diff --git a/.gitignore b/.gitignore index 6ebb996e24..e00c565d08 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ target *.tar *.asc *.tmp +.vs *~ .#* *#*# diff --git a/CHANGELOG.md b/CHANGELOG.md index 5848b3cd74..b4fb2bc4a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,160 @@ +# [2023-04-17] (Chart Release 4.35.0) + +## Release notes + + +* Wire cloud operators only: Before deploying apply the changes from https://github.com/zinfra/cailleach/pull/1586 to production as well. (#3146) + +* New 'ingress-nginx-controller' wrapper chart compatible with kubernetes versions [1.23 - 1.26]. The old one 'nginx-ingress-controller' (compatible only up to k8s 1.19) is now DEPRECATED. + We advise to upgrade your version of kubernetes in use to 1.23 or higher (we tested on kubernetes version 1.26), and to make use of the new ingress controller chart. Main features: + - up-to-date nginx version ('1.21.6') + - TLS 1.3 support (including allowing specifying which cipher suites to use) + - security fixes + - no more accidental logging of Wire access tokens under specific circumstances + + The 'kind: Ingress' resources installed via 'nginx-ingress-services' chart remain compatible with both the old and the new ingress controller, and k8s versions [1.18 - 1.26]. In case you upgrade an existing kubernetes cluster (not recommended), you may need to first uninstall the old controller before installing the new controller chart. + + In case you have custom overrides, you need to modify the directory name and top-level configuration key: + + ```diff + # If you have overrides for the controller chart (such as cipher suites), ensure to rename file and top-level key: + -# nginx-ingress-controller/values.yaml + +# ingress-nginx-controller/values.yaml + -nginx-ingress: + +ingress-nginx: + controller: + # ... + ``` + + and double-check if all overrides you use are indeed provided under the same name by the upstream chart. See also the default overrides in [the default values.yaml](https://github.com/wireapp/wire-server/blob/develop/charts/ingress-nginx-controller/values.yaml). + + In case you use helmfile change your ingress controller like this: + + ```diff + # helmfile.yaml + releases: + - - name: 'nginx-ingress-controller' + + - name: 'ingress-nginx-controller' + namespace: 'wire' + - chart: 'wire/nginx-ingress-controller' + + chart: 'wire/ingress-nginx-controller' + version: 'CHANGE_ME' + ``` + + For more information read the documentation under https://docs.wire.com/how-to/install/ingress.html (or go to https://docs.wire.com and search for "ingress-nginx-controller") (#3140) + +* If you are using OAuth (`optSettings.setOAuthEnabled: true` in brig config): before the deployment of wire-server the private and public keys for OAuth have to be provided for `brig` and `nginz` (see `docs/src/developer/reference/oauth.md` for more information) (#2989) + +* Upgrade webapp version to 2023-04-11-production.0-v0.31.13-0-bb91157 (#2302) + + +## API changes + + +* Adding a new version of /list-users that allows for partial success. (#3117) + +* Added a `failed_to_send` field to response when sending mls messages. (#3081) + +* List failed-to-add remote users in response to `POST /conversations` (#3150) + +* Updating the V4 version of /users/list-prekeys to return partial successes, listing users that could not be listed. (#3108) + +* Non-binding team endpoints are removed from API version V4 (#3213) + + +## Features + + +* Add TLS and basic authentication to the inbucket (fake webmailer) ingress. (#3161) + +* OAuth support for authorization of a curated list of 3rd party applications (see for details) (#2989) + +* Enforce a minimum length of 8 characters when setting a new password (#3137) + +* Optional password for guest links (#3149) + +* Authorization Code Flow with PKCE support (#3165) + +* `conversations/join` endpoint rate limited per IP address (#3202) + + +## Bug fixes and other updates + + +* coturn helm chart: use a memory-backed folder to store sqllite DB to improve performance (#3220) + +* Coturn helm chart: Increase the default timeout of liveness/readiness probe and make it configurable (#3218) + +* When using the (now deprecated) ingress controller on older versions of kubernetes, ensure query parameters are not logged in the ingress logs (#3139) + +* Fix version parsing in swagger-ui end-points (#3152) + +* Fix a rate-limit exemption whereby authenticated endpoints did not get the unlimited_requests_endpoint, if set, applied. This is a concern for the webapp and calls to /assets, which can happen in larger numbers on initial loading. A previous change in [this PR](https://github.com/wireapp/wire-server/pull/2786) had no effect. This PR also increases default rate limits, to compensate for [new ingress controller chart](https://github.com/wireapp/wire-server/pull/3140)'s default topologyAwareRouting. (#3138, #3201) + + +## Documentation + + +* Add a client API version bump checklist (#3135) + +* Fix the Swagger documentation for the failed_to_send field in the response of the Proteus message sending endpoint (#3223) + +* Extend docs to support render plantuml directly, rewrote the saml flow diagram in plantuml (#3226) + +* Allow swagger on disabled versions. (#3196) + +* Documentation of setting up SSO integration with Okta was outdated with images from Okta Classic UI, the new version was updated using Oktas latest design. (#3175) + + +## Internal changes + + +* When sending a push message, stop deleting the push token and start recreating + ARN when ARN is reported as invalid on AWS, but push token still is present in + Cassandra. This allows on-demand migrations from one AWS account used for push + notifications to another one. (#3162) + +* We don't explicitly set with-compiler inside the cabal.project file anymore, because the version of GHC is controlled by Nix, and our nixpkgs pin. (#3209) + +* - integration tests on CI will use either the old or the new ingress controller; depending on which kubernetes version they run on. + - upgrade `kubectl` to default from the nixpkgs channel (currently `1.26`) by removing the manual version pin on 1.19 + - upgrade `helmfile` to default from the nixpkgs channel by removing the manual version pin + - upgrade `helm` to default from the nixpkgs channel by removing the manual version pin + - add `kubelogin-oidc` so the kubectl in this environment can also talk to kubernetes clusters using OIDC (#3140) + +* Make new record syntax a language default (#3192) + +* nixpkgs has been bumped to a more recent checkout (8c619a1f3cedd16ea172146e30645e703d21bfc1 -> 402cc3633cc60dfc50378197305c984518b30773, 2023-02-12 -> 2023-03-28). (#3206) + +* Introduce VersionNumber newtype (see `/libs/wire-api/src/Wire/API/Routes/Version.hs` for explanation) (#3075) + +* Fix a memory leak in `gundeck` when Redis is offline (#3136) + +* Rust library `rusty-jwt-tools` upgraded to latest version (#3142) + +* Updated rusty-jwt-tools to version 0.3.4 (#3194) + +* Integration tests for backoffice/stern (#3216) + +* ormolu: don't redundantly add language extensions from dead package-defaults.yaml (#3193) + +* Stop support for versions on internal APIs (#3200) + +* helm charts: bump kubectl docker images from 1.19.7 to 1.24.12 (#3221) + +* Add an option (`UPLOAD_LOGS`) to upload integration test logs to AWS S3. (#3169) + + +## Federation changes + + +* Do not cause denial of service when creating a conversation with users from an unreachable backend (#3150) + +* Report federated Proteus message sending errors to clients (#3097) + +* Fix bug with asset downloads and large federated responses (#3154) + + # [2023-03-06] (Chart Release 4.34.0) ## Release notes @@ -71,8 +228,6 @@ * Deflake integration test: metrics (#3053) -* Document in code a function that sends remote Proteus messages (#PR_NOT_FOUND) - * Lower the log level of federator inotify (#3056) * use Wai's settings for graceful shutdown (#3069) @@ -442,7 +597,7 @@ * Fix copyright date on docs.wire.com (#2792) -* Improve and cross-link documentation on SNS / push notifications. (#PR_NOT_FOUND) +* Improve and cross-link documentation on SNS / push notifications. (#2781) * Add extension sphinx-reredirects and configuration to generate simple JavaScript based redirects to new locations of previously inconsistently named files/URLs. (#2811) diff --git a/Makefile b/Makefile index 21bd392ad4..dd49069100 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ DOCKER_TAG ?= $(USER) # default helm chart version must be 0.0.42 for local development (because 42 is the answer to the universe and everything) HELM_SEMVER ?= 0.0.42 # The list of helm charts needed on internal kubernetes testing environments -CHARTS_INTEGRATION := wire-server databases-ephemeral redis-cluster fake-aws nginx-ingress-controller nginx-ingress-services fluent-bit kibana sftd restund coturn +CHARTS_INTEGRATION := wire-server databases-ephemeral redis-cluster fake-aws ingress-nginx-controller nginx-ingress-controller nginx-ingress-services fluent-bit kibana sftd restund coturn # The list of helm charts to publish on S3 # FUTUREWORK: after we "inline local subcharts", # (e.g. move charts/brig to charts/wire-server/brig) @@ -17,7 +17,7 @@ CHARTS_RELEASE := wire-server redis-ephemeral redis-cluster databases-ephemeral fake-aws fake-aws-s3 fake-aws-sqs aws-ingress fluent-bit kibana backoffice \ calling-test demo-smtp elasticsearch-curator elasticsearch-external \ elasticsearch-ephemeral minio-external cassandra-external \ -nginx-ingress-controller nginx-ingress-services reaper sftd restund coturn \ +nginx-ingress-controller ingress-nginx-controller nginx-ingress-services reaper sftd restund coturn \ inbucket k8ssandra-test-cluster postgresql KIND_CLUSTER_NAME := wire-server HELM_PARALLELISM ?= 1 # 1 for sequential tests; 6 for all-parallel tests @@ -94,11 +94,16 @@ ci: c db-migrate .PHONY: sanitize-pr sanitize-pr: ./hack/bin/generate-local-nix-packages.sh - make formatf-all - make hlint-inplace-all + make formatf + make hlint-inplace-pr make git-add-cassandra-schema @git diff-files --quiet -- || ( echo "There are unstaged changes, please take a look, consider committing them, and try again."; exit 1 ) @git diff-index --quiet --cached HEAD -- || ( echo "There are staged changes, please take a look, consider committing them, and try again."; exit 1 ) + make list-flaky-tests + +list-flaky-tests: + @echo -e "\n\nif you want to run these, set RUN_FLAKY_TESTS=1\n\n" + @git grep -Hn '\bflakyTestCase \"' .PHONY: cabal-fmt cabal-fmt: @@ -159,7 +164,7 @@ services: init install format: ./tools/ormolu.sh -# formats all Haskell files even if local changes are not committed to git +# formats all Haskell files changed in this PR, even if local changes are not committed to git .PHONY: formatf formatf: ./tools/ormolu.sh -f pr @@ -216,7 +221,7 @@ upload-hoogle-image: ## cassandra management .PHONY: git-add-cassandra-schema -git-add-cassandra-schema: db-reset git-add-cassandra-schema-impl +git-add-cassandra-schema: db-migrate git-add-cassandra-schema-impl .PHONY: git-add-cassandra-schema-impl git-add-cassandra-schema-impl: @@ -270,8 +275,8 @@ ifeq ($(INTEGRATION_FEDERATION_TESTS), 1) $(EXE_SCHEMA) --keyspace $(package)_test2 --replication-factor 1 --reset endif endif - ./dist/brig-index reset --elasticsearch-index directory_test --elasticsearch-server http://localhost:9200 > /dev/null - ./dist/brig-index reset --elasticsearch-index directory_test2 --elasticsearch-server http://localhost:9200 > /dev/null + ./dist/brig-index reset --elasticsearch-index-prefix directory --elasticsearch-server http://localhost:9200 > /dev/null + ./dist/brig-index reset --elasticsearch-index-prefix directory2 --elasticsearch-server http://localhost:9200 > /dev/null # Usage: # @@ -340,7 +345,11 @@ kube-integration-setup: charts-integration .PHONY: kube-integration-test kube-integration-test: - export NAMESPACE=$(NAMESPACE); export HELM_PARALLELISM=$(HELM_PARALLELISM); ./hack/bin/integration-test.sh + export NAMESPACE=$(NAMESPACE); \ + export HELM_PARALLELISM=$(HELM_PARALLELISM); \ + export VERSION=${DOCKER_TAG}; \ + export UPLOAD_LOGS=${UPLOAD_LOGS}; \ + ./hack/bin/integration-test.sh .PHONY: kube-integration-teardown kube-integration-teardown: diff --git a/cabal.project b/cabal.project index ddfd0b8efc..fc707525ab 100644 --- a/cabal.project +++ b/cabal.project @@ -1,5 +1,3 @@ -with-compiler: ghc-9.2.4 - packages: libs/api-bot/ , libs/api-client/ diff --git a/cassandra-schema.cql b/cassandra-schema.cql index 292d52425f..a3b1e345be 100644 --- a/cassandra-schema.cql +++ b/cassandra-schema.cql @@ -418,6 +418,7 @@ CREATE TABLE galley_test.conversation_codes ( key ascii, scope int, conversation uuid, + password blob, value ascii, PRIMARY KEY (key, scope) ) WITH CLUSTERING ORDER BY (scope ASC) @@ -746,6 +747,67 @@ CREATE TABLE brig_test.team_invitation_info ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; +CREATE TABLE brig_test.provider_keys ( + key text PRIMARY KEY, + provider 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.oauth_refresh_token ( + id uuid PRIMARY KEY, + client uuid, + created_at timestamp, + scope set, + user uuid +) 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.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 = 14515200 + 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.team_invitation_email ( + email text, + team uuid, + code ascii, + invitation uuid, + PRIMARY KEY (email, team) +) WITH CLUSTERING ORDER BY (team 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 brig_test.rich_info ( user uuid PRIMARY KEY, json blob @@ -806,12 +868,14 @@ CREATE TABLE brig_test.service_tag ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; -CREATE TABLE brig_test.login_codes ( - user uuid PRIMARY KEY, - code text, - retries int, - timeout timestamp -) WITH bloom_filter_fp_chance = 0.01 +CREATE TABLE brig_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'} @@ -1001,13 +1065,11 @@ CREATE TABLE brig_test.service ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; -CREATE TABLE brig_test.team_invitation_email ( - email text, - team uuid, - code ascii, - invitation uuid, - PRIMARY KEY (email, team) -) WITH CLUSTERING ORDER BY (team ASC) +CREATE TABLE brig_test.oauth_user_refresh_token ( + user uuid, + token_id uuid, + PRIMARY KEY (user, token_id) +) WITH CLUSTERING ORDER BY (token_id ASC) AND bloom_filter_fp_chance = 0.01 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} AND comment = '' @@ -1015,7 +1077,7 @@ CREATE TABLE brig_test.team_invitation_email ( 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 default_time_to_live = 14515200 AND gc_grace_seconds = 864000 AND max_index_interval = 2048 AND memtable_flush_period_in_ms = 0 @@ -1165,13 +1227,35 @@ CREATE TABLE brig_test.nonce ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; -CREATE TABLE brig_test.provider_keys ( - key text PRIMARY KEY, - provider uuid -) WITH bloom_filter_fp_chance = 0.1 +CREATE TABLE brig_test.login_codes ( + user uuid PRIMARY KEY, + code text, + retries int, + timeout timestamp +) 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 + 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.oauth_client ( + id uuid PRIMARY KEY, + name text, + redirect_uri blob, + secret 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.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 @@ -1206,6 +1290,31 @@ CREATE TABLE brig_test.service_team ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; +CREATE TABLE brig_test.invitation ( + inviter uuid, + id uuid, + code ascii, + created_at timestamp, + email text, + name text, + phone text, + PRIMARY KEY (inviter, id) +) WITH CLUSTERING ORDER BY (id 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 brig_test.blacklist ( key text PRIMARY KEY ) WITH bloom_filter_fp_chance = 0.1 @@ -1397,20 +1506,21 @@ CREATE TABLE brig_test.prekeys ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; -CREATE TABLE brig_test.password_reset ( - key ascii PRIMARY KEY, - code ascii, - retries int, - timeout timestamp, +CREATE TABLE brig_test.oauth_auth_code ( + code ascii PRIMARY KEY, + client uuid, + code_challenge blob, + redirect_uri blob, + scope set, user uuid -) WITH bloom_filter_fp_chance = 0.1 +) 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 - AND default_time_to_live = 0 + AND default_time_to_live = 300 AND gc_grace_seconds = 864000 AND max_index_interval = 2048 AND memtable_flush_period_in_ms = 0 @@ -1534,42 +1644,16 @@ CREATE TABLE brig_test.connection ( AND speculative_retry = '99PERCENTILE'; CREATE INDEX conn_status ON brig_test.connection (status); -CREATE TABLE brig_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 brig_test.invitation ( - inviter uuid, - id uuid, +CREATE TABLE brig_test.password_reset ( + key ascii PRIMARY KEY, code ascii, - created_at timestamp, - email text, - name text, - phone text, - PRIMARY KEY (inviter, id) -) WITH CLUSTERING ORDER BY (id ASC) - AND bloom_filter_fp_chance = 0.01 + retries int, + timeout timestamp, + 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.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 diff --git a/charts/backoffice/templates/tests/configmap.yaml b/charts/backoffice/templates/tests/configmap.yaml new file mode 100644 index 0000000000..a20785b354 --- /dev/null +++ b/charts/backoffice/templates/tests/configmap.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: "stern-integration" + annotations: + "helm.sh/hook": post-install + "helm.sh/hook-delete-policy": before-hook-creation +data: + integration.yaml: | + brig: + host: brig + port: 8080 + + galley: + host: galley + port: 8080 + + stern: + host: backoffice + port: 8080 diff --git a/charts/backoffice/templates/tests/stern-integration.yaml b/charts/backoffice/templates/tests/stern-integration.yaml new file mode 100644 index 0000000000..e8c968ee64 --- /dev/null +++ b/charts/backoffice/templates/tests/stern-integration.yaml @@ -0,0 +1,25 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ .Release.Name }}-stern-integration" + annotations: + "helm.sh/hook": test + labels: + app: stern-integration + release: {{ .Release.Name }} +spec: + volumes: + - name: "stern-integration" + configMap: + name: "stern-integration" + containers: + - name: integration + image: "{{ .Values.image.repository }}-integration:{{ .Values.image.tag }}" + volumeMounts: + - name: "stern-integration" + mountPath: "/etc/wire/integration" + resources: + requests: + memory: "128Mi" + cpu: "1" + restartPolicy: Never diff --git a/charts/brig/templates/configmap.yaml b/charts/brig/templates/configmap.yaml index 45f664e865..781f90c9f1 100644 --- a/charts/brig/templates/configmap.yaml +++ b/charts/brig/templates/configmap.yaml @@ -302,8 +302,26 @@ data: {{- if .setEnableMLS }} setEnableMLS: {{ .setEnableMLS }} {{- end }} + {{- if $.Values.secrets.oauthJwkKeyPair }} + setOAuthJwkKeyPair: /etc/wire/brig/secrets/oauth_ed25519.jwk + {{- end }} + {{- if .setOAuthAuthCodeExpirationTimeSecs }} + setOAuthAuthCodeExpirationTimeSecs: {{ .setOAuthAuthCodeExpirationTimeSecs }} + {{- end }} + {{- if .setOAuthAccessTokenExpirationTimeSecs }} + setOAuthAccessTokenExpirationTimeSecs: {{ .setOAuthAccessTokenExpirationTimeSecs }} + {{- end }} + {{- if .setOAuthEnabled }} + setOAuthEnabled: {{ .setOAuthEnabled }} + {{- end }} {{- if .setDisabledAPIVersions }} setDisabledAPIVersions: {{ .setDisabledAPIVersions }} {{- end }} + {{- if .setOAuthRefreshTokenExpirationTimeSecs }} + setOAuthRefreshTokenExpirationTimeSecs: {{ .setOAuthRefreshTokenExpirationTimeSecs }} + {{- end }} + {{- if .setOAuthMaxActiveRefreshTokens }} + setOAuthMaxActiveRefreshTokens: {{ .setOAuthMaxActiveRefreshTokens }} + {{- end }} {{- end }} {{- end }} diff --git a/charts/brig/templates/secret.yaml b/charts/brig/templates/secret.yaml index dd967b4a67..eb073d97b3 100644 --- a/charts/brig/templates/secret.yaml +++ b/charts/brig/templates/secret.yaml @@ -28,5 +28,8 @@ data: {{- if .dpopSigKeyBundle }} dpop_sig_key_bundle.pem: {{ .dpopSigKeyBundle | b64enc | quote }} {{- end }} + {{- if .oauthJwkKeyPair }} + oauth_ed25519.jwk: {{ .oauthJwkKeyPair | b64enc | quote }} + {{- end }} {{- end }} diff --git a/charts/brig/templates/tests/brig-integration.yaml b/charts/brig/templates/tests/brig-integration.yaml index c8cfa602f1..c48c531f57 100644 --- a/charts/brig/templates/tests/brig-integration.yaml +++ b/charts/brig/templates/tests/brig-integration.yaml @@ -84,4 +84,8 @@ spec: - name: INTEGRATION_FEDERATION_TESTS value: "1" {{- end }} + resources: + requests: + memory: "512Mi" + cpu: "2" restartPolicy: Never diff --git a/charts/brig/values.yaml b/charts/brig/values.yaml index 6f97219f49..70d865a13c 100644 --- a/charts/brig/values.yaml +++ b/charts/brig/values.yaml @@ -87,9 +87,14 @@ config: setNonceTtlSecs: 300 # 5 minutes setDpopMaxSkewSecs: 1 setDpopTokenExpirationTimeSecs: 300 # 5 minutes + setOAuthAuthCodeExpirationTimeSecs: 300 # 5 minutes + setOAuthAccessTokenExpirationTimeSecs: 900 # 15 minutes + setOAuthRefreshTokenExpirationTimeSecs: 14515200 # 24 weeks + setOAuthEnabled: true + setOAuthMaxActiveRefreshTokens: 10 # Disable one ore more API versions. Please make sure the configuration value is the same in all these charts: # brig, cannon, cargohold, galley, gundeck, proxy, spar. - # setDisabledAPIVersions: [ 3 ] + # setDisabledAPIVersions: [ v3 ] smtp: passwordFile: /etc/wire/brig/secrets/smtp-password.txt proxy: {} diff --git a/charts/cannon/values.yaml b/charts/cannon/values.yaml index 9142603160..303638772a 100644 --- a/charts/cannon/values.yaml +++ b/charts/cannon/values.yaml @@ -24,7 +24,7 @@ config: # Disable one ore more API versions. Please make sure the configuration value is the same in all these charts: # brig, cannon, cargohold, galley, gundeck, proxy, spar. - # disabledAPIVersions: [ 3 ] + # disabledAPIVersions: [ v3 ] metrics: serviceMonitor: diff --git a/charts/cargohold/values.yaml b/charts/cargohold/values.yaml index 5445d1bc23..f5624d8cfc 100644 --- a/charts/cargohold/values.yaml +++ b/charts/cargohold/values.yaml @@ -28,7 +28,7 @@ config: downloadLinkTTL: 300 # Seconds # Disable one ore more API versions. Please make sure the configuration value is the same in all these charts: # brig, cannon, cargohold, galley, gundeck, proxy, spar. - # disabledAPIVersions: [ 3 ] + # disabledAPIVersions: [ v3 ] serviceAccount: # When setting this to 'false', either make sure that a service account named diff --git a/charts/coturn/templates/statefulset.yaml b/charts/coturn/templates/statefulset.yaml index 37ce6aef3e..93550eb97f 100644 --- a/charts/coturn/templates/statefulset.yaml +++ b/charts/coturn/templates/statefulset.yaml @@ -49,6 +49,10 @@ spec: - name: secrets secret: secretName: coturn + - name: coturndb + emptyDir: + medium: Memory + sizeLimit: 128Mi # observed size: 80 kilobytes {{- if .Values.tls.enabled }} - name: secrets-tls secret: @@ -56,7 +60,7 @@ spec: {{- end }} initContainers: - name: get-external-ip - image: bitnami/kubectl:1.19.7 + image: bitnami/kubectl:1.24.12 volumeMounts: - name: external-ip mountPath: /external-ip @@ -102,6 +106,11 @@ spec: - name: secrets mountPath: /secrets/ readOnly: true + # > By default, Coturn Docker image persists its data in /var/lib/coturn/ directory. + # > You can speedup Coturn simply by using tmpfs for that. + # We use a memory-backed emptyDir here instead. + - name: coturndb + mountPath: /var/lib/coturn {{- if .Values.tls.enabled }} - name: secrets-tls mountPath: /secrets-tls/ @@ -143,11 +152,15 @@ spec: protocol: TCP livenessProbe: + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} httpGet: path: / port: status-http readinessProbe: + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} httpGet: path: / port: status-http diff --git a/charts/coturn/values.yaml b/charts/coturn/values.yaml index 30bc42b470..a3b2c13f9e 100644 --- a/charts/coturn/values.yaml +++ b/charts/coturn/values.yaml @@ -53,3 +53,11 @@ coturnGracefulTermination: false # terminated. This setting is only effective when coturnGracefulTermination is # set to true. coturnGracePeriodSeconds: 86400 # one day + +livenessProbe: + timeoutSeconds: 5 + failureThreshold: 5 + +readinessProbe: + timeoutSeconds: 5 + failureThreshold: 5 diff --git a/charts/galley/templates/deployment.yaml b/charts/galley/templates/deployment.yaml index c5f1b9ee25..c1594f9996 100644 --- a/charts/galley/templates/deployment.yaml +++ b/charts/galley/templates/deployment.yaml @@ -26,7 +26,7 @@ spec: # An annotation of the configmap checksum ensures changes to the configmap cause a redeployment upon `helm upgrade` checksum/configmap: {{ include (print .Template.BasePath "/configmap.yaml") . | sha256sum }} checksum/aws-secret: {{ include (print .Template.BasePath "/aws-secret.yaml") . | sha256sum }} - checksum/mls-secret: {{ include (print .Template.BasePath "/mls-secret.yaml") . | sha256sum }} + checksum/secret: {{ include (print .Template.BasePath "/secret.yaml") . | sha256sum }} spec: serviceAccountName: {{ .Values.serviceAccount.name }} volumes: @@ -35,7 +35,7 @@ spec: name: "galley" - name: "galley-secrets" secret: - secretName: "galley-mls" + secretName: "galley" containers: - name: galley image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" diff --git a/charts/galley/templates/mls-secret.yaml b/charts/galley/templates/secret.yaml similarity index 95% rename from charts/galley/templates/mls-secret.yaml rename to charts/galley/templates/secret.yaml index 1b77c325a9..354c52fc1e 100644 --- a/charts/galley/templates/mls-secret.yaml +++ b/charts/galley/templates/secret.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: Secret metadata: - name: galley-mls + name: galley labels: app: galley chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" diff --git a/charts/galley/templates/tests/galley-integration.yaml b/charts/galley/templates/tests/galley-integration.yaml index c543eb2048..13e72fe551 100644 --- a/charts/galley/templates/tests/galley-integration.yaml +++ b/charts/galley/templates/tests/galley-integration.yaml @@ -39,7 +39,7 @@ spec: name: "galley-integration-secrets" - name: "galley-secrets" secret: - secretName: "galley-mls" + secretName: "galley" containers: - name: integration image: "{{ .Values.image.repository }}-integration:{{ .Values.image.tag }}" @@ -61,4 +61,8 @@ spec: value: "dummy" - name: AWS_REGION value: "eu-west-1" + resources: + requests: + memory: "512Mi" + cpu: "2" restartPolicy: Never diff --git a/charts/galley/values.yaml b/charts/galley/values.yaml index c54d90292b..a6fc86f4aa 100644 --- a/charts/galley/values.yaml +++ b/charts/galley/values.yaml @@ -34,7 +34,7 @@ config: indexedBillingTeamMember: false # Disable one ore more API versions. Please make sure the configuration value is the same in all these charts: # brig, cannon, cargohold, galley, gundeck, proxy, spar. - # disabledAPIVersions: [ 3 ] + # disabledAPIVersions: [ v3 ] featureFlags: # see #RefConfigOptions in `/docs/reference` (https://github.com/wireapp/wire-server/) appLock: defaults: diff --git a/charts/gundeck/values.yaml b/charts/gundeck/values.yaml index 3f8a547229..d37b248292 100644 --- a/charts/gundeck/values.yaml +++ b/charts/gundeck/values.yaml @@ -37,7 +37,7 @@ config: soft: 1000 # Disable one ore more API versions. Please make sure the configuration value is the same in all these charts: # brig, cannon, cargohold, galley, gundeck, proxy, spar. - # disabledAPIVersions: [ 3 ] + # disabledAPIVersions: [ v3 ] serviceAccount: # When setting this to 'false', either make sure that a service account named diff --git a/charts/inbucket/templates/basic-auth-secret.yaml b/charts/inbucket/templates/basic-auth-secret.yaml new file mode 100644 index 0000000000..9918cbb716 --- /dev/null +++ b/charts/inbucket/templates/basic-auth-secret.yaml @@ -0,0 +1,15 @@ +{{- if (hasKey .Values "basicAuthSecret") }} +apiVersion: v1 +kind: Secret +metadata: + name: inbucket-basic-auth + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ include "inbucket.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + helm.sh/chart: {{ include "inbucket.chart" . }} +type: Opaque +data: + auth: {{ .Values.basicAuthSecret | b64enc | quote }} +{{- end }} diff --git a/charts/inbucket/templates/cert.yaml b/charts/inbucket/templates/cert.yaml new file mode 100644 index 0000000000..3d6996212f --- /dev/null +++ b/charts/inbucket/templates/cert.yaml @@ -0,0 +1,32 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: "letsencrypt-inbucket-csr" + namespace: {{ .Release.Namespace }} + labels: + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" + annotations: + # The issuer changes when it's configured to use staging or prod ACME + # servers. + checksum/issuer: {{ include (print .Template.BasePath "/issuer.yaml") . | sha256sum }} +spec: + issuerRef: + name: letsencrypt-inbucket + kind: Issuer + usages: + - server auth + duration: 2160h # 90d, Letsencrypt default; NOTE: changes are ignored by Letsencrypt + renewBefore: 360h # 15d + isCA: false + secretName: letsencrypt-inbucket-secret + + privateKey: + algorithm: ECDSA + size: 384 # 521 is not supported by Letsencrypt + encoding: PKCS1 + rotationPolicy: Always + + dnsNames: + - {{ .Values.host }} diff --git a/charts/inbucket/templates/ingress.yaml b/charts/inbucket/templates/ingress.yaml index c2803b4e00..1866c5fc6a 100644 --- a/charts/inbucket/templates/ingress.yaml +++ b/charts/inbucket/templates/ingress.yaml @@ -10,7 +10,18 @@ metadata: app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/managed-by: {{ .Release.Service }} helm.sh/chart: {{ include "inbucket.chart" . }} + annotations: + kubernetes.io/ingress.class: nginx +{{- if (hasKey .Values "basicAuthSecret") }} + nginx.ingress.kubernetes.io/auth-type: basic + nginx.ingress.kubernetes.io/auth-secret: inbucket-basic-auth + nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - inbucket' +{{- end }} spec: + tls: + - hosts: + - {{ required "must specify host" .Values.host | quote }} + secretName: letsencrypt-inbucket-secret rules: - host: {{ required "must specify host" .Values.host | quote }} http: diff --git a/charts/inbucket/templates/issuer.yaml b/charts/inbucket/templates/issuer.yaml new file mode 100644 index 0000000000..d3f1d1bc94 --- /dev/null +++ b/charts/inbucket/templates/issuer.yaml @@ -0,0 +1,27 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt-inbucket + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ include "inbucket.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + helm.sh/chart: {{ include "inbucket.chart" . }} +spec: + acme: +{{- if .Values.useStagingACMEServer }} + server: https://acme-staging-v02.api.letsencrypt.org/directory +{{- else }} + server: https://acme-v02.api.letsencrypt.org/directory +{{- end }} + # Email address used for ACME registration + email: {{ required "must specify certManager.certmasterEmail" .Values.certManager.certmasterEmail | quote }} + # Name of a secret used to store the ACME account private key + privateKeySecretRef: + name: letsencrypt-inbucket-key + # Enable the HTTP-01 challenge provider + solvers: + - http01: + ingress: + class: nginx diff --git a/charts/inbucket/values.yaml b/charts/inbucket/values.yaml index 3bff990c7e..4a15cfa1ab 100644 --- a/charts/inbucket/values.yaml +++ b/charts/inbucket/values.yaml @@ -11,3 +11,12 @@ inbucket: INBUCKET_WEB_GREETINGFILE: "/config/greeting.html" INBUCKET_MAILBOXNAMING: full INBUCKET_STORAGE_RETENTIONPERIOD: "72h" + +# The production ACME server of let's encrypt has a very strict rate limiting +# and bans for weeks. Better try with the staging ACME server first. +useStagingACMEServer: true + +# Enables and configures HTTP Basic Auth secret as e.g. created with +# `htpasswd -bc auth username password`. +# +# basicAuthSecret: username:$apr1$3jXFMMZX$z6OOf4eUn1wU.NYJt246u1 diff --git a/charts/ingress-nginx-controller/Chart.yaml b/charts/ingress-nginx-controller/Chart.yaml new file mode 100644 index 0000000000..479a9b5d90 --- /dev/null +++ b/charts/ingress-nginx-controller/Chart.yaml @@ -0,0 +1,8 @@ +apiVersion: v2 +description: A Helm chart for an ingress controller (using nginx) on Kubernetes +name: ingress-nginx-controller +version: 0.0.42 +dependencies: +- name: ingress-nginx + version: 4.5.2 # k8s compatibility [1.23 - 1.26] + repository: https://kubernetes.github.io/ingress-nginx diff --git a/charts/ingress-nginx-controller/values.yaml b/charts/ingress-nginx-controller/values.yaml new file mode 100644 index 0000000000..d8fd654f93 --- /dev/null +++ b/charts/ingress-nginx-controller/values.yaml @@ -0,0 +1,58 @@ +# The following defaults apply to a cloud-like setup (in which you can ask your +# kubernetes installation to give you a LoadBalancer setup). +# +# If you are on bare metal and wish an installation similiar in spirit as the +# older similarly named wrapper chart 'nginx-ingress-controller' (note the +# swapped words 'nginx' and 'ingress'), where we assume no load balancer support +# and instead expose NodePorts on ports 31773 and 31772, and where you need to +# ensure traffic gets to these ports in another way; then please read the +# documentation on https://docs.wire.com/how-to/install/ingress.html (or go to +# https://docs.wire.com and search for "ingress-nginx-controller") +# +# See +# https://github.com/kubernetes/ingress-nginx/blob/main/charts/ingress-nginx/values.yaml +# for all possible values to override. +ingress-nginx: + controller: + enableTopologyAwareRouting: true + # Use kind: `DaemonSet` (when using NodePort) or `Deployment` (when using + # LoadBalancer) + kind: Deployment + service: + type: LoadBalancer # or NodePort (then also use DaemonSet) + # set externalTrafficPolicy=Local to keep the source IP available in + # upstream services. Comes with tradeoff considerations, see + # documentation on "ingress" on docs.wire.com + externalTrafficPolicy: Local + nodePorts: + # If you set service.type = NodePort, then the nginx controller instance + # is exposed on ports 31773 (https) and 31772 (http) on the node on + # which it runs. You should add a port-forwarding rule on the node or on + # the loadbalancer that forwards ports 443 and 80 to these respective + # ports. + https: 31773 + http: 31772 + config: + # NOTE: These are some sane defaults (compliant to TR-02102-2), you may + # want to overrride them on your own installation For TR-02102-2 see + # https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/TechGuidelines/TG02102/BSI-TR-02102-2.html + # As a Wire employee, for Wire-internal discussions and context see * + # https://wearezeta.atlassian.net/browse/FS-33 * + # https://wearezeta.atlassian.net/browse/FS-444 + ssl-protocols: "TLSv1.2 TLSv1.3" + # override cipher suites used in TLS 1.2 (only, if TLS 1.2 is used) + ssl-ciphers: "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384" + # override cipher suites used in TLS 1.3 (only, if TLS 1.3 is used) + server-snippet: "ssl_conf_command Ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384;" + # used to be called http2-max-(header|field)-size, removed in controller v1.3 + large-client-header-buffers: "16 32k" + proxy-buffer-size: "16k" + proxy-body-size: "1024m" + hsts-max-age: "31536000" + # Override log format to remove logging access tokens: + # removes 'request_query: "$args"', since it can include '?access_token=...' + # (sometimes sent for assets and websocket establishments) + # We do not wish to log these (SEC-47) + # Also add ssl/tls protocol/cipher to gain some observability here (can we turn off TLS 1.2?) + log-format-escape-json: true + log-format-upstream: '{"bytes_sent": "$bytes_sent", "duration": "$request_time", "http_referrer": "$http_referer", "http_user_agent": "$http_user_agent", "method": "$request_method", "path": "$uri", "remote_addr": "$proxy_protocol_addr", "remote_user": "$remote_user", "request_id": "$req_id", "request_length": "$request_length", "request_proto": "$server_protocol", "request_time": "$request_time", "status": "$status", "time": "$time_iso8601", "tls_cipher": "$ssl_cipher", "tls_protocol": "$ssl_protocol", "vhost": "$host", "x_forwarded_for": "$proxy_add_x_forwarded_for"}' diff --git a/charts/nginx-ingress-controller/Chart.yaml b/charts/nginx-ingress-controller/Chart.yaml index 96f94a8958..3177840c9c 100644 --- a/charts/nginx-ingress-controller/Chart.yaml +++ b/charts/nginx-ingress-controller/Chart.yaml @@ -1,4 +1,5 @@ apiVersion: v1 -description: A Helm chart for an ingress controller (using nginx) on Kubernetes +description: ingress-controller. DEPRECATED. Use ingress-nginx-controller chart instead. name: nginx-ingress-controller version: 0.0.42 +deprecated: true diff --git a/charts/nginx-ingress-controller/README.md b/charts/nginx-ingress-controller/README.md index 11a8508044..b0a4d744b1 100644 --- a/charts/nginx-ingress-controller/README.md +++ b/charts/nginx-ingress-controller/README.md @@ -1,3 +1,5 @@ +WARNING: deprecated. Use ingress-nginx-controller instead, if possible. + This deploys a single ingress controller - ideally, you want this on a separate, shared namespace since controllers listen on all namespaces by default (you can also modify that but it's generally discouraged). It is mostly a wrapper of the [nginx-ingress](https://github.com/helm/charts/blob/master/stable/nginx-ingress/README.md) with some other defaults that make sense for our use case(s). diff --git a/charts/nginx-ingress-controller/values.yaml b/charts/nginx-ingress-controller/values.yaml index 6063fa239a..ee4d1e5824 100644 --- a/charts/nginx-ingress-controller/values.yaml +++ b/charts/nginx-ingress-controller/values.yaml @@ -15,6 +15,13 @@ nginx-ingress: http2-max-header-size: 32k proxy-buffer-size: 16k proxy-body-size: 1024m + + # custom log format, remove query parameters from logs as they sometimes contain sensitive information like access tokens (context: websocket establishment in browsers) + # See also SEC-47 for context. + # default log format (for image 0.30.0) in + # https://github.com/kubernetes/ingress-nginx/blob/49f20f849cc13564691acc49f639955f02f3c75e/docs/user-guide/nginx-configuration/configmap.md + # If ever needing to debug query parameter usage, you can use the (sanitized) logs from nginz instead. + log-format-upstream: '$remote_addr - $remote_user [$time_local] "$request_method $uri" $status $body_bytes_sent "$http_referer" "$http_user_agent" $request_length $request_time [$proxy_upstream_name] [$proxy_alternative_upstream_name] $upstream_addr $upstream_response_length $upstream_response_time $upstream_status $req_id' # Normally, NodePort will listen to traffic on all nodes, and uses kube-proxy # to redirect to the node that actually runs nginx-ingress-controller. However # one problem with this is that this traffic is NAT'ed. This means that nginx @@ -43,6 +50,7 @@ nginx-ingress: # downsides of this setting # https://www.asykim.com/blog/deep-dive-into-kubernetes-external-traffic-policies kind: DaemonSet + ingressClass: nginx # By default, each node will now be configured to accept ingress traffic. You should add # all the nodes to your external load balancer, or add them to DNS records. # diff --git a/charts/nginx-ingress-services/README.md b/charts/nginx-ingress-services/README.md index 50eb736fa8..2dadd73b38 100644 --- a/charts/nginx-ingress-services/README.md +++ b/charts/nginx-ingress-services/README.md @@ -1,5 +1,5 @@ This helm chart is a helper to set up needed services, ingresses and (likely) secrets to access your cluster. -It will _NOT_ deploy an ingress controller! Ensure you already have one on your cluster - or have a look at our [nginx-ingress-controller](../nginx-ingress-controller/README.md) +It will _NOT_ deploy an ingress controller! Ensure you already have one on your cluster - or have a look at our [ingress-nginx-controller](../ingress-nginx-controller/README.md) If tls.enabled == true, then you need to supply 2 variables, `tlsWildcardCert` and `tlsWildcardKey` that could either be supplied as plain text in the form of a `-f path/to/secrets.yaml`, like this: @@ -28,16 +28,14 @@ Q: My ingress keeps serving "Kubernetes Ingress Controller Fake Certificate"!! A: Ensure that your certificate is _valid_ and has _not expired_; trying to serve expired certificates will silently fail and the nginx ingress will simply fallback to the default certificate. - ## About cert-manager ### Prerequisites -* `cert-manager` and its CRDs have to be installed upfront, +* `cert-manager` and its CRDs have to be installed upfront, e.g. `helm upgrade --install -n cert-manager-ns --set 'installCRDs=true' cert-manager jetstack/cert-manager`, because upstream decided that this is the way (https://github.com/jetstack/cert-manager/pull/2964) - ### What does this chart do? * define `Ingress` for various services and their corresponding FQDNS @@ -45,10 +43,4 @@ A: Ensure that your certificate is _valid_ and has _not expired_; trying to serv *cert-manager* take care of this * [optional] configure an *Issuer* to issue ACME HTTP01 certificates provided by Letsencrypt * [optional] define a *Certificate* representation that causes *cert-manager* to issue a - certificate that is then used by `Ingress` - - -### Todo when introducing support for K8s >= 1.15 - -* the `apiVersion` of all resources based on cert-manager's CRDs, namely `./templates/issuer.yaml` and - `./templates/certificate.yaml`, has to be changed to `cert-manager.io/v1alpha3` + certificate that is then used by `Ingress` diff --git a/charts/nginx-ingress-services/templates/_helpers.tpl b/charts/nginx-ingress-services/templates/_helpers.tpl index 32f4467617..7b0faa4711 100644 --- a/charts/nginx-ingress-services/templates/_helpers.tpl +++ b/charts/nginx-ingress-services/templates/_helpers.tpl @@ -86,3 +86,7 @@ Returns the Letsencrypt API server URL based on whether testMode is enabled or d {{- define "ingress.supportsPathType" -}} {{- or (eq (include "ingress.isStable" .) "true") (and (eq (include "ingress.apiVersion" .) "networking.k8s.io/v1beta1") (semverCompare ">= 1.18-0" (include "kubeVersion" .))) -}} {{- end -}} + +{{- define "integrationTestHelperNewLabels" -}} + {{- (semverCompare ">= 1.23-0" (include "kubeVersion" .)) -}} +{{- end -}} diff --git a/charts/nginx-ingress-services/templates/federation-test-helper.yaml b/charts/nginx-ingress-services/templates/federation-test-helper.yaml index 4c06577052..0fb621e34d 100644 --- a/charts/nginx-ingress-services/templates/federation-test-helper.yaml +++ b/charts/nginx-ingress-services/templates/federation-test-helper.yaml @@ -1,3 +1,4 @@ +{{- $newLabels := eq (include "integrationTestHelperNewLabels" .) "true" -}} # Assumes that the controller is deployed in the same namespace. Only used for # enabling discovery by creating SRV records while running integration tests. {{- if (and .Values.federator.enabled .Values.federator.integrationTestHelper) }} @@ -13,7 +14,12 @@ spec: protocol: TCP targetPort: https selector: + {{- if $newLabels }} + app.kubernetes.io/component: controller + app.kubernetes.io/name: ingress-nginx + {{- else }} app: nginx-ingress component: controller + {{- end }} type: ClusterIP {{- end }} diff --git a/charts/nginx-ingress-services/templates/ingress.yaml b/charts/nginx-ingress-services/templates/ingress.yaml index 0314ee9513..6b19cdd090 100644 --- a/charts/nginx-ingress-services/templates/ingress.yaml +++ b/charts/nginx-ingress-services/templates/ingress.yaml @@ -5,7 +5,7 @@ kind: Ingress metadata: name: nginx-ingress annotations: - kubernetes.io/ingress.class: "nginx" + kubernetes.io/ingress.class: "{{ .Values.config.ingressClass }}" spec: # This assumes you have created the given cert (see secret.yaml) # https://github.com/kubernetes/ingress-nginx/blob/master/docs/examples/PREREQUISITES.md#tls-certificates diff --git a/charts/nginx-ingress-services/templates/ingress_federator.yaml b/charts/nginx-ingress-services/templates/ingress_federator.yaml index 8d52ff9a33..0318d9ce5b 100644 --- a/charts/nginx-ingress-services/templates/ingress_federator.yaml +++ b/charts/nginx-ingress-services/templates/ingress_federator.yaml @@ -8,7 +8,7 @@ kind: Ingress metadata: name: federator-ingress annotations: - kubernetes.io/ingress.class: "nginx" + kubernetes.io/ingress.class: "{{ .Values.config.ingressClass }}" nginx.ingress.kubernetes.io/ssl-redirect: "true" nginx.ingress.kubernetes.io/backend-protocol: "HTTP" nginx.ingress.kubernetes.io/auth-tls-verify-client: "on" diff --git a/charts/nginx-ingress-services/values.yaml b/charts/nginx-ingress-services/values.yaml index b76a61390d..259d80540f 100644 --- a/charts/nginx-ingress-services/values.yaml +++ b/charts/nginx-ingress-services/values.yaml @@ -91,8 +91,9 @@ service: accountPages: externalPort: 8080 +config: + ingressClass: "nginx" # You will need to supply some DNS names, namely -# config: # dns: # https: nginz-https. # ssl: nginz-ssl. # For websockets diff --git a/charts/nginz/templates/conf/_nginx.conf.tpl b/charts/nginz/templates/conf/_nginx.conf.tpl index 052cf0b52f..13896d814f 100644 --- a/charts/nginz/templates/conf/_nginx.conf.tpl +++ b/charts/nginz/templates/conf/_nginx.conf.tpl @@ -162,9 +162,7 @@ http { limit_req_log_level warn; limit_conn_log_level warn; - # Limit by $zauth_user if present and not part of rate limit exemptions - limit_req zone=reqs_per_user burst=20; - limit_conn conns_per_user 25; + limit_conn conns_per_user 75; # # Proxied Upstream Services @@ -193,6 +191,7 @@ http { zauth_keystore {{ .Values.nginx_conf.zauth_keystore }}; zauth_acl {{ .Values.nginx_conf.zauth_acl }}; + oauth_pub_key {{ .Values.nginx_conf.oauth_pub_key }}; location /status { zauth off; @@ -256,6 +255,16 @@ http { limit_conn conns_per_addr 20; {{- end }} {{- end }} + {{- else }} + {{- if ($location.unlimited_requests_endpoint) }} + # Note that this endpoint has no rate limit per user for authenticated requests + {{- else }} + limit_req zone=reqs_per_user burst=20 nodelay; + {{- end }} + {{- end }} + + {{- if ($location.oauth_scope) }} + oauth_scope {{ $location.oauth_scope }}; {{- end }} {{- if hasKey $location "specific_user_rate_limit" }} @@ -345,6 +354,7 @@ http { # we need to specify zauth_keystore etc. zauth_keystore {{ .Values.nginx_conf.zauth_keystore }}; zauth_acl {{ .Values.nginx_conf.zauth_acl }}; + oauth_pub_key {{ .Values.nginx_conf.oauth_pub_key }}; listen {{ .Values.config.http.metricsPort }}; diff --git a/charts/nginz/templates/secret.yaml b/charts/nginz/templates/secret.yaml index 12779270f6..1c03a612f7 100644 --- a/charts/nginz/templates/secret.yaml +++ b/charts/nginz/templates/secret.yaml @@ -15,4 +15,7 @@ data: {{- with .Values.secrets }} zauth.conf: {{ .zAuth.publicKeys | b64enc | quote }} basic_auth.txt: {{ .basicAuth | b64enc | quote }} + {{- if .oAuth }} + oauth_ed25519_pub.jwk: {{ .oAuth.publicKeys | b64enc | quote }} + {{- end }} {{- end }} diff --git a/charts/nginz/values.yaml b/charts/nginz/values.yaml index 2427de7753..fc62d4d06c 100644 --- a/charts/nginz/values.yaml +++ b/charts/nginz/values.yaml @@ -30,6 +30,7 @@ nginx_conf: zauth_keystore: /etc/wire/nginz/secrets/zauth.conf zauth_acl: /etc/wire/nginz/conf/zauth.acl basic_auth_file: /etc/wire/nginz/secrets/basic_auth.txt + oauth_pub_key: /etc/wire/nginz/secrets/oauth_ed25519_pub.jwk worker_processes: auto worker_rlimit_nofile: 131072 worker_connections: 65536 @@ -55,8 +56,8 @@ nginx_conf: - /search/common default_client_max_body_size: "256k" - rate_limit_reqs_per_user: "10r/s" - rate_limit_reqs_per_addr: "5r/m" + rate_limit_reqs_per_user: "30r/s" + rate_limit_reqs_per_addr: "15r/m" # This value must be a list of strings. Each string is copied verbatim into # the nginx.conf after the default 'limit_req_zone' directives. This should be @@ -159,6 +160,7 @@ nginx_conf: envs: - staging - path: /self$ # Matches exactly /self + oauth_scope: self envs: - all - path: /self/name @@ -299,6 +301,12 @@ nginx_conf: disable_zauth: true basic_auth: true versioned: false + - path: /i/users/([^/]*)/verification-code/([^/])* + envs: + - staging + disable_zauth: true + basic_auth: true + versioned: false - path: /i/teams/([^/]*)/suspend envs: - staging @@ -376,6 +384,34 @@ nginx_conf: envs: - all disable_zauth: true + - path: /system/settings$ + envs: + - all + - path: /oauth/clients/([^/]*)$ + envs: + - all + - path: /i/oauth/clients$ + envs: + - staging + disable_zauth: true + basic_auth: true + - path: /oauth/authorization/codes + envs: + - all + - path: /oauth/applications + envs: + - all + - path: /oauth/token + envs: + - all + disable_zauth: true # authorized by auth code (see above) + - path: /oauth/revoke + envs: + - all + disable_zauth: true + - path: /oauth/applications + envs: + - all galley: - path: /conversations/code-check disable_zauth: true @@ -404,6 +440,21 @@ nginx_conf: - all max_body_size: 40m body_buffer_size: 256k + - path: /conversations$ + envs: + - all + doc: true + oauth_scope: conversations + - path: /conversations/([^/]*)/code + envs: + - all + doc: true + oauth_scope: conversations_code + - path: /conversations/join + envs: + - all + specific_user_rate_limit: reqs_per_addr + specific_user_rate_limit_burst: "10" - path: /conversations envs: - all @@ -475,6 +526,7 @@ nginx_conf: - path: /feature-configs(.*) envs: - all + oauth_scope: feature_configs - path: /mls/welcome envs: - all diff --git a/charts/proxy/values.yaml b/charts/proxy/values.yaml index 6dd53032a9..b8a6c77dbb 100644 --- a/charts/proxy/values.yaml +++ b/charts/proxy/values.yaml @@ -21,4 +21,4 @@ config: proxy: {} # Disable one ore more API versions. Please make sure the configuration value is the same in all these charts: # brig, cannon, cargohold, galley, gundeck, proxy, spar. - # disabledAPIVersions: [ 3 ] + # disabledAPIVersions: [ v3 ] diff --git a/charts/reaper/templates/deployment.yaml b/charts/reaper/templates/deployment.yaml index 8a11b60b4f..89b581b094 100644 --- a/charts/reaper/templates/deployment.yaml +++ b/charts/reaper/templates/deployment.yaml @@ -22,7 +22,7 @@ spec: serviceAccountName: reaper-role containers: - name: reaper - image: bitnami/kubectl:1.19.7 + image: bitnami/kubectl:1.24.12 command: ["bash"] args: - -c diff --git a/charts/restund/templates/statefulset.yaml b/charts/restund/templates/statefulset.yaml index da29825c4d..87fa6571c2 100644 --- a/charts/restund/templates/statefulset.yaml +++ b/charts/restund/templates/statefulset.yaml @@ -49,7 +49,7 @@ spec: {{- end }} initContainers: - name: get-external-ip - image: bitnami/kubectl:1.19.7 + image: bitnami/kubectl:1.24.12 volumeMounts: - name: external-ip mountPath: /external-ip diff --git a/charts/sftd/templates/statefulset.yaml b/charts/sftd/templates/statefulset.yaml index 32fe9e0622..8559acf765 100644 --- a/charts/sftd/templates/statefulset.yaml +++ b/charts/sftd/templates/statefulset.yaml @@ -47,7 +47,7 @@ spec: {{- end }} initContainers: - name: get-external-ip - image: bitnami/kubectl:1.19.7 + image: bitnami/kubectl:1.24.12 volumeMounts: - name: external-ip mountPath: /external-ip diff --git a/charts/spar/templates/tests/spar-integration.yaml b/charts/spar/templates/tests/spar-integration.yaml index 7063bbb745..b646c623c8 100644 --- a/charts/spar/templates/tests/spar-integration.yaml +++ b/charts/spar/templates/tests/spar-integration.yaml @@ -24,5 +24,8 @@ spec: mountPath: "/etc/wire/integration" - name: "spar-config" mountPath: "/etc/wire/spar/conf" - + resources: + requests: + memory: "512Mi" + cpu: "2" restartPolicy: Never diff --git a/charts/spar/values.yaml b/charts/spar/values.yaml index c2023b6634..65433c3820 100644 --- a/charts/spar/values.yaml +++ b/charts/spar/values.yaml @@ -27,4 +27,4 @@ config: proxy: {} # Disable one ore more API versions. Please make sure the configuration value is the same in all these charts: # brig, cannon, cargohold, galley, gundeck, proxy, spar. - # disabledAPIVersions: [ 3 ] + # disabledAPIVersions: [ v3 ] diff --git a/charts/webapp/values.yaml b/charts/webapp/values.yaml index 015b109ada..a2314d9efe 100644 --- a/charts/webapp/values.yaml +++ b/charts/webapp/values.yaml @@ -9,7 +9,7 @@ resources: cpu: "1" image: repository: quay.io/wire/webapp - tag: "2023-01-24-production.0-v0.31.9-0-17b742f" + tag: "2023-04-11-production.0-v0.31.13-0-bb91157" service: https: externalPort: 443 diff --git a/docs/diagrams/README.md b/docs/diagrams/README.md index ed60a1b3ff..62a90726ed 100644 --- a/docs/diagrams/README.md +++ b/docs/diagrams/README.md @@ -18,6 +18,26 @@ This is a "diagrams playground" folder and you don't have to use this. Instead, B->>-A: Greetings! ``` +To create diagrams in a markdown file, you can use the same syntax as above but wrap it in triple backticks (used for code blocks) with `{eval-rst}` as the language identifier, e.g.: + +````markdown +```{eval-rst} +.. kroki:: + :type: mermaid + :caption: Diagram + + %% The message sending flow as implemented for M1 (Oct 2021) federation + sequenceDiagram + actor A as Alice + participant B as Backend + + Note over A,B: send message + %% this is a code-comment not visible + A->>+B: Hello! + B->>-A: Greetings!!!! +``` +```` + For more information, see syntax on https://mermaid-js.github.io/mermaid/#/sequenceDiagram # Playground mermaid setup and further links diff --git a/docs/src/conf.py b/docs/src/conf.py index 939c35ae68..352e744f5c 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -34,6 +34,7 @@ # ones. extensions = [ 'sphinxcontrib.kroki', + 'sphinxcontrib.plantuml', "myst_parser", 'rst2pdf.pdfbuilder', 'sphinx_multiversion', diff --git a/docs/src/developer/developer/api-versioning.md b/docs/src/developer/developer/api-versioning.md index 539f73decb..053c830749 100644 --- a/docs/src/developer/developer/api-versioning.md +++ b/docs/src/developer/developer/api-versioning.md @@ -103,6 +103,21 @@ by this system. It is still possible to implement them, but they will appear as different endpoints (and have different names) on the same path, and with non-overlapping version ranges. +### Version bump checklist + +When making the client API version bump, i.e., when finalising a version, there +are several steps to make apart from deciding what endpoint changes are part of +the version: + + - In `wire-api` extend the `Version` type with a new version by appending the + new version to the end, e.g., by adding `V4`. Make sure to update its + `ToSchema` instance, + - In the same `Version` module update the `developmentVersions` value to list + only the new version, + - Consider updating the `backendApiVersion` value in Stern, which is + unit-tested by checking if it is listed as supported in the response to `GET + /api-version`. + ### Examples of endpoint evolution In the following, we present some examples of API changes and how they might be diff --git a/docs/src/developer/developer/editor-setup.md b/docs/src/developer/developer/editor-setup.md index 7b39f8a463..a9e2d92db6 100644 --- a/docs/src/developer/developer/editor-setup.md +++ b/docs/src/developer/developer/editor-setup.md @@ -92,19 +92,18 @@ or check the entire repository. This takes about 10 seconds. Vim integration is [linked here](https://github.com/tweag/ormolu#editor-integration). -If you use sdiehl's module, you you need to collect the language extensions from `package-defaults.yaml`: +If you use sdiehl's module, you may need to collect the language extensions from a cabal file: ``` - let g:ormolu_options = ["--ghc-opt -XAllowAmbiguousTypes --ghc-opt -XBangPatterns --ghc-opt -XConstraintKinds --ghc-opt -XDataKinds --ghc-opt -XDefaultSignatures --ghc-opt -XDerivingStrategies --ghc-opt -XDeriveFunctor --ghc-opt -XDeriveGeneric --ghc-opt -XDeriveLift --ghc-opt -XDeriveTraversable --ghc-opt -XEmptyCase --ghc-opt -XFlexibleContexts --ghc-opt -XFlexibleInstances --ghc-opt -XFunctionalDependencies --ghc-opt -XGADTs --ghc-opt -XInstanceSigs --ghc-opt -XKindSignatures --ghc-opt -XLambdaCase --ghc-opt -XMultiParamTypeClasses --ghc-opt -XMultiWayIf --ghc-opt -XNamedFieldPuns --ghc-opt -XNoImplicitPrelude --ghc-opt -XOverloadedStrings --ghc-opt -XPackageImports --ghc-opt -XPatternSynonyms --ghc-opt -XPolyKinds --ghc-opt -XQuasiQuotes --ghc-opt -XRankNTypes --ghc-opt -XScopedTypeVariables --ghc-opt -XStandaloneDeriving --ghc-opt -XTemplateHaskell --ghc-opt -XTupleSections --ghc-opt -XTypeApplications --ghc-opt -XTypeFamilies --ghc-opt -XTypeFamilyDependencies --ghc-opt -XTypeOperators --ghc-opt -XUndecidableInstances --ghc-opt -XViewPatterns"] + let g:ormolu_options = ["--ghc-opt -XAllowAmbiguousTypes --ghc-opt -XBangPatterns --ghc-opt -XConstraintKinds --ghc-opt -XDataKinds --ghc-opt -XDefaultSignatures --ghc-opt -XDerivingStrategies --ghc-opt -XDeriveFunctor --ghc-opt -XDeriveGeneric --ghc-opt -XDeriveLift --ghc-opt -XDeriveTraversable --ghc-opt -XDuplicateRecordFields --ghc-opt -XEmptyCase --ghc-opt -XFlexibleContexts --ghc-opt -XFlexibleInstances --ghc-opt -XFunctionalDependencies --ghc-opt -XGADTs --ghc-opt -XInstanceSigs --ghc-opt -XKindSignatures --ghc-opt -XLambdaCase --ghc-opt -XMultiParamTypeClasses --ghc-opt -XMultiWayIf --ghc-opt -XNamedFieldPuns --ghc-opt -XNoImplicitPrelude --ghc-opt -XOverloadedRecordDot --ghc-opt -XOverloadedStrings --ghc-opt -XPackageImports --ghc-opt -XPatternSynonyms --ghc-opt -XPolyKinds --ghc-opt -XQuasiQuotes --ghc-opt -XRankNTypes --ghc-opt -XScopedTypeVariables --ghc-opt -XStandaloneDeriving --ghc-opt -XTemplateHaskell --ghc-opt -XTupleSections --ghc-opt -XTypeApplications --ghc-opt -XTypeFamilies --ghc-opt -XTypeFamilyDependencies --ghc-opt -XTypeOperators --ghc-opt -XUndecidableInstances --ghc-opt -XViewPatterns"] ``` -If you want to be playful, you can look at how `tools/ormolu.sh` -collects the language extensions automatically and see if you can get -it to work here. +**EDIT:** this may no longer be necessary, as the ormolu version we + use consults the cabal files for enabled language extensions. ## VSCode -The project can be loaded into the [Haskell Language Server](https://github.com/haskell/haskell-language-server). +The project can be loaded into the [Haskell Language Server](https://github.com/haskell/haskell-language-server). This gives type checking, code completion, HLint hints, formatting with Ormolu, lookup of definitions and references, etc.. All needed dependencies (like the `haskell-language-server` and `stack` binaries) are provided by `shell.nix`. @@ -115,5 +114,5 @@ Setup steps: - Reload the window as proposed by the `Nix Environment Selector` plugin An alternative way to make these dependencies accessible to VSCode is to start it in the `direnv` environment. -I.e. from a shell that's current working directory is in the project. The drawbacks of this approach are +I.e. from a shell that's current working directory is in the project. The drawbacks of this approach are that it only works locally (not on a remote connection) and one VSCode process needs to be started per project. diff --git a/docs/src/developer/developer/large-conversations.md b/docs/src/developer/developer/large-conversations.md new file mode 100644 index 0000000000..bda4349e74 --- /dev/null +++ b/docs/src/developer/developer/large-conversations.md @@ -0,0 +1,69 @@ +# Refactoring galley to support large conversations + +To be able to suppport large conversations galley needs refactoring. This +section in the developer docs is meant to contain useful references for this +endeavor. + +## Call hierarchy + +See [large-conversations.yaml](https://github.com/wireapp/wire-server/blob/develop/docs/src/developer/developer/large-conversations.yaml) for + call hierarchy of functions that load the full member list into memory. + +## Galley Endpoints + +These are all the endpoints in galley with response types that contain the full member list of conversations. + +In the `ConversationAPI`: + +For type `Conversation`: +- `get-unqualified-conversation` +- `get-unqualified-conversation-legalhold-alias` +- `get-conversation@v2` +- `get-conversation` +- `get-mls-self-conversation` + +For type `ConversationList Conversation` +- `get-conversations` + +For type `ConversationsResponse`: +- `list-conversations@v1` +- `list-conversations@v2` +- `list-conversations` + +For type `ConversationResponse`: +- `create-group-conversation@v2` +- `create-group-conversation@v3` +- `create-one-to-one-conversation@v2` +- `create-one-to-one-conversation` + +For type `CreateGroupConversationResponse`: +- `create-group-conversation` +- `create-self-conversation@v2` +- `create-self-conversation@v2` +- `create-self-conversation` + +For type `Wire.API.Event.Conversation.Event` (`EventData` might contain a `Conversation` object, but it's not clear from API type alone if it contains one) +- `add-members-to-conversation-unqualified` +- `add-members-to-conversation-unqualified2` +- `add-members-to-conversation` +- `join-conversation-by-id-unqualified` +- `join-conversation-by-code-unqualified` +- `create-conversation-code-unqualified` +- `remove-code-unqualified` +- `remove-member-unqualified` +- `update-conversation-name-deprecated` +- `update-conversation-name-unqualified` +- `update-conversation-name` +- `update-conversation-message-timer-unqualified` +- `update-conversation-message-timer` +- `update-conversation-receipt-mode` +- `update-conversation-access-unqualified` +- `update-conversation-access@v2` +- `update-conversation-access` + +In the `MLSAPI`: + +- `mls-commit-bundle` returns a `MLSMessageSendingStatus` which contains list of `Event`, as well a list of unreachable users. +- `mls-message-v1` and `mls-message` returns list of `Event` + +The `MessagingAPI` (Proetus) is not listed here, assuming that Proteus conversations won't support large conversations. diff --git a/docs/src/developer/developer/large-conversations.yaml b/docs/src/developer/developer/large-conversations.yaml new file mode 100644 index 0000000000..4f39885162 --- /dev/null +++ b/docs/src/developer/developer/large-conversations.yaml @@ -0,0 +1,797 @@ +# Context: We would like to refactor galley so the full list of conversation +# members is never loaded into memory. This refactoring a requiste for +# supporting large conversations (10k, 100k users). +# +# This file is the outcome of mapping the call hierarchy of these functions in +# galley: +# +# Galley.Cassandra.Conversation.Members.members +# Galley.Cassandra.Conversation.Members.lookupMLSClients +# Galley.Cassandra.Conversation.Members.lookupRemoteMembers +# +# These functions all fetch the full member list of a conversation. The call +# hierarchy is encoded in the "dependents" field, e.g. +# +# - name: members +# dependents: +# - Galley.Effects.ConversationStore.getConversation +# +# means that `Galley.Effects.ConversationStore.getConversation` calls `members`. +# You can think of the call hierarchy as a directed (acyclic) graph with +# `members`, `lookupMLSClients`, `lookupRemoteMembers` at the top. The leaves of +# the graph are functions which are API handlers or "main" function of +# background threads: they are not called by any other functions. +# +# For each function in the graph I judged if it can be refactored so that it +# doesn't load the full member list into memory. I also judged if that change +# can be "compatible", i.e. all its dependents can continue using the function +# without any interface / behaviour change or whether the refactoring is +# "breaking" the API contract with its dependents / endpoints. If the +# refactoring needs helper functions that don't exist yet I've added them in the +# "new_things" section at the end of this file. Any new helper functions which +# are needed I've listed in the "change_needs" field. +# +# I hope this file is useful in making a refactoring plan, estimating effort and +# keeping track of the refactoring progress. It probably is easiest to start +# refactoring at the leaves, gradually refactoring up the call hierarchy until +# eventually the member-loading functions are not used anymore and can be +# deleted. +# + +functions: + - name: Galley.Cassandra.Conversation.Members.members + change: breaking + comments: | + Delete it! + dependents: + - Galley.Effects.ConversationStore.getConversation + - Galley.Cassandra.Conversation.deleteConversation + + - name: Galley.Cassandra.Conversation.Members.lookupMLSClients + change: breaking + comments: | + Delete it! + dependents: + - Galley.Effects.MemberStore.lookupMLSClients + + - name: Galley.Cassandra.Conversation.Members.lookupRemoteMembers + change: breaking + comments: | + delete it! + dependents: + - Galley.Cassandra.Conversation.deleteConversation + - Galley.Cassandra.Conversation.getConversation + - Galley.Cassandra.Conversation.localConversation + - Galley.Effects.MemberStore.getRemoteMembers + + - name: Galley.Effects.MemberStore.lookupMLSClients + change: breaking + comments: | + delete it! + dependents: + - Galley.API.MLS.Message.postMLSCommitBundleToLocalConv + - Galley.API.MLS.Message.postMLSMessageToLocalConv + - Galley.API.MLS.Removal.removeClient + - Galley.API.MLS.Removal.removeUser + + - name: Galley.API.Clients.rmClientH + change: compatible + comments: | + member list use by removeClientsWithClientMap + to propagate backend remove proposal for client that is leaving conv + change_needs: + - removeClientsWithClientMap' + + - name: Galley.API.Create.createConnectConversation + change: breaking + comments: | + breaking because of PublicConversationViewWithoutMembers + change_needs: + - getConversation' + - createConversation' + - newPushLocal' + - push1' + - notifyCreatedConversation' + - isMemberOfLocalConv + - hasLocalConvMembers + + - name: Galley.API.Create.createGroupConversationGeneric + change: breaking + dependents: + - Galley.API.Create.createGroupConversationUpToV3 + - Galley.API.Create.createGroupConversation + comments: | + wouldnt be able to get (Set (Remote User)) of failedToNotify users + change_needs: + - createConversation' + - getConversation' + + - name: Galley.API.Create.createGroupConversationUpToV3 + change: breaking + comments: | + would respond with ResponseForExistedCreated PublicConversation' + + - name: Galley.API.Create.createGroupConversation + change: breaking + comments: | + would respond with ResponseForExistedCreated CreateGroupConversationResponse' + + - name: Galley.API.Federation.onClientRemoved + change: compatible + comments: | + See plan for Galley.API.Clients.rmClientH + + - name: Galley.API.Federation.onUserDeleted + change: compatible + change_needs: + - isRemoteMember' + - notifyConversationAction' + + - name: Galley.API.MLS.Util.getLocalConvForUser + change: breaking + comments: | + returns Conversation' + dependents: + - Galley.API.MLS.GroupInfo.getGroupInfoFromLocalConv + - Galley.API.MLS.Message.postMLSCommitBundleToLocalConv + - Galley.API.MLS.Message.postMLSMessageToLocalConv + - Galley.API.MLS.Message.processExternalCommit + + - name: Galley.API.MLS.GroupInfo.getGroupInfoFromLocalConv + change: compatible + change_needs: + - isMember' + + - name: Galley.API.MLS.Message.postMLSCommitBundleToLocalConv + chanage: breaking + comments: | + Maybe compatible, depends on unreachables, do we need them? + change_needs: + - isMember' + - getConversation' + - propagateMessage' + + - name: Galley.API.MLS.Message.postMLSMessageToLocalConv + chanage: breaking + comments: | + Maybe compatible, depends on unreachables, do we need them? + change_needs: + - isMember' + - getConversation' + + - name: Galley.API.MLS.Message.processExternalCommit + change: compatible + comments: | + Argument would change from Conv to ConvId + change_needs: + - removeClientsWithClientMap' + + - name: Galley.API.LegalHold.handleGroupConvPolicyConflicts + change: compatible + comments: | + This functions need to be kept as blocking. + change_needs: + - getConvLocalMembersPage + + - name: Galley.API.Message.postQualifiedOtrMessage + change: ??? + comments: | + Proteus. Not sure if we want to do this. + + - name: Galley.API.One2One.iUpsertOne2OneConversation + change: compatible + change_needs: + - getConversation' + - hasLocalConvMembers + - hasRemoteConvMembers + + - name: Galley.API.Query.getConversationByReusableCode + change: compatible + change_needs: + - ensureConversationAccess' + + - name: Galley.API.Query.getConversationGuestLinksStatus + change: compatible + change_needs: + - getConversation' + - ensureConvAdmin' + + - name: Galley.API.Query.getLocalSelf + change: compatible + comments: | + Includes weird cleanup code that deletes the conversation if it is not "alive" + change_needs: + - getLocalMember' + + - name: Galley.API.Query.getMLSSelfConversation + change: breaking + dependents: + - Galley.API.Query.getMLSSelfConversationWithError + comments: | + Maybe optional? + returns PublicConversation' + change_needs: + - getConversation' + + - name: Galley.API.Query.getMLSSelfConversationWithError + change: ??? + comments: | + Maybe optional? + + - name: Galley.API.Teams.uncheckedDeleteTeamMember + change: compatible + dependents: + - newPushLocal' + - push1' + + - name: Galley.API.Create.createLegacyOne2OneConversationUnchecked + change: compatible + change_needs: + - getConversation' + + - name: Galley.API.Create.createOne2OneConversationLocally + change: compatible + change_needs: + - getConversation' + + - name: Galley.API.Create.createProteusSelfConversation + change: ??? + change_needs: + - getConversation' + + - name: Galley.API.Update.acceptConv + change: compatible + change_needs: + - getConversation' + + - name: Galley.API.Update.addBot + change: compatible + change_needs: + - isMember' + - getLocalMember' + - ensureConversationAccess' + - ensureMemberLimit' + + - name: Galley.API.Update.addCode + change: compatible + change_needs: + - getConversation' + - ensureConvAdmin' + - pushConversationEvent' + + - name: Galley.API.Update.blockConv + change: compatible + change_needs: + - getConversation' + - isMember' + + - name: Galley.API.Update.checkReusableCode + change: compatible + change_needs: + - getConversation' + + - name: Galley.API.Update.getCode + change: compatible + change_needs: + - getConversation' + - isMember' + + - name: Galley.API.Update.joinConversationById + change: breaking + change_needs: + - getConversation' + + - name: Galley.API.Update.rmBot + change: compatible + change_needs: + - getConversation' + - isMember' + - getLocalMember' + - ensureActionAllowed' + - push1' + + - name: Galley.API.Update.rmCode + change: compatible + change_needs: + - getConversation' + - ensureConvAdmin' + - ensureConversationAccess' + - pushConversationEvent' + + - name: Galley.API.Update.unblockConv + change: breaking + change_needs: + - getConversation' + + - name: Galley.API.Update.joinConversation + change: breaking + comments: | + This is a big one + dependents: + - Galley.API.Update.joinConversationById + - Galley.API.Update.joinConversationByReusableCode + change_needs: + - notifyConversationAction' + + - name: Galley.API.Update.joinConversationByReusableCode + change: breaking + change_needs: + - getConversation' + + - name: Galley.API.Util.getConversationWithError + change: breaking + dependents: + - Galley.API.Util.getConversationAndMemberWithError + - Galley.API.Util.updateLocalConversation + change_needs: + - getConversation' + + - name: Galley.API.Util.getConversationAndMemberWithError + change: breaking + comments: | + return (Conversation', mem) + dependents: + - Galley.API.Query.getBotConversation + - Galley.API.Util.getConversationAndCheckMembership + - Galley.API.Federation.leaveConversation + - Galley.API.Update.memberTyping + - Galley.API.Federation.updateTypingIndicator + + - name: Galley.API.Util.getConversationAndCheckMembership + change: breaking + dependents: + - Galley.API.Query.getUnqualifiedConversation + - Galley.API.Query.getConversationRoles + + - name: Galley.API.Query.getUnqualifiedConversation + change: breaking + dependents: + - Galley.API.Query.getConversation + + - name: Galley.API.Query.getConversation + change: breaking + + - name: Galley.API.Query.getConversationRoles + change: compatible + change_needs: + - getConversation' + - isMember' + + - name: Galley.API.Query.getBotConversation + change: compatible + change_needs: + - isMember' + + - name: Galley.API.Federation.leaveConversation + change: compatible + change_needs: + - getConversation' + - isMember' + - notifyConversationAction' + + - name: Galley.API.Update.memberTyping + change: compatible + change_needs: + - push1' + - isRemoteMember' + + - name: Galley.API.Federation.updateTypingIndicator + change: compatible + change_needs: + - push1' + + - name: Galley.Cassandra.Conversation.localConversation + change: breaking + dependents: + - Galley.Effects.ConversationStore.getConversations + + - name: Galley.Effects.ConversationStore.getConversations + change: breaking + dependents: + - Galley.API.Federation.getConversations + - Galley.API.Internal.rmUser + - Galley.API.Query.getConversationsInternal + - Galley.API.Query.listConversations + + - name: Galley.API.Federation.getConversations + change: breaking + dependents: + - fed enpoint "get-conversations" + + - name: Galley.API.Internal.rmUser + change: compatible + dependents: + change_needs: + - getConversation' + + - name: Galley.API.Query.getConversationsInternal + change: breaking + dependents: + - Galley.API.Query.getConversations + - Galley.API.Query.iterateConversations + + - name: Galley.API.Query.getConversations + change: breaking + + - name: Galley.API.Query.iterateConversations + change: breaking + dependents: + - Galley.API.LegalHold.handleGroupConvPolicyConflicts + + - name: Galley.API.Query.listConversations + change: breaking + + - name: Galley.API.Action.performAction + change: breaking + comments: | + centeral one + dependents: + - Galley.API.Action.updateLocalConversationUnchecked + change_needs: + - ensureMemberLimit' + - ensureConversationAccess' + - performConversationJoin' + - getLocalMember' + - getRemoteMember' + + - name: Galley.API.Action.updateLocalConversationUnchecked + change: breaking + dependents: + - Galley.API.Action.updateLocalConversation + - Galley.API.MLS.Message.executeProposalAction + change_needs: + - Galley.API.Action.performAction' + - notifyConversationAction' + + - name: Galley.API.Action.updateLocalConversation + change: breaking + dependents: + - Galley.API.Action.updateLocalConversationUnchecked' + + - name: Galley.API.MLS.Message.executeProposalAction + change: breaking + comments: | + because ConversationUpdate contains cuAlreadyPresentUsers (federation api) + change_needs: + - lookupMLSClients' + + - name: Galley.API.Federation.updateConversation + change: breaking + comments: | + no more cuAlreadyPresentUsers + + - name: Galley.API.Internal.deleteLoop + change: compatible + + - name: Galley.API.Teams.deleteTeamConversation + change: compatible + change_needs: + - Galley.API.Action.updateLocalConversation + + - name: Galley.API.Teams.uncheckedDeleteTeam + change: compatible + dependents: + - Galley.API.Internal.deleteLoop + comments: | + This loops over all team members, not conversation members. + So it's still problematic. Should do this in pages to avoid OOM. + + - name: Galley.API.Update.deleteLocalConversation + change: compatible + comments: | + The event is probably a conversation delete event not containting any members + dependents: + - Galley.API.Teams.deleteTeamConversation + + - name: Galley.Cassandra.Conversation.getConversation + change: breaking + dependents: + - Galley.Effects.ConversationStore.getConversation + + - name: Galley.Cassandra.Team.deleteTeam + change: compatible + dependents: + - Galley.Effects.TeamStore.deleteTeam + + - name: Galley.Effects.TeamStore.deleteTeam + change: compatible + dependents: + - Galley.API.Teams.uncheckedDeleteTeam + + - name: Galley.Effects.TeamStore.deleteTeamConversation + change: compatible + dependents: + - Galley.API.Action.updateLocalConversationUnchecked' + + - name: Galley.Cassandra.Conversation.deleteConversation + change: compatible + dependents: + - Galley.Cassandra.Team.removeTeamConv + - Galley.Effects.ConversationStore.deleteConversation + + - name: Galley.Cassandra.Team.removeTeamConv + change: compatible + dependents: + - Galley.Cassandra.Team.deleteTeam + - Galley.Effects.TeamStore.deleteTeamConversation + + - name: Galley.Effects.ConversationStore.deleteConversation + change: compatible + dependents: + - Galley.API.Action.performAction + - Galley.API.Query.getConversationsInternal + - Galley.API.Query.listConversations + - Galley.API.Query.getLocalSelf + + - name: Galley.Effects.ConversationStore.getConversation + change: breaking + comments: | + Remove the member list + dependents: + - Galley.API.Clients.rmClientH + - Galley.API.Create.createGroupConversationGeneric + - Galley.API.Create.createProteusSelfConversation + - Galley.API.Create.createLegacyOne2OneConversationUnchecked + - Galley.API.Create.createOne2OneConversationLocally + - Galley.API.Create.createConnectConversation + - Galley.API.Federation.onClientRemoved + - Galley.API.Federation.onUserDeleted + - Galley.API.MLS.Util.getLocalConvForUser + - Galley.API.Message.postQualifiedOtrMessage + - Galley.API.One2One.iUpsertOne2OneConversation + - Galley.API.Query.getConversationByReusableCode + - Galley.API.Query.getConversationGuestLinksStatus + - Galley.API.Query.getMLSSelfConversation + - Galley.API.Teams.uncheckedDeleteTeamMember + - Galley.API.Update.acceptConv + - Galley.API.Update.blockConv + - Galley.API.Update.unblockConv + - Galley.API.Update.addCode + - Galley.API.Update.rmCode + - Galley.API.Update.getCode + - Galley.API.Update.checkReusableCode + - Galley.API.Update.joinConversationByReusableCode + - Galley.API.Update.joinConversationById + - Galley.API.Update.addBot + - Galley.API.Update.rmBot + - Galley.API.Util.getConversationWithError + + - name: Galley.Cassandra.Conversation.Members.addMembers + change: breaking + comments: | + we should restrict the number of users you can add in the dependents + dependents: + - Galley.Cassandra.Conversation.createMLSSelfConversation + - Galley.Cassandra.Conversation.createConversation + - Galley.Effects.MemberStore.createMember + + - name: Galley.Cassandra.Conversation.createMLSSelfConversation + change: breaking + comments: | + Remove member list + + - name: Galley.Cassandra.Conversation.createConversation + change: breaking + comments: | + Remove member list + + - name: Galley.Effects.MemberStore.createMember + change: breaking + comments: | + Remove member list + dependents: + - Galley.Effects.ConversationStore.createConversation + + - name: Galley.Effects.ConversationStore.createConversation + change: breaking + comments: | + Remove member list + dependents: + - Galley.API.Create.createGroupConversationGeneric + - Galley.API.Create.createProteusSelfConversation + - Galley.API.Create.createLegacyOne2OneConversationUnchecked + - Galley.API.Create.createOne2OneConversationLocally + - Galley.API.Create.createConnectConversation + - Galley.API.One2One.iUpsertOne2OneConversation + + - name: Galley.API.Create.createGroupConversationGeneric + change: breaking + comments: | + Remove member list + dependents: + - Galley.API.Create.createGroupConversationUpToV3 + - Galley.API.Create.createGroupConversation + + - name: Galley.API.Create.createGroupConversationUpToV3 + change: breaking + comments: | + Remove member list + + - name: Galley.API.Create.createGroupConversation + change: breaking + comments: | + Remove member list + + - name: Galley.API.Create.createProteusSelfConversation + change: breaking + comments: | + Remove member list + + - name: Galley.API.Create.createLegacyOne2OneConversationUnchecked + change: breaking + comments: | + Remove member list + + - name: Galley.API.Create.createOne2OneConversationLocally + change: breaking + comments: | + Remove member list + + - name: Galley.API.Create.createConnectConversation + change: breaking + comments: | + Remove member list + + - name: Galley.API.One2One.iUpsertOne2OneConversation + change: breaking + comments: | + Remove member list + + - name: Galley.Effects.MemberStore.getLocalMembers + change: breaking + comments: | + Remove member list + dependents: + - Galley.API.Teams.uncheckedDeleteTeam + - Galley.API.Update.updateSelfMember + + - name: Galley.API.Update.updateSelfMember + change: compatible + change_needs: + - isMember' + - isRemoteMember' + + - name: Galley.Effects.MemberStore.getRemoteMembers + change: breaking + comments: | + this is DEAD CODE + + - name: Galley.API.MLS.Removal.removeClient + change: compatible + change_needs: + - removeClientsWithClientMap' + + - name: Galley.API.MLS.Removal.removeUser + change: compatible + change_needs: + - removeClientsWithClientMap' + + +new_things: + + - name: PublicConversation' + description: + public API type that has all the fields of Converation but + without the members list + + - name: Conversation' + description: + internal conversation type that hold all fields except + the member lists + + - name: Push' + description: + Similar to Push but instead of pushRecipients fields the conversation id + exceptions + + - name: getConversation' + description: | + :: ConvId + -> m (Maybe ConversationWithoutMembers) + in Galley.Effects.ConversationStore + + - name: createConversation' + description: | + :: Local ConvId + -> NewConversation + -> ConversationStore m ConversationWithoutMembers + + - name: notifyCreatedConversation' + description: | + :: Local UserId + -> Maybe ConnId + -> ConvId -> + Sem r () + + in Galley.API.Create + + breaking api change. notifyCreatedConversation returned set of remote UserId + that could not be contacted + + - name: newPushLocal' + description: | + :: ListType + -> UserId + -> PushEvent + -> Maybe Push' + in Galley.Intra.Push.Internal + + not clear how to do define this + + - name: push1' + desciption: | + two stratgies (maybe): + strategy 1: consumer loops over pages and calls for each page + this requires that gundeck has fast response times for each call + strategy 2: consumer only gives recipients in form of a convid (+ excpetions) + async gundeck fetches recicpients + + - name: pushConversationEvent' + + - name: removeClientsWithClientMap' + description: | + Local ConvId -> + [KeyPackageRef] -> + Qualified UserId -> + Sem r () + + - name: isMemberOfLocalConv + description: | + :: UserId -> ConvId -> m Bool + + - name: hasLocalConvMembers + description: | + :: ConvId -> m Bool + check if conversation has any members + + - name: hasRemoteConvMembers + description: | + :: ConvId -> m Bool + + - name: isRemoteMember' + description: | + :: Remote UserId -> ConvId -> m Bool + + - name: isMember' + description: | + :: Local UserId -> ConvId -> m Bool + + - name: notifyConversationAction' + description: | + in Galley.API.Action + this one is complicated + + - name: propagateMessage' + description: | + in Galley.API.MLS.Propagate + + - name: getConvLocalMembersPage + + - name: ensureConversationAccess' + + - name: ensureConvAdmin' + + - name: ensureActionAllowed' + + - name: getLocalMember' + description: + in MemberStore + + - name: getRemoteMember' + description: + in MemberStore + + - name: ensureMemberLimit' + decription: | + maybe we don't need this function anymore when large groups + are supported + + - name: performConversationJoin' + description: | + in Galley.API.Action + big and complicated + + - name: lookupMLSClients' + description: | + should replace the lookup in ClientMap + + - name: Galley.API.Action.updateLocalConversationUnchecked' diff --git a/docs/src/developer/reference/config-options.md b/docs/src/developer/reference/config-options.md index 687aba4776..e1599b2cea 100644 --- a/docs/src/developer/reference/config-options.md +++ b/docs/src/developer/reference/config-options.md @@ -633,36 +633,89 @@ If there is no configuration for a domain, it's defaulted to `no_search`. This options determines whether development versions should be enabled. If set to `False`, all development versions are removed from the `supported` field of the `/api-version` endpoint. Note that they are still listed in the `development` field, and continue to work normally. +### OAuth + +For more information on OAuth please refer to . + +En-/Disable OAuth as follows (if not set the default is disabled): + +```yaml +# [brig.yaml] +optSettings: + # ... + setOAuthEnabled: [true|false] +``` + +#### JWK + +The JSON Web Keys in `test/resources/oauth/` are used to sign and verify OAuth access tokens in the local integration tests. +The path to the JWK can be configured in `brig.integration.yaml` as follows: + +```yaml +# [brig.yaml] +optSettings: + # ... + setOAuthJwkKeyPair: test/resources/oauth/ed25519.jwk +``` + +A JWK can be generated with `didkit` e.g. Run `cargo install didkit-cli` to install and `didkit generate-ed25519-key` to generate a JWK. + +#### Expiration time + +Optionally, configure the OAuth authorization code, access token, and refresh token expiration time in seconds with the following settings: + +```yaml +# [brig.yaml] +optSettings: + # ... + setOAuthAuthCodeExpirationTimeSecs: 300 # 5 minutes + setOAuthAccessTokenExpirationTimeSecs: 300 # 5 minutes + setOAuthRefreshTokenExpirationTimeSecs: 14515200 # 24 weeks +``` + +For more information on what these settings mean in particular, please refer to . + +#### Max number of active refresh tokens + +The maximum number of active OAuth refresh tokens a user is allowed to have. Built-in default: + +```yaml +# [brig.yaml] +optSettings: + # ... + setOAuthMaxActiveRefreshTokens: 10 +``` + #### Disabling API versions It is possible to disable one ore more API versions. When an API version is disabled it won't be advertised on the `GET /api-version` endpoint, neither in the `supported`, nor in the `development` section. Requests made to any endpoint of a disabled API version will result in the same error response as a request made to an API version that does not exist. -Each of the services brig, cannon, cargohold, galley, gundeck, proxy, spar should to be configured with the same set of disable API versions in each service's values.yaml config files. +Each of the services brig, cannon, cargohold, galley, gundeck, proxy, spar should to be configured with the same set of disable API versions in each service's values.yaml config files. For example to disable API version v3, you need to configure: ``` # brig's values.yaml -config.optSettings.setDisabledAPIVersions: [ 3 ] +config.optSettings.setDisabledAPIVersions: [ v3 ] # cannon's values.yaml -config.disabledAPIVersions: [ 3 ] +config.disabledAPIVersions: [ v3 ] # cargohold's values.yaml -config.settings.disabledAPIVersions: [ 3 ] +config.settings.disabledAPIVersions: [ v3 ] # galley's values.yaml -config.settings.disabledAPIVersions: [ 3 ] +config.settings.disabledAPIVersions: [ v3 ] # gundecks' values.yaml -config.disabledAPIVersions: [ 3 ] +config.disabledAPIVersions: [ v3 ] # proxy's values.yaml -config.disabledAPIVersions: [ 3 ] +config.disabledAPIVersions: [ v3 ] # spar's values.yaml -config.disabledAPIVersions: [ 3 ] +config.disabledAPIVersions: [ v3 ] ``` The default setting is that no API version is disabled. diff --git a/docs/src/developer/reference/make-docker-and-qemu.md b/docs/src/developer/reference/make-docker-and-qemu.md index a7c10f06cf..2445814068 100644 --- a/docs/src/developer/reference/make-docker-and-qemu.md +++ b/docs/src/developer/reference/make-docker-and-qemu.md @@ -177,7 +177,7 @@ QEMU's userspace emulation allows you to download a program written for a differ #### About BinFmt Support: BinFmt support is a set of extensions to the linux kernel that allow you to specify an interpreter for binaries of a certain pattern (magic number, ELF header, .EXE header, etc), so that when you attempt to execute them, the kernel will launch the interpreter of your choice, passing it the path to the binary you tried to execute. On my debian machine, it is used for python by default. Many people use this support for executing windows executables on linux using the WINE package, which contains a re-implementation of the Windows system libraries. -The Linux kernel's BinFmt module can be set to look for an interpreter at run time, or to load the interpreter into memory when it is configured. The packages we're going to set up and exercise in this stage use the "load when you configure" approach. This is useful, so than when you're operating in a docker container, you don't have to place the system emulator in the docker container itsself for the kernel to find it. +The Linux kernel's BinFmt module can be set to look for an interpreter at run time, or to load the interpreter into memory when it is configured. The packages we're going to set up and exercise in this stage use the "load when you configure" approach. This is useful, so than when you're operating in a docker container, you don't have to place the system emulator in the docker container itself for the kernel to find it. #### Installing QEMU with BinFmt support: @@ -412,7 +412,7 @@ To remove all of the git repositories containing the Dockerfiles we download to ### Reading through the Makefile -OK, now that we have a handle on what it does, and how to use it, let's get into the Makefile itsself. +OK, now that we have a handle on what it does, and how to use it, let's get into the Makefile itself. A Makefile is a series of rules for performing tasks, variables used when creating those tasks, and some minimal functions and conditional structures. Rules are implemented as groups of bash commands, where each line is handled by a new bash interpreter. Personally, I think it 'feels functiony', only without a type system and with lots of side effects. Like if bash tried to be functional. @@ -1065,7 +1065,7 @@ The cassandra/Dockerfile rule is almost identical to this last rule, only substi ## Pitfalls I fell into writing this. -The first large mistake I made when writing this, is that the root of the makefile's dependency tree contained both images that had dependencies, and the dependent images themselves. This had me writing methods to keep the image build process from stepping on itsself. what was happening is that, in the case of the airdock-* and localstack images, when trying to build all of the images at once, make would race all the way down to the git clone steps, and run the git clone multiple times at the same time, where it just needs to be run once. +The first large mistake I made when writing this, is that the root of the makefile's dependency tree contained both images that had dependencies, and the dependent images themselves. This had me writing methods to keep the image build process from stepping on itself. what was happening is that, in the case of the airdock-* and localstack images, when trying to build all of the images at once, make would race all the way down to the git clone steps, and run the git clone multiple times at the same time, where it just needs to be run once. The second was that I didn't really understand that manifest files refer to dockerhub only, not to the local machine. This was giving me similar race conditions, where an image build for architecture A would complete, and try to build the manifest when architecture B was still building. diff --git a/docs/src/developer/reference/oauth.md b/docs/src/developer/reference/oauth.md new file mode 100644 index 0000000000..dbb5330d6b --- /dev/null +++ b/docs/src/developer/reference/oauth.md @@ -0,0 +1,453 @@ +# OAuth + +```{contents} +:depth: 3 +``` + +## Introduction and overview + +OAuth 2.0 is used to authorize 3rd party applications to access `wire-server` resources on behalf of a Wire user. + +Currently, only 3rd party apps that have been implemented and approved by Wire are supported. OAuth is not open for public use. + +Supported OAuth apps: + +- Outlook Calendar Extension + +`wire-server` implements a subset of the [The OAuth 2.0 Authorization Framework (RFC 6749)](https://www.rfc-editor.org/rfc/rfc6749). + +Please refer to the documentation below for a reference of the subset that is implemented without the noise of having to go through the complete RFC. + +### Roles + +#### The user (resource owner) + +The user is the resource owner who gives permission to the OAuth client to access parts of their resources. + +#### 3rd party application (OAuth client) + +A 3rd party app is attempting to get access a resource on behalf of the user. It needs to get permission from the user in order to do so. The terminology is a bit fuzzy here: we use the terms app, application, client synonymous. To disambiguate, we qualify with "oauth" (*oauth* client, ...). + +#### Resource server + +The resource server is the API server the 3rd party app attempts to access on behalf of the user. In our case the resource server is `wire-server`. + +#### Authorization server + +The authorization server does the authentication of the user and establishes whether the user approves or denies the client's access request. In this case the authorization server is the same server as the resource server which is `wire-server`. + +### Supported OAuth flow + +`wire-server` currently only supports the [Authorization Code Flow with Proof Key for Code Exchange (PKCE)](https://www.rfc-editor.org/rfc/rfc7636) which is optimized for public clients such as Outlook Calendar Extension. + +```{image} oauth.svg +``` + +## OAuth client developer reference + +### Registering an OAuth client + +A new OAuth client can be register *only* via the internal API of `brig` by providing an application name and a redirect URL: + +```shell + curl -s -X POST server-internal.example.com/i/oauth/clients \ + -H "Content-Type: application/json" \ + -d '{ + "application_name":"Outlook Calendar Extension", + "redirect_url":"https://client.example.com" + }' +``` + +Parameters: + +| Parameter | Description | +| ------------------ | ---------------------------------------------------------------------------------------------------- | +| `redirect_url` | The URL to which Wire app will redirect the browser after authorization has been granted by the user | +| `application_name` | The name of the application that will be shown on the consent page | + +Client credentials will be generated and returned by wire-server: + +```json +{ + "clientId": "b9e65569-aa61-462d-915d-94c8d6ef17a7", + "clientSecret": "3f6fbd62835859b2bac411b2a2a2a54699ec56504ee32099748de3a762d41a2d" +} +``` + +These credentials have to be stored in a safe place and cannot be recovered if they are lost. + +### Authorization request + +When the user wants to use the 3rd party app for the first time, they need to authorize it to access Wire resources on their behalf. + +They first need to click on the "Login" (or similar) button (1. in OAuth 2.0 authorization code flow diagram above) which will redirect them to a Wire login page to authenticate (2.-3. in diagram above). Once authenticated, they are redirected to the consent page. + +If the user is already logged in the authentication will be skipped and they are directly shown the consent page. + +On the consent page, the user is asked to authorize the client's access request. They can either grant or deny the request and the corresponding scope, a list of permissions to give to the 3rd party app, (4. in diagram above). + +The client needs to create a unique `code_verifier` as described in [RFC 7636 section 4.1](https://www.rfc-editor.org/rfc/rfc7636#section-4.1) and send a `code_challenge`, which is the unpadded base64url-encoded SHA256 hash of the code verifier as described in [RFC 7636 section 4.2](https://www.rfc-editor.org/rfc/rfc7636#section-4.2). The `code_challenge` must be included in the request. The `S256` code challenge method is mandatory. The `code_verifier` must not be included in the request. + +Example request: + +``` +GET /authorize? + scope=read%3Aself%20write%3Aconversation& + response_type=code& + client_id=b9e65569-aa61-462d-915d-94c8d6ef17a7& + redirect_uri=https%3A%2F%2Fclient.example.com& + state=foobar& + code_challenge=qVrqDTN8ivyWEEw6wyfUc3bwhCA2RE4V2fbiC4mC7ofqAF4t& + code_challenge_method=S256 HTTP/1.1 +``` + +Url encoded query parameters: + +| Parameter | Description | +| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `scope` | Required. The scope of the access request. | +| `response_type` | Required. Value MUST be set to `code`. | +| `client_id` | Required. The client identifier. | +| `redirect_url` | Required. MUST match the URL that was provided during client registration | +| `state` | Required. An opaque value used by the client to maintain state between the request and callback.
The authorization server includes this value when redirecting the user-agent back to the client.
The parameter is used for preventing cross-site request forgery. | +| `code_challenge` | Required. Generated by the client from the `code_verifier` | +| `code_challenge_method` | Required. It MUST be set to `S256` | + +Once the user consents, the browser will be redirected back to the 3rd party app, using the redirect URI provided during client registration, with an authorization code and the state value as query parameters (5. in diagram above). The authorization code can now be used by the 3rd party app to retrieve an access token and a refresh token and is good for one use. + +Example response: + +```http +HTTP/1.1 302 Found +Transfer-Encoding: chunked +Date: Thu, 23 Feb 2023 15:50:21 GMT +Server: Warp/3.3.23 +Location: https://client.example.com?code=1395a1a44b72e0b81ec8fe6c791d2d3f22bc1c4df96857a88c3e2914bb687b7b&state=foobar +Vary: Accept-Encoding +``` + +### Retrieve access and refresh token + +The 3rd party app sends the authorization code together with the client credentials and the parameters shown below using the `application/x-www-form-urlencoded` format with character encoding of UTF-8 to the authorization server (6. in diagram above) to retrieve an access token and a refresh token (7.-8. in diagram above): + +```shell +curl -s -X POST server.example.com/oauth/token \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d 'code=1395a1a44b72e0b81ec8fe6c791d2d3f22bc1c4df96857a88c3e2914bb687b7b&client_id=b9e65569-aa61-462d-915d-94c8d6ef17a7&grant_type=authorization_code&redirect_uri=https%3A%2F%2Fclient.example.com&code_verifier=2dae11ce5e162e2c01180ae4f8b55103b8297408b8aab12f99f63df3c2415234' +``` + +Parameters: + +| Parameter | Description | +| --------------- | --------------------------------------------------------------------------------------- | +| `code` | Required. The authorization code received from the authorization server. | +| `client_id` | Required. The client identifier. | +| `grant_type` | Required. Value MUST be set to `authorization_code`. | +| `redirect_uri` | Required. The value MUST be identical to the one provided in the authorization request | +| `code_verifier` | Required. The code verifier as described above. | + +Example response: + +```JSON +{ + "access_token": "eyJhbGciOiJFZERTQSJ9.eyJhdWQiOiJleGFtcGxlLmNvbSIsImV4cCI6MS42NzcyMzYyNDkxMTIzMTk3MWU5LCJpYXQiOjEuNjc3MjM2MjQ2MTEyMzE5NzFlOSwiaXNzIjoiZXhhbXBsZS5jb20iLCJzY29wZSI6InJlYWQ6c2VsZiIsInN1YiI6ImJhOTIxY2ZmLWU1ZWEtNDMxNS1iZTNkLWZiNjA3NTU0M2Y3MCJ9.5ksjS7msi9NSNat-qh7-Y5O-u9TcuYeLWTAsiAyes_oLwfjD_jYtGevUAuiVV6RXgPBO00VEMv-ZS86e7sd5Dg", + "expires_in": 300, + "refresh_token": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiI4NzI1ZTRkNC01Njc5LTQwZGEtOTI3My03YTBkMmIwYjUwMGYifQ.59IICzGoli5nfwJ1ZwRH_b3T-lRgBrralE1EZZRtadI2eKrta0kaLIZpuMWPC2Icj6-LSEBsyYLXpxOm3cNaDw", + "token_type": "Bearer" +} +``` + +The expiration time in the response (`expires_in`) refers to the expiration time of the access token. + +### Accessing a resource + +The access token, presented as `Bearer ` in the `Authorization` header, can now be used by the 3rd party app to access resources on behalf of the user (9.-11. in diagram above). + +### Refresh access token + +Access tokens are short lived and need to be refreshed regularly. To do so, the client makes a refresh request to the token endpoint by adding the parameters shown below using the `application/x-www-form-urlencoded` format with a character encoding of UTF-8 in the HTTP request entity-body. + +Example request: + +```shell +curl -s -X POST server.example.com/oauth/token \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d 'refresh_token=eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiI4NzI1ZTRkNC01Njc5LTQwZGEtOTI3My03YTBkMmIwYjUwMGYifQ.59IICzGoli5nfwJ1ZwRH_b3T-lRgBrralE1EZZRtadI2eKrta0kaLIZpuMWPC2Icj6-LSEBsyYLXpxOm3cNaDw&client_id=b9e65569-aa61-462d-915d-94c8d6ef17a7&grant_type=refresh_token&client_secret=3f6fbd62835859b2bac411b2a2a2a54699ec56504ee32099748de3a762d41a2d' +``` + +Parameters: + +| Parameter | Description | +| --------------- | ------------------------------------------------- | +| `refresh_token` | Required. The refresh token issued to the client. | +| `client_id` | Required. The client identifier. | +| `grant_type` | Required. Value MUST be set to `refresh_token`. | +| `client_secret` | Required. The client's secret. | + +Example response: + +```json +{ + "access_token": "eyJhbGciOiJFZERTQSJ9.eyJhdWQiOiJleGFtcGxlLmNvbSIsImV4cCI6MS42NzcyMzc3MjkyMzAyMDU3NTJlOSwiaWF0IjoxLjY3NzIzNzcyNjIzMDIwNTc1MmU5LCJpc3MiOiJleGFtcGxlLmNvbSIsInNjb3BlIjoicmVhZDpzZWxmIiwic3ViIjoiYmE5MjFjZmYtZTVlYS00MzE1LWJlM2QtZmI2MDc1NTQzZjcwIn0.__fa8l5E2L-DNaxG5tJFLF8zf5y8lpCJI0F5MOFPdewaXbOLleTJ_eAueCjG7dB7yXNVY9Uiry_W5Mw7O19UCw", + "expires_in": 300, + "refresh_token": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiJjMzQwOWU4OC0wYmE5LTQ3YjgtOWQ0My03YTVmMmM3YjhlNGYifQ.Padku_6pOUIlE469cW5TELtiNtO1mZnBrRpj8CRMgNVBTck7qxInz6LyXVYfSV7soAa44202yXzI0o4IcfZiBA", + "token_type": "Bearer" +} +``` + +### Revoke a refresh token + +A refresh token can be revoked as follows: + +```shell +curl -i -s -X POST localhost:8080/oauth/revoke \ + -H "Content-Type: application/json" \ + -d '{ + "client_id": "31a605c5-b033-405a-ab05-f8307cf22d3f", + "client_secret": "d2580e9b759eca52fdf3a21532ea5aae0e08706529a0a6e6fa4ab2a3d7b39da4", + "refresh_token": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiJjMzQwOWU4OC0wYmE5LTQ3YjgtOWQ0My03YTVmMmM3YjhlNGYifQ.Padku_6pOUIlE469cW5TELtiNtO1mZnBrRpj8CRMgNVBTck7qxInz6LyXVYfSV7soAa44202yXzI0o4IcfZiBA" + }' +``` + +Parameters: + +| Parameter | Description | +| --------------- | ------------------------------------------------- | +| `client_id` | Required. The client identifier. | +| `refresh_token` | Required. The refresh token issued to the client. | +| `client_secret` | Required. The client's secret. | + +Example response: + +``` +HTTP/1.1 200 OK +(empty-response-body) +``` + +## Wire client developer reference (ZAuth authorized API) + +### Retrieve OAuth client info + +Authenticated endpoint to retrieve client information, necessary to display authorization prompt/user consent page. + +See [swagger docs](https://staging-nginz-https.zinfra.io/api/swagger-ui/#/default/get_oauth_clients__OAuthClientId_). + +### Retrieve a list of 3rd party apps with account access + +Authenticated endpoint to retrieve a list of all applications that have account access via OAuth. + +See [swagger docs](https://staging-nginz-https.zinfra.io/api/swagger-ui/#/default/get_oauth_applications). + +### Revoke account access + +3rd party app access can be revoked, by invalidating all active refresh tokens, as follows: + +See [swagger docs](https://staging-nginz-https.zinfra.io/api/swagger-ui/#/default/delete_oauth_applications__OAuthClientId_). + +## Site admin reference (Configuration) + +### Enable/disable OAuth + +If not configured, OAuth is disabled per default. OAuth can be enabled in the wire-server Helm as follows: + +```yaml +brig: + # ... + config: + # ... + optSettings: + # ... + setOAuthEnabled: true +``` + +#### Setting up public and private keys + +To use the OAuth functionality, you will need to set up a public and private JSON web key pair (JWK) in the wire-server Helm chart. This key pair will be used to sign and verify OAuth access tokens. + +Key can be generated e.g. with [jwx](https://github.com/lestrrat-go/jwx/tree/develop/v2/cmd/jwx) like this: + +```shell +jwx jwk generate --type OKP --curve Ed25519 | jq -c +``` + +`jwx` is available via nix: `nix-shell -p jwx`. + +To configure the JWK, go to the wire-server Helm chart and provide the JWK information, private and public key set for `brig` and the public key for `nginz`, as in the examples below: + +```yaml +# values.yaml or secrets.yaml +brig: + secrets: + oauthJwkKeyPair: | + { + "kty": "OKP", + "crv": "Ed25519", + "x": "...", + "d": "..." + } +``` + +```yaml +# values.yaml or secrets.yaml +nginz: + secrets: + oAuth: + publicKeys: | + { + "kty": "OKP", + "crv": "Ed25519", + "x": "..." + } +``` + +Note that the JWK is a sensitive configuration value, so it is recommended to use Helm's support for managing secrets instead of including it in a plaintext `values.yaml` file. + +### OAuth authorization code, access token, and refresh token expiration + +The the OAuth authorization code expiration and access and refresh token expiration can be overridden in the Helm file as follows: + +```yaml +brig: + # ... + config: + # ... + optSettings: + # ... + setOAuthAuthCodeExpirationTimeSecs: 300 # 5 minutes + setOAuthAccessTokenExpirationTimeSecs: 300 # 5 minutes + setOAuthRefreshTokenExpirationTimeSecs: 14515200 # 24 weeks +``` + +### Maximum number of active refresh tokens + +The maximum number of active OAuth refresh tokens a user is allowed to have can be configured as follows: + +```yaml +brig: + # ... + config: + # ... + optSettings: + # ... + setOAuthMaxActiveRefreshTokens: 20 +``` + +## Enable 3rd party apps for teams + +3rd party apps are enabled based on the team's payment plan by `ibis`. + +## Implementation details + +### Token handling + +#### Authorization code + +The authorization code is stored as plain text rather than a "scrypted" hash because it is the key to look up the associated information like the client ID, the user ID, the scope and the redirect URL. An authorization code can only be used once and has a very short time to live. + +#### Access token + +Access tokens are self-contained JSON Web Tokens (JWT) that contain the following claims: + +- `iss`: The issuer, e.g. `wire-server` +- `aud`: The resource server (in our case the same as `iss`) +- `iat`: The time at which the token was issued +- `sub`: Identifier of the resource owner, the Wire user ID +- `exp`: The expiration time of the token +- `scope`: A whitespace separated list of permissions + +Example token payload: + +```json +{ + "iss": "server.example.com", + "aud": "server.example.com", + "iat": 1311280970, + "sub": "7cf24b6c-8c7e-4788-a532-2c998d20ce4a", + "exp": 1311281970, + "scope": "write:conversations write:conversations_code" +} +``` + +- The access tokens are created and signed by `brig`. +- When accessing a resource `nginz` validates the token and forwards the request to `wire-server` with the `Z-User` header containing the user ID taken from the `sub` claim. +- Token validation includes signature, expiration, and scope validation. +- Access tokens are short lived. +- Access tokens are bearer tokens and cannot be revoked directly, therefore 3rd party access revocation will entail the token expiration. + +#### Refresh token + +- A refresh token is always associated with + - a user + - a 3rd party app (the OAuth client) + - and a scope (list of permissions given to the app) +- A user can have more than one active refresh token for the same 3rd party app (e.g. they might use multiple devices, replace devices, or run multiple instances of the app somehow) +- The maximum number of active refresh tokens per user and app is limited (see `values.yaml` for default settings) +- Once a new refresh token is requested and the limit is exceeded, the oldest refresh token will be deleted/invalidated + - If the bearer of the invalidated token is not identical to the requester, it could mean that the bearer of the invalidated token needs to re-authorize +- Once a refresh token is used, it will be invalidated and a new refresh token will be generated and returned as part of the response (token rotation) +- For now, we will not yet implement re-use detection, but in the future this should be possible +- The refresh token is given to the client/app as a signed JWT containing only the refresh token ID which is used internally to look up the refresh token info +- Refresh tokens are long-lived, and the expiration is configurable on the server level + +### Scopes + +Endpoints that support OAuth have the required scope listed in the swagger documentation. + +#### Scope implementation details + +To enable OAuth access for a resource a scope has to be defined in the nginx location config that matches the endpoint's path. + +The current convention is that scope names should match the resource's paths separated by an underscore. E.g. `/conversations/:cid/code` becomes `conversations_code` (path parameters are omitted). + +Furthermore, the scope must be prefixed (separated by a colon) with + +- `admin`, `write`, or `read` for endpoints with HTTP method `GET` +- `admin`, or `write` for endpoints with HTTP methods `POST` or `PUT` +- and `admin` for endpoints with HTTP method `DELETE` + +E.g. the required scope for `POST /conversations/:cid/code` is `write:conversations_code`. + +### Steps for adding a new scope (making an endpoint accessible via OAuth) + +- Add a new constructor to the type `OAuthScope` in `/home/leif/Repositories/wire-server/libs/wire-api/src/Wire/API/OAuth.hs` +- Implement `IsOAuthScope` +- Update `ToByteString` and `FromByteString` instances and verify that the roundtrip tests run successfully +- Add the servant combinator `DescriptionOAuthScope` to the endpoint in question which will render the correct swagger description +- Finally assign the scope name (without the prefix) to the location config via the `charts/nginz/values.yaml` file to the `oauth_scope` as shown in the example below + +Example: + +```haskell +type SelfAPI = + Named + "get-self" + ( Summary "Get your own profile" + :> DescriptionOAuthScope 'ReadSelf + :> ZUser + :> "self" + :> Get '[JSON] SelfProfile + ) +``` + +```nginx + - path: /self$ # Matches exactly /self + oauth_scope: self + envs: +``` + +For local development and integration tests, add the scope to `services/nginz/integration-test/conf/nginz/nginx.conf` as follows + +```nginx + location ~* ^(/v[0-9]+)?/self$ { + include common_response_with_zauth.conf; + oauth_scope self; + proxy_pass http://brig; + } +``` + +### Public/private keys + +- Public and private keys are provided as JSON Web Keys (JWK) or key sets +- The keys can be generated using [jwx](https://github.com/lestrrat-go/jwx/tree/develop/v2/cmd/jwx#jwx-jwk-generate) +- Keys are provided as secrets. Details depend on the type of deployment. +- `brig` needs to be in possession of the public and private key and `nginz` needs to be provided with the public key only diff --git a/docs/src/developer/reference/oauth.mmd b/docs/src/developer/reference/oauth.mmd new file mode 100644 index 0000000000..e1f4dfe7f2 --- /dev/null +++ b/docs/src/developer/reference/oauth.mmd @@ -0,0 +1,18 @@ +sequenceDiagram + autonumber + actor U as User + participant C as Outlook Calendar Extension + participant A as Authorization Server (wire-server) + participant R as Resource Server (wire-server) + + U->>C: Click login + C->>A: Authorization code request + code challenge /authorize + A->>U: Redirect to login/authorization prompt + U->>A: Authenticate and consent + A->>C: Authorization code + C->>A: Authorization code + code verifier + A->>A: Validate authorization code + code verifier + A->>C: Access token + C->>R: Request a resource with access token (e.g. POST /conversations) + R->>R: Validate access token with public key from auth server + R->>C: Response diff --git a/docs/src/developer/reference/oauth.svg b/docs/src/developer/reference/oauth.svg new file mode 100644 index 0000000000..ebd4f56c36 --- /dev/null +++ b/docs/src/developer/reference/oauth.svg @@ -0,0 +1 @@ +UserOutlook Calendar ExtensionAuthorization Server (wire-server)Resource Server (wire-server)Click login1Authorization code request + code challenge /authorize2Redirect to login/authorization prompt3Authenticate and consent4Authorization code5Authorization code + code verifier6Validate authorization code + code verifier7Access token8Request a resource with access token (e.g. POST /conversations)9Validate access token with public key from auth server10Response11UserOutlook Calendar ExtensionAuthorization Server (wire-server)Resource Server (wire-server) diff --git a/docs/src/how-to/install/index.md b/docs/src/how-to/install/index.md index b45b694832..f312f8e332 100644 --- a/docs/src/how-to/install/index.md +++ b/docs/src/how-to/install/index.md @@ -19,6 +19,7 @@ Infrastructure configuration How to monitor wire-server How to see centralized logs for wire-server +Ingress-controller (getting traffic in) Web app settings sft restund diff --git a/docs/src/how-to/install/ingress.md b/docs/src/how-to/install/ingress.md new file mode 100644 index 0000000000..03a4329052 --- /dev/null +++ b/docs/src/how-to/install/ingress.md @@ -0,0 +1,102 @@ +# Ingress traffic to wire-server (ingress-nginx-controller) + +*at the time of writing (2023-03), this section assumes you use a kubernetes +version 1.23 or above (tested with 1.26)* + +## Installing in a cloud-like environment + +Install the ingress controller chart in your helmfile with the defaults, simply +like this: + +```yaml +# helmfile.yaml +repositories: + - name: wire + url: 'https://s3-eu-west-1.amazonaws.com/public.wire.com/charts' + +releases: + - name: 'ingress-nginx-controller' + namespace: 'wire' + chart: 'wire/ingress-nginx-controller' + version: 'CHANGE_ME' + +# charts wire-server and nginx-ingress-services also need to be installed, see other +# documentation +# - name: ... +# chart: ... +``` + +By default, the `wire/ingress-nginx-controller` chart will create a `Deployment` +with services of type `LoadBalancer`, where your kubernetes installation needs +to support dynamic LoadBalancers. If this is not possible, read the next section. + +By default three pods will come up and external traffic will be load balanced into these +three pods, which will also do TLS termination and forward traffic to upstream +services (`nginz` and others). + +To inspect default TLS settings, see [defaults in the latest code](https://github.com/wireapp/wire-server/blob/develop/charts/ingress-nginx-controller/values.yaml) and also see {ref}`tls`. + +## Installing on bare-metal without dynamic load balancer support + +In case you cannot create a `kind: service` of `type: LoadBalancer`, then you +can fall back to manually ensure traffic reaches your installation: + +```yaml +# helmfile.yaml +releases: + - name: 'ingress-nginx-controller' + namespace: 'wire' + chart: 'wire/ingress-nginx-controller' + version: 'CHANGE_ME' + values: + - './helm_vars/ingress-nginx-controller/values.yaml' +``` + +Create this file with the following override values: + +```yaml +# helm_vars/ingress-nginx-controller/values.yaml +ingress-nginx: + controller: + kind: DaemonSet + service: + type: NodePort +``` + +Then, on each of your kubernetes worker nodes, two ports are exposed: ports +31773 (https) and 31772 (http) + +You should add a port-forwarding rule on the node or on the loadbalancer that +forwards ports 443 and 80 to these respective ports. Any traffic hitting the http port is simply getting a http 30x redirect to https. + +Downsides of this approach: The NodePort approach always requires manual configuration of some external load balancer/firewall to round-robin between node IPs and is error-prone. It's also a bit annoying to have to decide on some global ports that may not be used otherwise. + +Most managed K8s clusters have support for LoadBalancers, you can also get this for your own clusters in hcloud etc. It's even possible to do it for pure bare metal, without any "load balancer hardware", by using BGP or some leadership election over who's announcing the "load balancer ip" via ARP (https://metallb.universe.tf/configuration/_advanced_l2_configuration/). + +### Using NodePort (not the default) with externalTrafficPolicy=Local (the default) + +Normally, NodePort will listen to traffic on all nodes, and uses kube-proxy +to redirect to the node that actually runs ingress-nginx-controller. However +one problem with this is that this traffic is NAT'ed. This means that nginx +will not have access to the source IP address from which the request +originated. We want to have this source IP address for potentially logging +and rate-limiting based on it. By setting externalTrafficPolicy: local, +nodes will no longer forward requests to other nodes if they receive a +request that they themselves can not handle. Upside is that the traffic is +now not NAT'ed anymore, and we get access to the source IP address. Downside +is that you need to know beforehand which nodes run a certain pod. However, +with kubernetes a pod can be rescheduled to any node at any time so we can +not trust this. We could do something with node affinities to decide apriori +on what set of nodes will be publicly reachable and make sure the nginx +controller pods are only ran on there but for now that sounds a bit overkill. +Instead, we just simply run the ingress controller on each node using a +daemonset. This means that any node in the cluster can receive requests and +redirect them to the correct service, whilst maintaining the source ip +address. The ingress controller is sort of taking over the role of what +kube-proxy was doing before. +More information: +- https://kubernetes.io/docs/tutorials/services/source-ip/#source-ip-for-services-with-typenodeport +- https://kubernetes.github.io/ingress-nginx/deploy/baremetal/ + +There are also downsides to setting `externalTrafficPolicy: Local`, please look at the [following blog post](https://www.asykim.com/blog/deep-dive-into-kubernetes-external-traffic-policies), which very clearly explains the upsides and +downsides of this setting diff --git a/docs/src/how-to/install/tls.md b/docs/src/how-to/install/tls.md index f3a044597a..2de626d705 100644 --- a/docs/src/how-to/install/tls.md +++ b/docs/src/how-to/install/tls.md @@ -25,7 +25,7 @@ Therefore it is not necessary to add them to openssl based configurations. ## Ingress Traffic (wire-server) -The list of TLS ciphers for incoming requests is limited by default to the [following](https://github.com/wireapp/wire-server/blob/master/charts/nginx-ingress-controller/values.yaml#L7) (for general server-certificates, both for federation and client API), and can be overridden on your installation if needed. +The list of TLS ciphers for incoming requests is limited by default to the [following](https://github.com/wireapp/wire-server/blob/master/charts/ingress-nginx-controller/values.yaml#L41-45) (for general server-certificates, both for federation and client API), and can be overridden on your installation if needed. ## Egress Traffic (wire-server/federation) diff --git a/docs/src/understand/api-client-perspective/swagger.md b/docs/src/understand/api-client-perspective/swagger.md index a7f1b0fb64..e7430b1c26 100644 --- a/docs/src/understand/api-client-perspective/swagger.md +++ b/docs/src/understand/api-client-perspective/swagger.md @@ -40,14 +40,19 @@ docs. The first part of the URL's path is the version. No specified version means Swagger docs of the *latest* API version. This differs from other API endpoints -where no version means `v0`! New versions are added from time to time. If you +where no version means `v0`! + +New versions are added from time to time. If you would like to look at the docs of another version (which did not exist at the time of writing): Just update the first path element of an existing link. - The URL pattern is `https:///v/api/swagger-ui/`. To figure out which versions are supported by your backend, query `https:////api-version`. +If you want to get the raw json for the swagger (ie., for compiling it +into client code in typescript, kotlin, swift, ...), replace +`swagger-ui` with `swagger.json` in the above URL pattern. + The [API versioning](../../developer/developer/api-versioning.md) article discusses the versioning topic in detail. @@ -57,7 +62,7 @@ To get the versions a backend (`staging-nginz-https.zinfra.io` in this case) supports, execute: ```sh -curl https://staging-nginz-https.zinfra.io/api-version +curl https://staging-nginz-https.zinfra.io/api-version {"development":[3],"domain":"staging.zinfra.io","federation":false,"supported":[0,1,2]} ``` @@ -70,19 +75,8 @@ Swagger docs for internal endpoints are served per service. I.e. there's one for `brig`, one for `cannon`, etc.. This is because Swagger doesn't play well with multiple actions having the same combination of HTTP method and URL path. -- Version `v3`: - - [`brig` - **internal** (private) - endpoints](https://staging-nginz-https.zinfra.io/v3/api-internal/swagger-ui/brig) - - [`cannon` - **internal** (private) - endpoints](https://staging-nginz-https.zinfra.io/v3/api-internal/swagger-ui/cannon) - - [`cargohold` - **internal** (private) - endpoints](https://staging-nginz-https.zinfra.io/v3/api-internal/swagger-ui/cargohold) - - [`galley` - **internal** (private) - endpoints](https://staging-nginz-https.zinfra.io/v3/api-internal/swagger-ui/galley) - - [`legalhold` - **internal** (private) - endpoints](https://staging-nginz-https.zinfra.io/v3/api-internal/swagger-ui/legalhold) - - [`spar` - **internal** (private) - endpoints](https://staging-nginz-https.zinfra.io/v3/api-internal/swagger-ui/spar) +Internal APIs are not under version control. + - Unversioned: - [`brig` - **internal** (private) endpoints](https://staging-nginz-https.zinfra.io/api-internal/swagger-ui/brig) @@ -92,15 +86,11 @@ multiple actions having the same combination of HTTP method and URL path. endpoints](https://staging-nginz-https.zinfra.io/api-internal/swagger-ui/cargohold) - [`galley` - **internal** (private) endpoints](https://staging-nginz-https.zinfra.io/api-internal/swagger-ui/galley) - - [`legalhold` - **internal** (private) - endpoints](https://staging-nginz-https.zinfra.io/api-internal/swagger-ui/legalhold) - [`spar` - **internal** (private) endpoints](https://staging-nginz-https.zinfra.io/api-internal/swagger-ui/spar) -The URL pattern is similar to that of public endpoints: -`https:///v/api-internal/swagger-ui/`. No specified version -means Swagger docs the *latest* version (as for public endpoints' Swagger docs.) +The URL pattern is similar to that of public endpoints for latest version: +`https:///api-internal/swagger-ui/`. -Due to technical reasons (we started to export Swagger docs for internal -endpoints in version `v3`), there are no meaningful Swagger docs for internal -endpoints for versions `v0` to `v2`. +If you want to get the raw json of the swagger: +`https:///api-internal/swagger-ui/-swagger.json`. diff --git a/docs/src/understand/single-sign-on/okta/001-applications-screen.png b/docs/src/understand/single-sign-on/okta/001-applications-screen.png index 399c09cd2a..58f1f58d42 100644 Binary files a/docs/src/understand/single-sign-on/okta/001-applications-screen.png and b/docs/src/understand/single-sign-on/okta/001-applications-screen.png differ diff --git a/docs/src/understand/single-sign-on/okta/002-add-application.png b/docs/src/understand/single-sign-on/okta/002-add-application.png index ea8b8b1c95..bdceb00aab 100644 Binary files a/docs/src/understand/single-sign-on/okta/002-add-application.png and b/docs/src/understand/single-sign-on/okta/002-add-application.png differ diff --git a/docs/src/understand/single-sign-on/okta/003-add-application-1.png b/docs/src/understand/single-sign-on/okta/003-add-application-1.png deleted file mode 100644 index b077dd957f..0000000000 Binary files a/docs/src/understand/single-sign-on/okta/003-add-application-1.png and /dev/null differ diff --git a/docs/src/understand/single-sign-on/okta/003-add-application-step1.png b/docs/src/understand/single-sign-on/okta/003-add-application-step1.png new file mode 100644 index 0000000000..7c736c0bcc Binary files /dev/null and b/docs/src/understand/single-sign-on/okta/003-add-application-step1.png differ diff --git a/docs/src/understand/single-sign-on/okta/004-add-application-step1.png b/docs/src/understand/single-sign-on/okta/004-add-application-step1.png deleted file mode 100644 index 93b638dc94..0000000000 Binary files a/docs/src/understand/single-sign-on/okta/004-add-application-step1.png and /dev/null differ diff --git a/docs/src/understand/single-sign-on/okta/004-add-application-step2.png b/docs/src/understand/single-sign-on/okta/004-add-application-step2.png new file mode 100644 index 0000000000..fee7a06ed8 Binary files /dev/null and b/docs/src/understand/single-sign-on/okta/004-add-application-step2.png differ diff --git a/docs/src/understand/single-sign-on/okta/005-add-application-step2.png b/docs/src/understand/single-sign-on/okta/005-add-application-step2.png deleted file mode 100644 index 92bfae99d2..0000000000 Binary files a/docs/src/understand/single-sign-on/okta/005-add-application-step2.png and /dev/null differ diff --git a/docs/src/understand/single-sign-on/okta/005-add-application-step3.png b/docs/src/understand/single-sign-on/okta/005-add-application-step3.png new file mode 100644 index 0000000000..80f1982ae3 Binary files /dev/null and b/docs/src/understand/single-sign-on/okta/005-add-application-step3.png differ diff --git a/docs/src/understand/single-sign-on/okta/006-add-application-sign-on.png b/docs/src/understand/single-sign-on/okta/006-add-application-sign-on.png new file mode 100644 index 0000000000..296601c3ca Binary files /dev/null and b/docs/src/understand/single-sign-on/okta/006-add-application-sign-on.png differ diff --git a/docs/src/understand/single-sign-on/okta/006-add-application-step3.png b/docs/src/understand/single-sign-on/okta/006-add-application-step3.png deleted file mode 100644 index 3e7c9e1a13..0000000000 Binary files a/docs/src/understand/single-sign-on/okta/006-add-application-step3.png and /dev/null differ diff --git a/docs/src/understand/single-sign-on/okta/007-application-sign-on.png b/docs/src/understand/single-sign-on/okta/007-application-sign-on.png deleted file mode 100644 index b21a684e0a..0000000000 Binary files a/docs/src/understand/single-sign-on/okta/007-application-sign-on.png and /dev/null differ diff --git a/docs/src/understand/single-sign-on/okta/007-view-idp-metadata.png b/docs/src/understand/single-sign-on/okta/007-view-idp-metadata.png new file mode 100644 index 0000000000..666920e099 Binary files /dev/null and b/docs/src/understand/single-sign-on/okta/007-view-idp-metadata.png differ diff --git a/docs/src/understand/single-sign-on/okta/008-assignment.png b/docs/src/understand/single-sign-on/okta/008-assignment.png index 9a347e1a66..2d4aea5e8e 100644 Binary files a/docs/src/understand/single-sign-on/okta/008-assignment.png and b/docs/src/understand/single-sign-on/okta/008-assignment.png differ diff --git a/docs/src/understand/single-sign-on/okta/main.md b/docs/src/understand/single-sign-on/okta/main.md index 6fe285c55f..e4f161a00c 100644 --- a/docs/src/understand/single-sign-on/okta/main.md +++ b/docs/src/understand/single-sign-on/okta/main.md @@ -12,32 +12,24 @@ ### Okta setup - Log in into Okta web interface -- Open the admin console and switch to the "Classic UI" - Navigate to "Applications" -- Click "Add application" +- Click "Create App Integration" ```{image} 001-applications-screen.png ``` ______________________________________________________________________ -- Create a new application +- Choose `SAML 2.0` ```{image} 002-add-application.png ``` ______________________________________________________________________ -- Choose `Web`, `SAML 2.0` - -```{image} 003-add-application-1.png -``` - -______________________________________________________________________ - - Pick a name for the application in "Step 1" and continue -```{image} 004-add-application-step1.png +```{image} 003-add-application-step1.png ``` ______________________________________________________________________ @@ -63,7 +55,7 @@ ______________________________________________________________________ **(\*) Note**: The application username **must be** unique in your team, and should be immutable once assigned. If more than one user has the same value for the field that you select here, those two users will log in as a single user on Wire. And if the value were to change, users will be re-assigned to a new account at the next login. Usually, `email` is a safe choice but you should evaluate it for your case. -```{image} 005-add-application-step2.png +```{image} 004-add-application-step2.png ``` ______________________________________________________________________ @@ -78,7 +70,7 @@ ______________________________________________________________________ +-----------------------------------+------------------------------------------------------------------------+ ``` -```{image} 006-add-application-step3.png +```{image} 005-add-application-step3.png ``` ______________________________________________________________________ @@ -87,11 +79,23 @@ ______________________________________________________________________ - Find the "Identity Provider Metadata" link. Copy the link address (normally done by right-clicking on the link and selecting "Copy link location" or a similar item in the menu). - Store the link address somewhere for a future step. -```{image} 007-application-sign-on.png +```{image} 006-add-application-sign-on.png ``` ______________________________________________________________________ +- In the case you are looking for Download of IdP Metadata and SAML Signing Certificates. +- Under "Sign-On" tab, find (scroll down) SAML Signing Certificates section. +- Click the Actions button next to the certificate. +- Choosing View IdP metadata opens a new browser tab containing the metadata, you can then right-click the metadata and save it as a (.xml) file. +- Choosing Download certificate will automatically save the certificate as a file. + +**(\*) Note**: To provide IdP metadata in Wire's Team Settings, file has to be saved as `.xml` file. + +```{image} 007-view-idp-metadata.png +``` +______________________________________________________________________ + - Switch to the "Assignments" tab - Make sure that some users (or everyone) is assigned to the application. These are the users that will be allowed to log in to Wire using Single Sign On. Add the relevant users to the list with the "Assign" button. diff --git a/docs/src/understand/single-sign-on/understand/Wire_SAML_Flow.png b/docs/src/understand/single-sign-on/understand/Wire_SAML_Flow.png deleted file mode 100644 index 6efa902500..0000000000 Binary files a/docs/src/understand/single-sign-on/understand/Wire_SAML_Flow.png and /dev/null differ diff --git a/docs/src/understand/single-sign-on/understand/main.md b/docs/src/understand/single-sign-on/understand/main.md index 3192231755..068569ee47 100644 --- a/docs/src/understand/single-sign-on/understand/main.md +++ b/docs/src/understand/single-sign-on/understand/main.md @@ -147,7 +147,33 @@ Here is a blog post we like about how SAML works: wireserver : User starts authentication in Wire +wireserver -> user: HTTP POST to IdP w/auth request +user -> idp : (HTML FORM redirect in browser) +note right: Auth request is passed, verified + +idp --> idp: end user is sent to login page at IdP \n user logs in, or browser sends cookie + +... + + +idp -> user: Redirect to Wire w/ SAML token +note right: SAML token is generated +user -> wireserver: (HTML FORM redirect in browser) +wireserver -> user: User is logged into Wire + +@enduml ``` Here is a critique of XML/DSig security (which SAML relies on): diff --git a/flaky-tests.yaml b/flaky-tests.yaml new file mode 100644 index 0000000000..d3ae0c2442 --- /dev/null +++ b/flaky-tests.yaml @@ -0,0 +1,83 @@ +- + test_name: no extra results.new-index + comments: | + I think this is a known flake (Stefan) + +- + test_name: "team tests around truncation limits - no events, too large team" + comments: | + Exception: Timeout: No matching notification received. + Match failure: expected: Just "user.delete" + but got: Just "conversation.delete" + + 2023-03-24: The test has multiple "user.delete" assertions, but since there + is a "conversation.delete" event I think it must be the one + generated by the team deletion. This would also explain why it + might take longer for the event to happen than in other cases. + I've increased the timeout from 4 to 7 seconds for that one + assertion. Let's see if this helps. + +- test_name: "GET /mls/key-packages/claim/local/:user - self claim" + comments: | + /bin/sh: createProcess: posix_spawnp: failed (Bad address) + This is probably to resource exhaustion. + Can we try increasing limits? + +- + test_name: "Brig API Integration.MLS.GET /mls/key-packages/claim/local/:user" + comments: | + Same error as for teset "GET /mls/key-packages/claim/local/:user - self claim" + + Error executing request: /bin/sh: createProcess: posix_spawnp: failed (Bad address) + +- + test_name: "max active tokens" + comments: | + CallStack (from HasCallStack): + assertFailure, called at ./Test/Tasty/HUnit/Orig.hs:71:30 in tasty-hunit-0.10.0.3-BA9Dg64ujOjHrKq3kYOvGI:Test.Tasty.HUnit.Orig + assertBool, called at test/integration/API/OAuth.hs:473:16 in main:API.OAuth + Use -p '(!/turn/&&!/user.auth.cookies.limit/)&&/max active tokens/' to rerun this test only. + +- + test_name: "send billing events to some owners in large teams (indexedBillingTeamMembers disabled)" + comments: | + Error message: create team: Expected 1 TeamActivate, got nothing + + CallStack (from HasCallStack): + assertFailure, called at test/integration/API/SQS.hs:74:32 in main:API.SQS + tActivate, called at test/integration/API/SQS.hs:78:47 in main:API.SQS + assertTeamActivate, called at test/integration/API/Util.hs:191:3 in main:API.Util + createBindingTeam', called at test/integration/API/Util.hs:183:20 in main:API.Util + createBindingTeam, called at test/integration/API/Teams.hs:1546:25 in main:API.Teams + +- + test_name: "delete team conversation" + comments: | + Exception: unexpected notification received + Match failure: expected: no notification + but got: "conversation.create" + + 2023-03-24: The test is not waiting for notifications triggered by creating + conversations. When later asserting that no notification is sent + for deleting conversations, the test fails under load when the + create conversation notification happens to come in late. This + is fixed by waiting for all previous notifications before + asserting the absence of (new) notifications. + +- + test_name: "POST /federation/on-user-deleted-conversations : Remove deleted remote user from local conversations" + comments: | + Exception: unexpected notification received + Match failure: expected: no notification + but got: "conversation.create" + + 2023-03-24: The test is not waiting for notifications triggered by creating + conversations. When later asserting that no notification is sent + for deleting user, the test fails under load when the create + conversation notification happens to come in late. This is fixed + by waiting for all previous notifications before asserting the + absence of (new) notifications. +- + test_name: "POST /register - can add team members above fanout limit when whitelisting is enabled" + comments: | + 2023-03-27: Hopefully fix by increasing timeout from 10 to 15 seconds for this test. diff --git a/hack/bin/cabal-run-integration.sh b/hack/bin/cabal-run-integration.sh index 57cf9a2874..b1c0043aa5 100755 --- a/hack/bin/cabal-run-integration.sh +++ b/hack/bin/cabal-run-integration.sh @@ -61,6 +61,7 @@ run_all_integration_tests() { run_integration_tests "$package" fi done + run_integration_tests "stern" } if [ "$package" == "all" ]; then diff --git a/hack/bin/flaky_tests.py b/hack/bin/flaky_tests.py new file mode 100755 index 0000000000..84bc8ad861 --- /dev/null +++ b/hack/bin/flaky_tests.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python3 + +import datetime +import requests +import json +import os +import yaml +import argparse +from pydoc import pager + +BUCKET_BASEURL = 'https://s3.eu-west-1.amazonaws.com/public.wire.com/ci/failing-tests' +CONCOURSE_BASEURL = 'https://concourse.ops.zinfra.io/teams/main' +CONCOURSE_LOG_RETENTION_DAYS = 7 + +class Colors: + GREEN = "\x1b[38;5;10m" + YELLOW = "\x1b[38;5;11m" + BLUE = "\x1b[38;5;6m" + PURPLEISH = "\x1b[38;5;13m" + ORANGE = "\x1b[38;5;3m" + RED = "\x1b[38;5;1m" + RESET = "\x1b[0m" + +def read_flaky_tests(): + project_root = os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir) + result = {} + with open(os.path.join(project_root, 'flaky-tests.yaml'), 'r') as f: + d = yaml.safe_load(f) + for item in d: + result[item['test_name']] = item['comments'] + return result + +def current_week_start(now): + k = now.weekday() + return (now - datetime.timedelta(days=k)).replace(hour=0, minute=0, second=0, microsecond=0) + +def format_date(dt): + return dt.strftime('%Y-%m-%d') + +def failing_tests_fn(week_start): + return format_date(week_start) + '_failing_tests.json' + +def fetch_week(week_start): + url = f'{BUCKET_BASEURL}/{failing_tests_fn(week_start)}' + r = requests.get(url) + result = [] + if r.status_code == 200: + for line in r.content.split(b'\n'): + if len(line) > 0: + item = json.loads(line.decode('utf8')) + result.append(item) + return result + +def fetch(today, n_weeks): + ws_start = current_week_start(today) + data = [] + for i in range(n_weeks): + ws = ws_start + datetime.timedelta(days=-7*i) + print(f'\rFetching {i+1}/{n_weeks} {format_date(ws)}') + data += fetch_week(ws) + print() + return data + +def tests_match(s1, s2): + return s1 in s2 or s2 in s1 + +def longer(s1, s2): + if len(s1) > len(s2): + return s1 + else: + return s2 + +def is_flake(item): + b = item['build'] + return ((b['pipeline_name'] == 'mls' and b['job_name'] == 'test') \ + or (b['pipeline_name'] == 'staging' and b['job_name'] == 'test') \ + or (b['pipeline_name'] == 'prod' and b['job_name'] == 'test')) + + +def add_flake(flake_set, test_name): + for k in flake_set: + if tests_match(k, test_name): + k_ = longer(k, test_name) + if k_ != k: + flake_set.remove(k) + flake_set.add(k_) + return + flake_set.add(test_name) + +def discover_flakes(data): + flake_set = set() + for item in data: + if is_flake(item): + add_flake(flake_set, item['test_name']) + return flake_set + +def search_matching_flake(test_names, test_name): + for flake in test_names: + if tests_match(flake, test_name): + return flake + +def associate_fails(test_names, data, default_comment=''): + d = {k: [] for k in test_names} + unassociated = [] + for item in data: + flake = search_matching_flake(test_names, item['test_name']) + if flake: + d[flake].append(item) + else: + unassociated.append(item) + + return [{'test_name': k, 'fails': v, 'comments': default_comment} for k, v in d.items()], unassociated + +def associate_comments(flakes, comments, default_comments=''): + for flake in flakes: + flake['comments'] = comments.get(flake['test_name'], default_comments) + +def sort_flakes(flakes): + flakes.sort(key=lambda f: (-len(f['fails']), f['test_name']), reverse=False) + for flake in flakes: + flake['fails'].sort(key=lambda f: f['build']['end_time'], reverse=True) + +def humanize_days(n): + if n < 7: + if n == 0: + return 'today' + elif n == 1: + return 'yesterday' + else: + return f'{n} days ago' + else: + weeks = n // 7 + if weeks < 4: + return f'{weeks} week{"s" if weeks >= 2 else ""} ago' + else: + return f'{weeks // 4} month{"s" if weeks >= 8 else ""} ago' + +def human_format_date(dt, today): + days = (today - dt).days + return Colors.BLUE + f'· {format_date(dt)} ({humanize_days(days)})' + Colors.RESET + +def create_url(build): + return CONCOURSE_BASEURL + f"/pipelines/{build['pipeline_name']}/jobs/{build['job_name']}/builds/{build['name']}" + +def pretty_flake(flake, today, logs=False): + lines = [] + lines.append(Colors.YELLOW + f"❄ \"{flake['test_name']}\"" + Colors.RESET) + + if not logs: + lines.append(f' Run with --logs "{flake["test_name"]}" to see error logs') + + comments = flake['comments'] + if comments: + for l in comments.splitlines(): + lines.append(' ' + Colors.PURPLEISH + l + Colors.RESET) + lines.append('') + for fail in flake['fails']: + b = fail['build'] + + end_time = datetime.datetime.fromtimestamp(b['end_time']) + s = human_format_date(end_time, today) + if (today - end_time) < datetime.timedelta(days=CONCOURSE_LOG_RETENTION_DAYS): + url = create_url(b) + s = s + ' ' + url + lines.append(' ' + s) + if logs: + lines.append('') + for l in fail['context'].splitlines(): + lines.append(' ' + l) + lines.append('') + + return "\n".join(lines) + '\n' + +def pretty_flakes(flakes, today, logs=False): + lines = [] + for flake in flakes: + lines.append(pretty_flake(flake, today, logs)) + return '\n'.join(lines) + +explain = '''Tips: + Run with --discover to manually discover new flaky tests + +''' + +def main(): + parser = argparse.ArgumentParser(prog='flaky_test.py', description='Shows flaky tests') + parser.add_argument('-d', '--discover', action='store_true', help='Show failing tests that are not marked/discovered as being flaky. Use this to manually discover flaky test.') + parser.add_argument('-l', '--logs', help='Show surrounding logs for given test') + args = parser.parse_args() + + today = datetime.datetime.now() + data = fetch(today=today, n_weeks=4*4) + if args.logs: + flakes, unassociated = associate_fails([args.logs], data) + sort_flakes(flakes) + pager(explain + pretty_flakes(flakes, today, logs=True)) + return + + test_names = discover_flakes(data) + flaky_tests_comments = read_flaky_tests() + test_names = test_names.union(flaky_tests_comments.keys()) + flakes, unassociated = associate_fails(test_names, data) + + + if args.discover: + + test_names = set([i['test_name'] for i in unassociated]) + flake_candidates, _ = associate_fails(test_names, unassociated, '(if this is a flaky test, please add it to flaky-tests.yaml)') + sort_flakes(flake_candidates) + pager(explain + pretty_flakes(flake_candidates, today)) + + else: + associate_comments(flakes, flaky_tests_comments, '(discovered flake, please check and add it to flaky-tests.yaml)') + sort_flakes(flakes) + pager(explain + pretty_flakes(flakes, today)) + + +def test(): + data = fetch(1) + return data + + +if __name__ == '__main__': + main() diff --git a/hack/bin/integration-setup-federation.sh b/hack/bin/integration-setup-federation.sh index f569928239..f624dc1167 100755 --- a/hack/bin/integration-setup-federation.sh +++ b/hack/bin/integration-setup-federation.sh @@ -20,13 +20,22 @@ ${DIR}/integration-cleanup.sh # script beforehand on all relevant charts to download the nested dependencies # (e.g. cassandra from underneath databases-ephemeral) echo "updating recursive dependencies ..." -charts=(fake-aws databases-ephemeral redis-cluster wire-server nginx-ingress-controller nginx-ingress-services) +charts=(fake-aws databases-ephemeral redis-cluster wire-server ingress-nginx-controller nginx-ingress-controller nginx-ingress-services) mkdir -p ~/.parallel && touch ~/.parallel/will-cite printf '%s\n' "${charts[@]}" | parallel -P "${HELM_PARALLELISM}" "$DIR/update.sh" "$CHARTS_DIR/{}" # FUTUREWORK: use helm functions instead, see https://wearezeta.atlassian.net/browse/SQPIT-723 echo "Generating self-signed certificates..." +KUBERNETES_VERSION_MAJOR="$(kubectl version -o json | jq -r .serverVersion.major)" +KUBERNETES_VERSION_MINOR="$(kubectl version -o json | jq -r .serverVersion.minor)" +export KUBERNETES_VERSION="$KUBERNETES_VERSION_MAJOR.$KUBERNETES_VERSION_MINOR" +if (( KUBERNETES_VERSION_MAJOR > 1 || KUBERNETES_VERSION_MAJOR == 1 && KUBERNETES_VERSION_MINOR >= 23 )); then + export INGRESS_CHART="ingress-nginx-controller" +else + export INGRESS_CHART="nginx-ingress-controller" +fi +echo "kubeVersion: $KUBERNETES_VERSION and ingress controller=$INGRESS_CHART" export NAMESPACE_1="$NAMESPACE" export FEDERATION_DOMAIN_BASE="$NAMESPACE_1.svc.cluster.local" export FEDERATION_DOMAIN_1="federation-test-helper.$FEDERATION_DOMAIN_BASE" diff --git a/hack/bin/integration-setup.sh b/hack/bin/integration-setup.sh index 59cf0e4f84..ed6be40c9e 100755 --- a/hack/bin/integration-setup.sh +++ b/hack/bin/integration-setup.sh @@ -14,10 +14,19 @@ HELM_PARALLELISM=${HELM_PARALLELISM:-1} "${DIR}/integration-cleanup.sh" echo "updating recursive dependencies ..." -charts=(fake-aws databases-ephemeral redis-cluster wire-server nginx-ingress-controller nginx-ingress-services) +charts=(fake-aws databases-ephemeral redis-cluster wire-server ingress-nginx-controller nginx-ingress-controller nginx-ingress-services) mkdir -p ~/.parallel && touch ~/.parallel/will-cite printf '%s\n' "${charts[@]}" | parallel -P "${HELM_PARALLELISM}" "$DIR/update.sh" "$CHARTS_DIR/{}" +KUBERNETES_VERSION_MAJOR="$(kubectl version -o json | jq -r .serverVersion.major)" +KUBERNETES_VERSION_MINOR="$(kubectl version -o json | jq -r .serverVersion.minor)" +export KUBERNETES_VERSION="$KUBERNETES_VERSION_MAJOR.$KUBERNETES_VERSION_MINOR" +if (( KUBERNETES_VERSION_MAJOR > 1 || KUBERNETES_VERSION_MAJOR == 1 && KUBERNETES_VERSION_MINOR >= 23 )); then + export INGRESS_CHART="ingress-nginx-controller" +else + export INGRESS_CHART="nginx-ingress-controller" +fi +echo "kubeVersion: $KUBERNETES_VERSION and ingress controller=$INGRESS_CHART" echo "Generating self-signed certificates..." export FEDERATION_DOMAIN_BASE="$NAMESPACE.svc.cluster.local" export FEDERATION_DOMAIN="federation-test-helper.$FEDERATION_DOMAIN_BASE" diff --git a/hack/bin/integration-teardown-federation.sh b/hack/bin/integration-teardown-federation.sh index 91897a6059..a439ab6219 100755 --- a/hack/bin/integration-teardown-federation.sh +++ b/hack/bin/integration-teardown-federation.sh @@ -12,6 +12,13 @@ export NAMESPACE_2="$NAMESPACE-fed2" export FEDERATION_DOMAIN_1="." export FEDERATION_DOMAIN_2="." +KUBERNETES_VERSION_MINOR="$(kubectl version -o json | jq -r .serverVersion.minor)" +if (( KUBERNETES_VERSION_MAJOR > 1 || KUBERNETES_VERSION_MAJOR == 1 && KUBERNETES_VERSION_MINOR >= 23 )); then + export INGRESS_CHART="ingress-nginx-controller" +else + export INGRESS_CHART="nginx-ingress-controller" +fi + . "$DIR/helm_overrides.sh" helmfile --file "${TOP_LEVEL}/hack/helmfile.yaml" destroy diff --git a/hack/bin/integration-teardown.sh b/hack/bin/integration-teardown.sh index cd82194c2b..60f1781b53 100755 --- a/hack/bin/integration-teardown.sh +++ b/hack/bin/integration-teardown.sh @@ -6,6 +6,12 @@ TOP_LEVEL="$DIR/../.." NAMESPACE=${NAMESPACE:-test-integration} # doesn't matter for destruction but needs to be set export FEDERATION_DOMAIN="." +KUBERNETES_VERSION_MINOR="$(kubectl version -o json | jq -r .serverVersion.minor)" +if (( KUBERNETES_VERSION_MAJOR > 1 || KUBERNETES_VERSION_MAJOR == 1 && KUBERNETES_VERSION_MINOR >= 23 )); then + export INGRESS_CHART="ingress-nginx-controller" +else + export INGRESS_CHART="nginx-ingress-controller" +fi set -ex diff --git a/hack/bin/integration-test.sh b/hack/bin/integration-test.sh index 47c5db9c3d..a354d636dc 100755 --- a/hack/bin/integration-test.sh +++ b/hack/bin/integration-test.sh @@ -6,11 +6,12 @@ NAMESPACE=${NAMESPACE:-test-integration} # set to 1 to disable running helm tests in parallel HELM_PARALLELISM=${HELM_PARALLELISM:-1} CLEANUP_LOCAL_FILES=${CLEANUP_LOCAL_FILES:-1} # set to 0 to keep files +UPLOAD_LOGS=${UPLOAD_LOGS:-0} echo "Running integration tests on wire-server with parallelism=${HELM_PARALLELISM} ..." CHART=wire-server -tests=(galley cargohold gundeck federator spar brig) +tests=(stern galley cargohold gundeck federator spar brig) cleanup() { if (( CLEANUP_LOCAL_FILES > 0 )); then @@ -21,6 +22,17 @@ cleanup() { fi } +# Copy to the concourse output (indetified by $OUTPUT_DIR) for propagation to +# following steps. +copyToAwsS3(){ + if (( UPLOAD_LOGS > 0 )); then + for t in "${tests[@]}"; do + echo "Copy logs-$t to s3://wire-server-test-logs/test-logs-$VERSION/$t-$VERSION.log" + aws s3 cp "logs-$t" "s3://wire-server-test-logs/test-logs-$VERSION/$t-$VERSION.log" + done + fi +} + summary() { echo "===============" echo "=== summary ===" @@ -83,6 +95,7 @@ if ((exit_code > 0)); then summary fi +copyToAwsS3 cleanup if ((exit_code > 0)); then diff --git a/hack/bin/oauth_test.sh b/hack/bin/oauth_test.sh new file mode 100755 index 0000000000..e5d9975d9c --- /dev/null +++ b/hack/bin/oauth_test.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash + +set -e + +USAGE="This script tests the OAuth2 flow by creating a client, requesting an authorization code, and +then requesting an access token. It then uses the access token to make a request to /self. + +Create a user first with './create_test_user.sh -n 1 -c'. Then use the user ID to call this script. + +USAGE: $0 + -u : User ID +" + +unset -v USER + +while getopts ":u:" opt; do + case ${opt} in + u) + USER="$OPTARG" + ;; + \?) + echo "$USAGE" 1>&2 + exit 1 + ;; + :) + echo "-$OPTARG" requires an argument 1>&2 + exit 1 + ;; + esac +done +shift $((OPTIND - 1)) + +if [ -z "$USER" ]; then + echo 'missing option -u ' 1>&2 + echo "$USAGE" 1>&2 + exit 1 +fi + +SCOPE="read:self" +# Beware to not use hard coded values like these, anywhere but in test environments +CODE_VERIFIER="0LgRJptQI--6vQjlQfoXEM1GG4oSeN6ttESXVZRL6SEQAS6GuXW4X_FNkfp72BS9W157xZQKoJqXksj8C6UzO0.MQfgV3sdeOlg1XNSVlR50gYHfVM0A~qNyfZDmWFE8" +CODE_CHALLENGE="dc8qty_fbGDz4wfFPnvApmLfaP14uYDVkJ2tE8N0Xgk" + +CLIENT=$( + curl -s -X POST localhost:8082/i/oauth/clients \ + -H "Content-Type: application/json" \ + -d '{ + "application_name":"foobar", + "redirect_url":"https://example.com" + }' +) + +CLIENT_ID=$(echo "$CLIENT" | jq -r '.client_id') + +AUTH_CODE_RESPONSE=$( + curl -i -s -X POST localhost:8082/oauth/authorization/codes \ + -H 'Z-User: '"$USER" \ + -H "Content-Type: application/json" \ + -d '{ + "client_id": "'"$CLIENT_ID"'", + "scope": "'"$SCOPE"'", + "response_type": "code", + "redirect_uri": "https://example.com", + "state": "foobar", + "code_challenge": "'"$CODE_CHALLENGE"'", + "code_challenge_method": "S256" + }' +) + +AUTH_CODE=$(echo "$AUTH_CODE_RESPONSE" | awk -F ': ' '/^Location/ {print $2}' | awk -F'[=&]' '{print $2}') + +ACCESS_TOKEN_RESPONSE=$( + curl -s -X POST localhost:8080/oauth/token \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d 'code='"$AUTH_CODE"'&client_id='"$CLIENT_ID"'&grant_type=authorization_code&redirect_uri=https://example.com&code_verifier='"$CODE_VERIFIER" +) + +ACCESS_TOKEN=$(echo "$ACCESS_TOKEN_RESPONSE" | jq -r '.access_token') +REFRESH_TOKEN=$(echo "$ACCESS_TOKEN_RESPONSE" | jq -r '.refresh_token') + +echo "client id : $CLIENT_ID" +echo "scope : $SCOPE" +echo "auth code : $AUTH_CODE" +echo "access token : $ACCESS_TOKEN" + +echo "" +echo "making a request to /self..." +curl -s -H 'Authorization: Bearer '"$ACCESS_TOKEN" -H "Content-Type: application/json" localhost:8080/self | jq + +REFRESH_ACCESS_TOKEN_RESPONSE=$( + curl -s -X POST localhost:8080/oauth/token \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d 'refresh_token='"$REFRESH_TOKEN"'&client_id='"$CLIENT_ID"'&grant_type=refresh_token' +) + +NEW_ACCESS_TOKEN=$(echo "$REFRESH_ACCESS_TOKEN_RESPONSE" | jq -r '.access_token') + +echo "" +echo "making a request to /self with a new access token ..." +curl -s -H 'Authorization: Bearer '"$NEW_ACCESS_TOKEN" -H "Content-Type: application/json" localhost:8080/self | jq + +echo "" +echo "Getting list of OAuth apps with account access..." + +curl -s -H 'Z-User: '"$USER" localhost:8082/oauth/applications | jq diff --git a/hack/bin/set-helm-chart-version.sh b/hack/bin/set-helm-chart-version.sh index 759da9a218..00b838642e 100755 --- a/hack/bin/set-helm-chart-version.sh +++ b/hack/bin/set-helm-chart-version.sh @@ -12,7 +12,7 @@ tempfile=$(mktemp) function update_chart(){ chart_file=$1 - sed -e "s/version: .*/version: $target_version/g" "$chart_file" > "$tempfile" && mv "$tempfile" "$chart_file" + sed -e "s/^version: .*/version: $target_version/g" "$chart_file" > "$tempfile" && mv "$tempfile" "$chart_file" } function write_versions() { diff --git a/hack/bin/update.sh b/hack/bin/update.sh index 20d597a796..47ecefb2b0 100755 --- a/hack/bin/update.sh +++ b/hack/bin/update.sh @@ -29,6 +29,12 @@ helmDepUp () { helm dep up echo "... updating in $path done." fi + + if grep "dependencies:" Chart.yaml; then + echo "Updating dependencies (from Chart.yaml) in $path ..." + helm dep up + echo "... updating in $path done." + fi } helmDepUp "$dir" diff --git a/hack/helm_vars/ingress-nginx-controller/values.yaml.gotmpl b/hack/helm_vars/ingress-nginx-controller/values.yaml.gotmpl new file mode 100644 index 0000000000..d3c24971e6 --- /dev/null +++ b/hack/helm_vars/ingress-nginx-controller/values.yaml.gotmpl @@ -0,0 +1,19 @@ +ingress-nginx: + controller: + ingressClassResource: + name: "nginx-{{ .Release.Namespace }}" + # -- Is this ingressClass enabled or not + enabled: true + ingressClass: "nginx-{{ .Release.Namespace }}" + kind: Deployment + replicaCount: 1 + service: + nodePorts: + # choose a random free port + https: null + http: null + # in CI, do not use ValidatingWebhooks, as these, if not properly cleaned up + # (i.e. the ingress controller was deleted in another namespace but the webhook remains) + # prevent new kind:Ingress resources to be created in the cluster. + admissionWebhooks: + enabled: false diff --git a/hack/helm_vars/nginx-ingress-controller/values.yaml b/hack/helm_vars/nginx-ingress-controller/values.yaml.gotmpl similarity index 67% rename from hack/helm_vars/nginx-ingress-controller/values.yaml rename to hack/helm_vars/nginx-ingress-controller/values.yaml.gotmpl index 53b1c57506..10fd76e22b 100644 --- a/hack/helm_vars/nginx-ingress-controller/values.yaml +++ b/hack/helm_vars/nginx-ingress-controller/values.yaml.gotmpl @@ -1,6 +1,8 @@ nginx-ingress: controller: - kind: DaemonSet + kind: Deployment + replicaCount: 1 + ingressClass: "nginx-{{ .Release.Namespace }}" service: type: NodePort externalTrafficPolicy: Local diff --git a/hack/helm_vars/nginx-ingress-services/values.yaml b/hack/helm_vars/nginx-ingress-services/values.yaml.gotmpl similarity index 92% rename from hack/helm_vars/nginx-ingress-services/values.yaml rename to hack/helm_vars/nginx-ingress-services/values.yaml.gotmpl index 76aa0657e8..fd80c5ca43 100644 --- a/hack/helm_vars/nginx-ingress-services/values.yaml +++ b/hack/helm_vars/nginx-ingress-services/values.yaml.gotmpl @@ -9,6 +9,7 @@ tls: useCertManager: false config: + ingressClass: "nginx-{{ .Release.Namespace }}" dns: https: nginz-https.integration.example.com ssl: nginz-ssl.integration.example.com diff --git a/hack/helm_vars/wire-server/values.yaml.gotmpl b/hack/helm_vars/wire-server/values.yaml.gotmpl index fa40f928e7..82f965a399 100644 --- a/hack/helm_vars/wire-server/values.yaml.gotmpl +++ b/hack/helm_vars/wire-server/values.yaml.gotmpl @@ -86,6 +86,11 @@ brig: setDpopMaxSkewSecs: 1 setDpopTokenExpirationTimeSecs: 300 setEnableMLS: true + setOAuthAuthCodeExpirationTimeSecs: 3 # 3 secs + setOAuthAccessTokenExpirationTimeSecs: 3 # 3 secs + setOAuthEnabled: true + setOAuthRefreshTokenExpirationTimeSecs: 14515200 # 24 weeks + setOAuthMaxActiveRefreshTokens: 10 aws: sesEndpoint: http://fake-aws-ses:4569 sqsEndpoint: http://fake-aws-sqs:4568 @@ -121,6 +126,13 @@ brig: -----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEACPvhIdimF20tOPjbb+fXJrwS2RKDp7686T90AZ0+Th8= -----END PUBLIC KEY----- + oauthJwkKeyPair: | + { + "kty": "OKP", + "crv": "Ed25519", + "x": "mhP-NgFw3ifIXGZqJVB0kemt9L3BtD5P8q4Gah4Iklc", + "d": "R8-pV2-sPN7dykV8HFJ73S64F3kMHTNnJiSN8UdWk_o" + } tests: enableFederationTests: true cannon: @@ -227,6 +239,13 @@ nginz: zAuth: # this must match the key in brig! publicKeys: 0UW38se1yeoc5bVNEvf5LyrHWGZkyvcGTVilK2geGdU= + oAuth: + publicKeys: | + { + "kty": "OKP", + "crv": "Ed25519", + "x": "mhP-NgFw3ifIXGZqJVB0kemt9L3BtD5P8q4Gah4Iklc" + } proxy: replicaCount: 1 imagePullPolicy: {{ .Values.imagePullPolicy }} diff --git a/hack/helmfile-single.yaml b/hack/helmfile-single.yaml index 3a770ee146..bb9f4f5b2f 100644 --- a/hack/helmfile-single.yaml +++ b/hack/helmfile-single.yaml @@ -13,7 +13,9 @@ environments: values: - namespace: {{ requiredEnv "NAMESPACE" }} - federationDomain: {{ requiredEnv "FEDERATION_DOMAIN" }} + - ingressChart: {{ requiredEnv "INGRESS_CHART" }} - imagePullPolicy: Always + - redisStorageClass: hcloud-volumes repositories: - name: stable @@ -39,17 +41,17 @@ releases: values: - './helm_vars/redis-cluster/values.yaml.gotmpl' - - name: '{{ .Values.namespace }}-nginx-ingress-controller' + - name: '{{ .Values.namespace }}-ic' namespace: '{{ .Values.namespace }}' - chart: '../.local/charts/nginx-ingress-controller' + chart: '../.local/charts/{{ .Values.ingressChart }}' values: - - './helm_vars/nginx-ingress-controller/values.yaml' + - './helm_vars/{{ .Values.ingressChart }}/values.yaml.gotmpl' - - name: '{{ .Values.namespace }}-nginx-ingress-services' + - name: '{{ .Values.namespace }}-i' namespace: '{{ .Values.namespace }}' chart: '../.local/charts/nginx-ingress-services' values: - - './helm_vars/nginx-ingress-services/values.yaml' + - './helm_vars/nginx-ingress-services/values.yaml.gotmpl' - './helm_vars/nginx-ingress-services/certificates-namespace1.yaml' set: # Federation domain is also the SRV record created by the @@ -57,6 +59,8 @@ releases: # differ, so we don't make any silly assumptions in the code. - name: config.dns.federator value: {{ .Values.federationDomain }} + needs: + - '{{ .Values.namespace }}-ic' # Note that wire-server depends on databases-ephemeral being up; and in some # cases on nginx-ingress also being up. If installing helm charts in a diff --git a/hack/helmfile.yaml b/hack/helmfile.yaml index 6392d64a43..f9e608107e 100644 --- a/hack/helmfile.yaml +++ b/hack/helmfile.yaml @@ -18,6 +18,7 @@ environments: - federationDomain: {{ requiredEnv "FEDERATION_DOMAIN_1" }} - namespaceFed2: {{ requiredEnv "NAMESPACE_2" }} - federationDomainFed2: {{ requiredEnv "FEDERATION_DOMAIN_2" }} + - ingressChart: {{ requiredEnv "INGRESS_CHART" }} - imagePullPolicy: Always - redisStorageClass: hcloud-volumes kind: @@ -26,6 +27,7 @@ environments: - federationDomain: {{ requiredEnv "FEDERATION_DOMAIN_1" }} - namespaceFed2: {{ requiredEnv "NAMESPACE_2" }} - federationDomainFed2: {{ requiredEnv "FEDERATION_DOMAIN_2" }} + - ingressChart: {{ requiredEnv "INGRESS_CHART" }} - imagePullPolicy: Never - redisStorageClass: standard @@ -36,6 +38,9 @@ repositories: - name: bitnami url: 'https://charts.bitnami.com/bitnami' + - name: ingress + url: 'https://kubernetes.github.io/ingress-nginx' + releases: - name: '{{ .Values.namespace }}-fake-aws' namespace: '{{ .Values.namespace }}' @@ -69,23 +74,23 @@ releases: values: - './helm_vars/redis-cluster/values.yaml.gotmpl' - - name: '{{ .Values.namespace }}-nginx-ingress-controller' + - name: '{{ .Values.namespace }}-ic' namespace: '{{ .Values.namespace }}' - chart: '../.local/charts/nginx-ingress-controller' + chart: '../.local/charts/{{ .Values.ingressChart }}' values: - - './helm_vars/nginx-ingress-controller/values.yaml' + - './helm_vars/{{ .Values.ingressChart }}/values.yaml.gotmpl' - - name: '{{ .Values.namespace }}-nginx-ingress-controller-2' + - name: '{{ .Values.namespace }}-ic2' namespace: '{{ .Values.namespaceFed2 }}' - chart: '../.local/charts/nginx-ingress-controller' + chart: '../.local/charts/{{ .Values.ingressChart }}' values: - - './helm_vars/nginx-ingress-controller/values.yaml' + - './helm_vars/{{ .Values.ingressChart }}/values.yaml.gotmpl' - - name: '{{ .Values.namespace }}-nginx-ingress-services' + - name: '{{ .Values.namespace }}-i' namespace: '{{ .Values.namespace }}' chart: '../.local/charts/nginx-ingress-services' values: - - './helm_vars/nginx-ingress-services/values.yaml' + - './helm_vars/nginx-ingress-services/values.yaml.gotmpl' - './helm_vars/nginx-ingress-services/certificates-namespace1.yaml' set: # Federation domain is also the SRV record created by the @@ -93,12 +98,14 @@ releases: # differ, so we don't make any silly assumptions in the code. - name: config.dns.federator value: {{ .Values.federationDomain }} + needs: + - '{{ .Values.namespace }}-ic' - - name: '{{ .Values.namespace }}-nginx-ingress-services-2' + - name: '{{ .Values.namespace }}-i2' namespace: '{{ .Values.namespaceFed2 }}' chart: '../.local/charts/nginx-ingress-services' values: - - './helm_vars/nginx-ingress-services/values.yaml' + - './helm_vars/nginx-ingress-services/values.yaml.gotmpl' - './helm_vars/nginx-ingress-services/certificates-namespace2.yaml' set: # Federation domain is also the SRV record created by the @@ -106,9 +113,9 @@ releases: # differ, so we don't make any silly assumptions in the code. - name: config.dns.federator value: {{ .Values.federationDomainFed2 }} + needs: + - '{{ .Values.namespace }}-ic2' - #--------------------------------------------- - # # Note that wire-server depends on databases-ephemeral being up; and in some # cases on nginx-ingress also being up. If installing helm charts in a # parallel way, it's expected to see some wire-server pods (namely the diff --git a/libs/api-bot/api-bot.cabal b/libs/api-bot/api-bot.cabal index 75b936e1d4..1efc9e2109 100644 --- a/libs/api-bot/api-bot.cabal +++ b/libs/api-bot/api-bot.cabal @@ -41,6 +41,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -52,6 +53,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/libs/api-bot/src/Network/Wire/Bot/Cache.hs b/libs/api-bot/src/Network/Wire/Bot/Cache.hs index 5ef86e566b..9f7f067775 100644 --- a/libs/api-bot/src/Network/Wire/Bot/Cache.hs +++ b/libs/api-bot/src/Network/Wire/Bot/Cache.hs @@ -45,7 +45,7 @@ import System.Random.MWC.Distributions (uniformShuffle) newtype Cache = Cache {cache :: IORef [CachedUser]} -data CachedUser = CachedUser !PlainTextPassword !User +data CachedUser = CachedUser !PlainTextPassword6 !User -- | Load users out of a file in the following format: -- @@ -77,7 +77,7 @@ put c a = liftIO . atomicModifyIORef (cache c) $ \u -> (a : u, ()) toUser :: HasCallStack => Logger -> Domain -> [CachedUser] -> [LText] -> IO [CachedUser] toUser _ domain acc [i, e, p] = do - let pw = PlainTextPassword . Text.toStrict $ Text.strip p + let pw = plainTextPassword6Unsafe . Text.toStrict $ Text.strip p let iu = error "Cache.toUser: invalid user" let ie = error "Cache.toUser: invalid email" let ui = fromMaybe iu . fromByteString . encodeUtf8 . Text.toStrict . Text.strip $ i diff --git a/libs/api-bot/src/Network/Wire/Bot/Monad.hs b/libs/api-bot/src/Network/Wire/Bot/Monad.hs index bef187e2db..efe7e9719b 100644 --- a/libs/api-bot/src/Network/Wire/Bot/Monad.hs +++ b/libs/api-bot/src/Network/Wire/Bot/Monad.hs @@ -104,7 +104,7 @@ import qualified Data.HashMap.Strict as HashMap import Data.Id import Data.Metrics (Metrics) import qualified Data.Metrics as Metrics -import Data.Misc (PlainTextPassword (..)) +import Data.Misc import Data.Qualified (Local, toLocalUnsafe) import Data.Text (pack, unpack) import Data.Time.Clock @@ -345,7 +345,7 @@ data Bot = Bot botMetrics :: BotMetrics, -- END TODO botClients :: TVar [BotClient], -- TODO: IORef? - botPassphrase :: PlainTextPassword + botPassphrase :: PlainTextPassword6 } instance Show Bot where @@ -452,7 +452,7 @@ newBot tag = liftBotNet $ do keys <- liftIO $ awaitActivationMail mbox folders sndr email log Info $ botLogFields (userId user) tag . msg (val "Activate user") forM_ keys (uncurry activateKey >=> flip assertTrue "Activation failed.") - bot <- mkBot tag user pw + bot <- mkBot tag user (plainTextPassword8To6 pw) -- TODO: addBotClient? incrBotsCreatedNew pure bot @@ -689,7 +689,7 @@ try ma = do ------------------------------------------------------------------------------- -- Internal Bot Lifecycle -mkBot :: BotTag -> User -> PlainTextPassword -> BotNet Bot +mkBot :: BotTag -> User -> PlainTextPassword6 -> BotNet Bot mkBot tag user pw = do log Info $ botLogFields (userId user) tag . msg (val "Login") let ident = fromMaybe (error "No email") (userEmail user) @@ -978,12 +978,12 @@ botLogFields u t = field "Bot" (show u) . field "Tag" (unTag t) ------------------------------------------------------------------------------- -- Randomness -randUser :: Email -> BotTag -> IO (NewUser, PlainTextPassword) +randUser :: Email -> BotTag -> IO (NewUser, PlainTextPassword8) randUser (Email loc dom) (BotTag tag) = do uuid <- nextRandom pwdUuid <- nextRandom let email = Email (loc <> "+" <> tag <> "-" <> pack (toString uuid)) dom - let passw = PlainTextPassword (pack (toString pwdUuid)) + let passw = plainTextPassword8Unsafe (pack (toString pwdUuid)) pure ( NewUser { newUserDisplayName = Name (tag <> "-Wirebot-" <> pack (toString uuid)), diff --git a/libs/api-client/api-client.cabal b/libs/api-client/api-client.cabal index ab3277d7ae..4b4d9599d3 100644 --- a/libs/api-client/api-client.cabal +++ b/libs/api-client/api-client.cabal @@ -40,6 +40,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -51,6 +52,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/libs/bilge/bilge.cabal b/libs/bilge/bilge.cabal index 931b4f55fc..4ba0f565d1 100644 --- a/libs/bilge/bilge.cabal +++ b/libs/bilge/bilge.cabal @@ -41,6 +41,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -52,6 +53,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/libs/brig-types/brig-types.cabal b/libs/brig-types/brig-types.cabal index 90839e99cb..3f6204b454 100644 --- a/libs/brig-types/brig-types.cabal +++ b/libs/brig-types/brig-types.cabal @@ -40,6 +40,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -51,6 +52,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -121,6 +123,7 @@ test-suite brig-types-tests DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -132,6 +135,7 @@ test-suite brig-types-tests MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/libs/brig-types/src/Brig/Types/User/Auth.hs b/libs/brig-types/src/Brig/Types/User/Auth.hs index 2ca14dca39..d6426a1483 100644 --- a/libs/brig-types/src/Brig/Types/User/Auth.hs +++ b/libs/brig-types/src/Brig/Types/User/Auth.hs @@ -25,7 +25,7 @@ where import Data.Aeson import Data.Id (UserId) -import Data.Misc (PlainTextPassword (..)) +import Data.Misc (PlainTextPassword6) import Imports import Wire.API.User.Auth @@ -37,7 +37,7 @@ data SsoLogin -- This kind of login returns restricted 'LegalHoldUserToken's instead of regular -- tokens. data LegalHoldLogin - = LegalHoldLogin !UserId !(Maybe PlainTextPassword) !(Maybe CookieLabel) + = LegalHoldLogin !UserId !(Maybe PlainTextPassword6) !(Maybe CookieLabel) instance FromJSON SsoLogin where parseJSON = withObject "SsoLogin" $ \o -> diff --git a/libs/cargohold-types/cargohold-types.cabal b/libs/cargohold-types/cargohold-types.cabal index 02f6dc2473..8b9786007a 100644 --- a/libs/cargohold-types/cargohold-types.cabal +++ b/libs/cargohold-types/cargohold-types.cabal @@ -30,6 +30,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -41,6 +42,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/libs/cassandra-util/cassandra-util.cabal b/libs/cassandra-util/cassandra-util.cabal index a7ca1e9234..c9ada6d0ce 100644 --- a/libs/cassandra-util/cassandra-util.cabal +++ b/libs/cassandra-util/cassandra-util.cabal @@ -34,6 +34,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -45,6 +46,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/libs/deriving-swagger2/deriving-swagger2.cabal b/libs/deriving-swagger2/deriving-swagger2.cabal index c05b0dcd5c..fefd2ed737 100644 --- a/libs/deriving-swagger2/deriving-swagger2.cabal +++ b/libs/deriving-swagger2/deriving-swagger2.cabal @@ -27,6 +27,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -38,6 +39,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/libs/dns-util/dns-util.cabal b/libs/dns-util/dns-util.cabal index e4c2629464..f0a88e1d77 100644 --- a/libs/dns-util/dns-util.cabal +++ b/libs/dns-util/dns-util.cabal @@ -32,6 +32,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -43,6 +44,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -95,6 +97,7 @@ test-suite spec DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -106,6 +109,7 @@ test-suite spec MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/libs/extended/extended.cabal b/libs/extended/extended.cabal index da2004dc1f..7a01bc3b94 100644 --- a/libs/extended/extended.cabal +++ b/libs/extended/extended.cabal @@ -38,6 +38,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -49,6 +50,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -114,6 +116,7 @@ test-suite extended-tests DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -125,6 +128,7 @@ test-suite extended-tests MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/libs/galley-types/galley-types.cabal b/libs/galley-types/galley-types.cabal index 005902f1d9..a7e9788986 100644 --- a/libs/galley-types/galley-types.cabal +++ b/libs/galley-types/galley-types.cabal @@ -35,6 +35,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -46,6 +47,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -115,6 +117,7 @@ test-suite galley-types-tests DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -126,6 +129,7 @@ test-suite galley-types-tests MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/libs/gundeck-types/gundeck-types.cabal b/libs/gundeck-types/gundeck-types.cabal index 65c12a9a7b..d3ef80d701 100644 --- a/libs/gundeck-types/gundeck-types.cabal +++ b/libs/gundeck-types/gundeck-types.cabal @@ -34,6 +34,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -45,6 +46,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/libs/imports/imports.cabal b/libs/imports/imports.cabal index 27af805297..610a694857 100644 --- a/libs/imports/imports.cabal +++ b/libs/imports/imports.cabal @@ -34,6 +34,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -45,6 +46,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/libs/jwt-tools/jwt-tools.cabal b/libs/jwt-tools/jwt-tools.cabal index 74156013d3..e719a2b85e 100644 --- a/libs/jwt-tools/jwt-tools.cabal +++ b/libs/jwt-tools/jwt-tools.cabal @@ -28,6 +28,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -39,6 +40,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -118,6 +120,7 @@ test-suite jwt-tools-tests DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -129,6 +132,7 @@ test-suite jwt-tools-tests MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/libs/jwt-tools/src/Data/Jwt/Tools.hs b/libs/jwt-tools/src/Data/Jwt/Tools.hs index 23360373fc..b858c38f3d 100644 --- a/libs/jwt-tools/src/Data/Jwt/Tools.hs +++ b/libs/jwt-tools/src/Data/Jwt/Tools.hs @@ -40,10 +40,8 @@ import Control.Exception import Control.Monad.Trans.Except import Data.ByteString.Conversion import Data.String.Conversions (cs) -import Foreign.C (CUChar (..)) import Foreign.C.String (CString, newCString, peekCString) import Foreign.Ptr (Ptr, nullPtr) -import Foreign.Storable (peek) import Imports import Network.HTTP.Types (StdMethod (..)) @@ -88,7 +86,7 @@ foreign import ccall unsafe "generate_dpop_access_token" foreign import ccall unsafe "free_dpop_access_token" free_dpop_access_token :: Ptr HsResult -> IO () -foreign import ccall unsafe "get_error" get_error :: Ptr HsResult -> Ptr CUChar +foreign import ccall unsafe "get_error" get_error :: Ptr HsResult -> Word8 foreign import ccall unsafe "get_token" get_token :: Ptr HsResult -> CString @@ -113,9 +111,9 @@ generateDpopAccessTokenFfi dpopProof user client domain nonce uri method maxSkew getErrorFfi :: Ptr HsResult -> IO (Maybe Word8) getErrorFfi ptr = do - let errorPtr = get_error ptr - if errorPtr /= nullPtr - then Just . fromIntegral <$> peek errorPtr + let err = get_error ptr + if err /= 0 + then pure $ Just err else pure Nothing getTokenFfi :: Ptr HsResult -> IO (Maybe String) diff --git a/libs/jwt-tools/test/Spec.hs b/libs/jwt-tools/test/Spec.hs index d39cb0b3e9..741c463ffb 100644 --- a/libs/jwt-tools/test/Spec.hs +++ b/libs/jwt-tools/test/Spec.hs @@ -25,12 +25,13 @@ main :: IO () main = hspec $ do describe "generateDpopToken FFI when passing valid inputs" $ do it "should return an access token" $ do - actual <- callFFIWithValidValuesValidUntil2038 + actual <- runExceptT $ generateDpopToken proof uid cid domain nonce uri method maxSkewSecs expires now pem + print actual isRight actual `shouldBe` True - describe "generateDpopToken FFI when passing nonsense values" $ do - it "should return an error" $ do - actual <- callFFIWithNonsenseValues - isRight actual `shouldBe` False + describe "generateDpopToken FFI when passing a wrong nonce value" $ do + it "should return BackendNonceMismatchError" $ do + actual <- runExceptT $ generateDpopToken proof uid cid domain (Nonce "foobar") uri method maxSkewSecs expires now pem + actual `shouldBe` Left BackendNonceMismatchError describe "toResult" $ do it "should convert to correct error" $ do toResult Nothing (Just token) `shouldBe` Right (cs token) @@ -72,42 +73,13 @@ main = hspec $ do toResult (Just 18) (Just token) `shouldBe` Left ExpError toResult Nothing Nothing `shouldBe` Left UnknownError where - token :: String - token = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" - -callFFIWithNonsenseValues :: IO (Either DPoPTokenGenerationError ByteString) -callFFIWithNonsenseValues = - runExceptT $ generateDpopToken proof uid cid domain nonce uri method maxSkewSecs expires now pem - where - proof = Proof "xxxx.yyyy.zzzz" - uid = UserId "8a6e8a6e-8a6e-8a6e-8a6e-8a6e8a6e8a6e" - cid = ClientId 8899 - domain = Domain "example.com" - nonce = Nonce "123" - uri = Uri "/foo" - method = POST - maxSkewSecs = MaxSkewSecs 1 - now = NowEpoch 5435234232 - expires = ExpiryEpoch $ 5435234232 + 360 - pem = - PemBundle $ - "-----BEGIN PRIVATE KEY-----\n\ - \MC4CAQAwBQYDK2VwBCIEIFANnxZLNE4p+GDzWzR3wm/v8x/0bxZYkCyke1aTRucX\n\ - \-----END PRIVATE KEY-----\n\ - \-----BEGIN PUBLIC KEY-----\n\ - \MCowBQYDK2VwAyEACPvhIdimF20tOPjbb+fXJrwS2RKDp7686T90AZ0+Th8=\n\ - \-----END PUBLIC KEY-----\n" - -callFFIWithValidValuesValidUntil2038 :: IO (Either DPoPTokenGenerationError ByteString) -callFFIWithValidValuesValidUntil2038 = - runExceptT $ generateDpopToken proof uid cid domain nonce uri method maxSkewSecs expires now pem - where - proof = Proof "eyJhbGciOiJFZERTQSIsInR5cCI6ImRwb3Arand0IiwiandrIjp7Imt0eSI6Ik9LUCIsImNydiI6IkVkMjU1MTkiLCJ4IjoiZ0tYSHpIV3QtRUh1N2ZQbmlWMXFXWGV2Rmk1eFNKd3RNcHJlSjBjdTZ3SSJ9fQ.eyJpYXQiOjE2NzgxMDcwMDksImV4cCI6MjA4ODA3NTAwOSwibmJmIjoxNjc4MTA3MDA5LCJzdWIiOiJpbXBwOndpcmVhcHA9WXpWbE1qRTVNelpqTTJKak5EQXdOMkpsWTJJd1lXTm1OVGszTW1FMVlqTS9lYWZhMDI1NzMwM2Q0MDYwQHdpcmUuY29tIiwianRpIjoiMmQzNzAzYTItNTc4Yi00MmRjLWE2MGUtYmM0NzA3OWVkODk5Iiwibm9uY2UiOiJRV1J4T1VaUVpYVnNTMlJZYjBGS05sWkhXbGgwYUV4amJUUmpTM2M1U2xnIiwiaHRtIjoiUE9TVCIsImh0dSI6Imh0dHBzOi8vd2lyZS5leGFtcGxlLmNvbS9jbGllbnRzLzE2OTMxODQ4MzIyNTQ3NTMxODcyL2FjY2Vzcy10b2tlbiIsImNoYWwiOiJZVE5HTkRSNlRqZHFabGRRZUVGYWVrMTZWMmhqYXpCVmJ6UlFWVXRWUlZJIn0.0J2sx5y0ubZ4NwmQhbKXDj6i5UWTx3cvuTPKbeXXOJFDamr-iFtE6sOnAQT90kfTx1cEoIyDfoUkj3h5GEanAA" - uid = UserId "c5e21936-c3bc-4007-becb-0acf5972a5b3" - cid = ClientId 16931848322547531872 + token = "" + proof = Proof "eyJhbGciOiJFZERTQSIsInR5cCI6ImRwb3Arand0IiwiandrIjp7Imt0eSI6Ik9LUCIsImNydiI6IkVkMjU1MTkiLCJ4IjoidUhNR0paWllUbU9zOEdiaTdaRUJLT255TnJYYnJzNTI1dE1QQUZoYjBzbyJ9fQ.eyJpYXQiOjE2Nzg4MDUyNTgsImV4cCI6MjA4ODc3MzI1OCwibmJmIjoxNjc4ODA1MjU4LCJzdWIiOiJpbTp3aXJlYXBwPVpHSmlNRGRsT1RRM1pESTVOREU0TUdFM09UQmhOVGN6WkdWbU16VmtaRFUvN2M2MzExYTFjNDNjMmJhNkB3aXJlLmNvbSIsImp0aSI6ImQyOWFkYTQ2LTBjMzYtNGNiMS05OTVlLWFlMWNiYTY5M2IzNCIsIm5vbmNlIjoiYzB0RWNtOUNUME00TXpKU04zRjRkMEZIV0V4TGIxUm5aMDQ1U3psSFduTSIsImh0bSI6IlBPU1QiLCJodHUiOiJodHRwczovL3dpcmUuZXhhbXBsZS5jb20vY2xpZW50cy84OTYzMDI3MDY5ODc3MTAzNTI2L2FjY2Vzcy10b2tlbiIsImNoYWwiOiJaa3hVV25GWU1HbHFUVVpVU1hnNFdHdHBOa3h1WWpWU09XRnlVRU5hVGxnIn0.8p0lvdOPjJ8ogjjLP6QtOo216qD9ujP7y9vSOhdYb-O8ikmW09N00gjCf0iGT-ZkxBT-LfDE3eQx27tWQ3JPBQ" + uid = UserId "dbb07e94-7d29-4180-a790-a573def35dd5" + cid = ClientId 8963027069877103526 domain = Domain "wire.com" - nonce = Nonce "QWRxOUZQZXVsS2RYb0FKNlZHWlh0aExjbTRjS3c5Slg" - uri = Uri "https://wire.example.com/clients/16931848322547531872/access-token" + nonce = Nonce "c0tEcm9CT0M4MzJSN3F4d0FHWExLb1RnZ045SzlHWnM" + uri = Uri "https://wire.example.com/clients/8963027069877103526/access-token" method = POST maxSkewSecs = MaxSkewSecs 5 now = NowEpoch 5435234232 @@ -115,8 +87,8 @@ callFFIWithValidValuesValidUntil2038 = pem = PemBundle $ "-----BEGIN PRIVATE KEY-----\n\ - \MC4CAQAwBQYDK2VwBCIEIMROyHqEinw8EvFSNXp0X0suu6gMQvd9i/l9v9R9UnhH\n\ + \MC4CAQAwBQYDK2VwBCIEIMkvahkqR9sHJSmFeCl3B7aJjsQGgwy++cccWTbuDyy+\n\ \-----END PRIVATE KEY-----\n\ \-----BEGIN PUBLIC KEY-----\n\ - \MCowBQYDK2VwAyEA5pDR/Yo4pkKUIxIody2fEQ56eIOW7UqeDeF7FG7WudA=\n\ + \MCowBQYDK2VwAyEAdYI38UdxksC0K4Qx6E9JK9YfGm+ehnY18oKmHL2YsZk=\n\ \-----END PUBLIC KEY-----\n" diff --git a/libs/libzauth/libzauth-c/Cargo.lock b/libs/libzauth/libzauth-c/Cargo.lock index 7acceb85ae..1b894e9bb6 100644 --- a/libs/libzauth/libzauth-c/Cargo.lock +++ b/libs/libzauth/libzauth-c/Cargo.lock @@ -11,25 +11,339 @@ dependencies = [ "memchr", ] +[[package]] +name = "anyhow" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" + [[package]] name = "asexp" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e368761ce758947307f1c2db1f46077b1aabb5af7f268b6cededd1b52802652" +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + +[[package]] +name = "base64ct" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" + +[[package]] +name = "binstring" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e0d60973d9320722cb1206f412740e162a33b8547ea8d6be75d7cff237c7a85" + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "cc" version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "coarsetime" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "454038500439e141804c655b4cd1bc6a70bcb95cd2bc9463af5661b6956f0e46" +dependencies = [ + "libc", + "once_cell", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "const-oid" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-bigint" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ct-codecs" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b7eb4404b8195a9abb6356f4ac07d8ba267045c8d6d220ac4dc992e6cc75df" + +[[package]] +name = "der" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "ecdsa" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12844141594ad74185a926d030f3b605f6a903b4e3fec351f3ea338ac5b7637e" +dependencies = [ + "der", + "elliptic-curve", + "rfc6979", + "signature 2.0.0", +] + [[package]] name = "ed25519" -version = "1.4.1" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +dependencies = [ + "signature 1.6.4", +] + +[[package]] +name = "ed25519-compact" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3d382e8464107391c8706b4c14b087808ecb909f6c15c34114bc42e53a9e4c" +dependencies = [ + "ct-codecs", + "getrandom", +] + +[[package]] +name = "elliptic-curve" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +dependencies = [ + "base16ct", + "crypto-bigint", + "der", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "hmac-sha1-compact" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e2440a0078e20c3b68ca01234cea4219f23e64b0c0bdb1200c5550d54239bb" + +[[package]] +name = "hmac-sha256" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d5c4b5e5959dc2c2b89918d8e2cc40fcdd623cef026ed09d2f0ee05199dc8e4" +checksum = "fc736091aacb31ddaa4cd5f6988b3c21e99913ac846b41f32538c5fae5d71bfe" dependencies = [ - "signature", + "digest", +] + +[[package]] +name = "hmac-sha512" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520c9c3f6040661669bc5c91e551b605a520c8e0a63a766a91a65adef734d151" +dependencies = [ + "digest", +] + +[[package]] +name = "itoa" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + +[[package]] +name = "jwt-simple" +version = "0.11.3" +source = "git+https://github.com/wireapp/rust-jwt-simple?rev=15a69f82288d68b74a75c1364e5d4bf681f1c07b#15a69f82288d68b74a75c1364e5d4bf681f1c07b" +dependencies = [ + "anyhow", + "binstring", + "coarsetime", + "ct-codecs", + "ed25519-compact", + "hmac-sha1-compact", + "hmac-sha256", + "hmac-sha512", + "k256", + "p256", + "p384", + "rand", + "rsa", + "serde", + "serde_json", + "spki", + "thiserror", + "zeroize", +] + +[[package]] +name = "k256" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92a55e0ff3b72c262bcf041d9e97f1b84492b68f1c1a384de2323d3dc9403397" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature 2.0.0", ] [[package]] @@ -37,6 +351,9 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] [[package]] name = "libc" @@ -44,6 +361,12 @@ version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" +[[package]] +name = "libm" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" + [[package]] name = "libsodium-sys" version = "0.2.7" @@ -56,18 +379,199 @@ dependencies = [ "walkdir", ] +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "num-bigint-dig" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2399c9463abc5f909349d8aa9ba080e0b88b3ce2885389b60b993f39b1a56905" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "p256" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49c124b3cbce43bcbac68c58ec181d98ed6cc7e6d0aa7c3ba97b2563410b0e55" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630a4a9b2618348ececfae61a4905f564b817063bf2d66cdfc2ced523fe1d2d4" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "pem-rfc7468" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d159833a9105500e0398934e205e0773f0b27529557134ecfc51c27646adac" +dependencies = [ + "base64ct", +] + +[[package]] +name = "pkcs1" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff33bdbdfc54cc98a2eca766ebdec3e1b8fb7387523d5c9c9a2891da856f719" +dependencies = [ + "der", + "pkcs8", + "spki", + "zeroize", +] + +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "primeorder" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b54f7131b3dba65a2f414cf5bd25b66d4682e4608610668eae785750ba4c5b2" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro2" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "regex" version = "1.6.0" @@ -85,12 +589,50 @@ version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +[[package]] +name = "rfc6979" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +dependencies = [ + "crypto-bigint", + "hmac", + "zeroize", +] + +[[package]] +name = "rsa" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "094052d5470cbcef561cb848a7209968c9f12dfa6d668f4bca048ac5de51099c" +dependencies = [ + "byteorder", + "digest", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature 1.6.4", + "smallvec", + "subtle", + "zeroize", +] + [[package]] name = "rustc-serialize" version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" +[[package]] +name = "ryu" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + [[package]] name = "same-file" version = "1.0.6" @@ -100,17 +642,87 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "sec1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "serde" -version = "1.0.137" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signature" +version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +dependencies = [ + "digest", + "rand_core", +] [[package]] name = "signature" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4" +checksum = "8fe458c98333f9c8152221191a77e2a44e8325d0193484af2e9421a53019e57d" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "sodiumoxide" @@ -124,6 +736,77 @@ dependencies = [ "serde", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "walkdir" version = "2.3.2" @@ -135,6 +818,66 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + [[package]] name = "winapi" version = "0.3.9" @@ -171,16 +914,28 @@ name = "zauth" version = "3.1.0" dependencies = [ "asexp", + "base64", + "jwt-simple", "lazy_static", "regex", "rustc-serialize", + "serde", + "serde_json", "sodiumoxide", + "thiserror", ] [[package]] name = "zauth-c" version = "3.0.0" dependencies = [ + "jwt-simple", "libc", "zauth", ] + +[[package]] +name = "zeroize" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" diff --git a/libs/libzauth/libzauth-c/Cargo.toml b/libs/libzauth/libzauth-c/Cargo.toml index 73ef35cc2f..25506f5ea5 100644 --- a/libs/libzauth/libzauth-c/Cargo.toml +++ b/libs/libzauth/libzauth-c/Cargo.toml @@ -13,3 +13,8 @@ libc = ">= 0.2" [dependencies.zauth] path = "../libzauth" + +# fork of jwt-simple which supports JWK handling +[dependencies.jwt-simple] +git = "https://github.com/wireapp/rust-jwt-simple" +rev = "15a69f82288d68b74a75c1364e5d4bf681f1c07b" diff --git a/libs/libzauth/libzauth-c/src/lib.rs b/libs/libzauth/libzauth-c/src/lib.rs index de419b7f77..84dbad3da5 100644 --- a/libs/libzauth/libzauth-c/src/lib.rs +++ b/libs/libzauth/libzauth-c/src/lib.rs @@ -20,31 +20,34 @@ extern crate zauth; use libc::size_t; use std::char; +use std::ffi::{CString, NulError}; use std::fs::File; use std::io::{self, BufReader, Read}; +use std::panic::{self, UnwindSafe}; use std::path::Path; use std::ptr; use std::slice; use std::str; -use std::panic::{self, UnwindSafe}; -use zauth::{Acl, Error, Keystore, Token, TokenType}; use zauth::acl; +use zauth::{ + verify_oauth_token, Acl, Error, Keystore, OauthError, Token, TokenType, TokenVerification, +}; /// Variant of std::try! that returns the unwrapped error. macro_rules! try_unwrap { ($expr:expr) => { match $expr { - Ok(x) => x, - Err(e) => return From::from(e) + Ok(x) => x, + Err(e) => return From::from(e), } - } + }; } #[repr(C)] #[derive(Clone, Copy, Debug)] pub struct Range { ptr: *const u8, - len: size_t + len: size_t, } pub struct ZauthAcl(zauth::Acl); @@ -52,58 +55,70 @@ pub struct ZauthKeystore(zauth::Keystore); pub struct ZauthToken(zauth::Token<'static>); #[no_mangle] -pub extern fn zauth_keystore_open(f: *const u8, n: size_t, s: *mut *mut ZauthKeystore) -> ZauthResult { +pub extern "C" fn zauth_keystore_open( + f: *const u8, + n: size_t, + s: *mut *mut ZauthKeystore, +) -> ZauthResult { if f.is_null() { return ZauthResult::NullArg; } catch_unwind(|| { let bytes = unsafe { slice::from_raw_parts(f, n) }; - let path = try_unwrap!(str::from_utf8(bytes)); + let path = try_unwrap!(str::from_utf8(bytes)); let store = try_unwrap!(Keystore::open(&Path::new(path))); unsafe { - *s= Box::into_raw(Box::new(ZauthKeystore(store))); + *s = Box::into_raw(Box::new(ZauthKeystore(store))); } ZauthResult::Ok }) } #[no_mangle] -pub extern fn zauth_keystore_delete(s: *mut ZauthKeystore) { +pub extern "C" fn zauth_keystore_delete(s: *mut ZauthKeystore) { catch_unwind(|| { - unsafe { Box::from_raw(s); } + unsafe { + drop(Box::from_raw(s)); + } ZauthResult::Ok }); } #[no_mangle] -pub extern fn zauth_acl_open(f: *const u8, n: size_t, a: *mut *mut ZauthAcl) -> ZauthResult { +pub extern "C" fn zauth_acl_open(f: *const u8, n: size_t, a: *mut *mut ZauthAcl) -> ZauthResult { if f.is_null() { return ZauthResult::NullArg; } catch_unwind(|| { let bytes = unsafe { slice::from_raw_parts(f, n) }; - let path = try_unwrap!(str::from_utf8(bytes)); + let path = try_unwrap!(str::from_utf8(bytes)); let mut rdr = BufReader::new(try_unwrap!(File::open(&Path::new(path)))); let mut txt = String::new(); try_unwrap!(rdr.read_to_string(&mut txt)); let acl = try_unwrap!(Acl::from_str(&txt)); unsafe { - *a= Box::into_raw(Box::new(ZauthAcl(acl))); + *a = Box::into_raw(Box::new(ZauthAcl(acl))); } ZauthResult::Ok }) } #[no_mangle] -pub extern fn zauth_acl_delete(a: *mut ZauthAcl) { +pub extern "C" fn zauth_acl_delete(a: *mut ZauthAcl) { catch_unwind(|| { - unsafe { Box::from_raw(a); } + unsafe { + drop(Box::from_raw(a)); + } ZauthResult::Ok }); } #[no_mangle] -pub extern fn zauth_token_parse(cs: *const u8, n: size_t, zt: *mut *mut ZauthToken) -> ZauthResult { +pub extern "C" fn zauth_token_parse( + cs: *const u8, + n: size_t, + zt: *mut *mut ZauthToken, +) -> ZauthResult { if cs.is_null() { return ZauthResult::NullArg; } @@ -119,16 +134,26 @@ pub extern fn zauth_token_parse(cs: *const u8, n: size_t, zt: *mut *mut ZauthTok } #[no_mangle] -pub extern fn zauth_token_verify(t: &ZauthToken, s: &ZauthKeystore) -> ZauthResult { - catch_unwind(|| { +pub extern "C" fn zauth_token_verify(t: &mut ZauthToken, s: &ZauthKeystore) -> ZauthResult { + let result = catch_unwind(|| { try_unwrap!(t.0.verify(&s.0)); ZauthResult::Ok - }) + }); + match result { + ZauthResult::Ok => t.0.verification = TokenVerification::Verified, + _ => t.0.verification = TokenVerification::Invalid, + }; + result } #[no_mangle] -pub extern -fn zauth_token_allowed(t: &ZauthToken, acl: &ZauthAcl, cp: *const u8, n: size_t, out: *mut u8) -> ZauthResult { +pub extern "C" fn zauth_token_allowed( + t: &ZauthToken, + acl: &ZauthAcl, + cp: *const u8, + n: size_t, + out: *mut u8, +) -> ZauthResult { catch_unwind(|| { let b = unsafe { slice::from_raw_parts(cp, n) }; let s = try_unwrap!(str::from_utf8(b)); @@ -142,10 +167,15 @@ fn zauth_token_allowed(t: &ZauthToken, acl: &ZauthAcl, cp: *const u8, n: size_t, } #[no_mangle] -pub extern fn zauth_token_type(t: &ZauthToken) -> ZauthTokenType { +pub extern "C" fn zauth_token_type(t: &ZauthToken) -> ZauthTokenType { From::from(t.0.token_type) } +#[no_mangle] +pub extern "C" fn zauth_token_verification(t: &ZauthToken) -> ZauthTokenVerification { + From::from(t.0.verification) +} + // Commented out, looks unused, and causing portability issues with ia32. //#[no_mangle] //pub extern fn zauth_token_time(t: &ZauthToken) -> c_long { @@ -153,24 +183,32 @@ pub extern fn zauth_token_type(t: &ZauthToken) -> ZauthTokenType { //} #[no_mangle] -pub extern fn zauth_token_version(t: &ZauthToken) -> u8 { +pub extern "C" fn zauth_token_version(t: &ZauthToken) -> u8 { t.0.version } #[no_mangle] -pub extern fn zauth_token_lookup(t: &ZauthToken, c: u8) -> Range { +pub extern "C" fn zauth_token_lookup(t: &ZauthToken, c: u8) -> Range { if let Some(k) = char::from_u32(c as u32) { if let Some(s) = t.0.lookup(k) { - return Range { ptr: s.as_ptr(), len: s.len() } + return Range { + ptr: s.as_ptr(), + len: s.len(), + }; } } - Range { ptr: ptr::null(), len: 0 } + Range { + ptr: ptr::null(), + len: 0, + } } #[no_mangle] -pub extern fn zauth_token_delete(t: *mut ZauthToken) { +pub extern "C" fn zauth_token_delete(t: *mut ZauthToken) { catch_unwind(|| { - unsafe { Box::from_raw(t); } + unsafe { + drop(Box::from_raw(t)); + } ZauthResult::Ok }); } @@ -178,25 +216,25 @@ pub extern fn zauth_token_delete(t: *mut ZauthToken) { #[repr(C)] #[derive(Clone, Copy, Debug)] pub enum ZauthTokenType { - User = 0, - Bot = 1, - Access = 2, - Provider = 4, - LegalHoldUser = 5, + User = 0, + Bot = 1, + Access = 2, + Provider = 4, + LegalHoldUser = 5, LegalHoldAccess = 6, - Unknown = 3 + Unknown = 3, } impl From for ZauthTokenType { fn from(t: TokenType) -> ZauthTokenType { match t { - TokenType::User => ZauthTokenType::User, - TokenType::Access => ZauthTokenType::Access, - TokenType::Bot => ZauthTokenType::Bot, - TokenType::Provider => ZauthTokenType::Provider, - TokenType::LegalHoldUser => ZauthTokenType::LegalHoldUser, - TokenType::LegalHoldAccess => ZauthTokenType::LegalHoldAccess, - TokenType::Unknown => ZauthTokenType::Unknown + TokenType::User => ZauthTokenType::User, + TokenType::Access => ZauthTokenType::Access, + TokenType::Bot => ZauthTokenType::Bot, + TokenType::Provider => ZauthTokenType::Provider, + TokenType::LegalHoldUser => ZauthTokenType::LegalHoldUser, + TokenType::LegalHoldAccess => ZauthTokenType::LegalHoldAccess, + TokenType::Unknown => ZauthTokenType::Unknown, } } } @@ -204,32 +242,32 @@ impl From for ZauthTokenType { #[repr(C)] #[derive(Clone, Copy, Debug)] pub enum ZauthResult { - Ok = 0, - Base64Error = 1, - Expired = 2, - InvalidAttr = 3, - IoError = 4, - MissingAttr = 5, - NullArg = 6, - ParseError = 7, + Ok = 0, + Base64Error = 1, + Expired = 2, + InvalidAttr = 3, + IoError = 4, + MissingAttr = 5, + NullArg = 6, + ParseError = 7, SignatureMismatch = 8, - UnknownKey = 9, - Utf8Error = 10, - AclError = 11, - Panic = 99 + UnknownKey = 9, + Utf8Error = 10, + AclError = 11, + Panic = 99, } impl From for ZauthResult { fn from(e: zauth::Error) -> ZauthResult { match e { - Error::Base64 => ZauthResult::Base64Error, - Error::Expired => ZauthResult::Expired, - Error::Invalid(_) => ZauthResult::InvalidAttr, - Error::Io(_) => ZauthResult::IoError, - Error::Missing(_) => ZauthResult::MissingAttr, - Error::Parse => ZauthResult::ParseError, + Error::Base64 => ZauthResult::Base64Error, + Error::Expired => ZauthResult::Expired, + Error::Invalid(_) => ZauthResult::InvalidAttr, + Error::Io(_) => ZauthResult::IoError, + Error::Missing(_) => ZauthResult::MissingAttr, + Error::Parse => ZauthResult::ParseError, Error::SignatureMismatch => ZauthResult::SignatureMismatch, - Error::UnknownKey(_) => ZauthResult::UnknownKey, + Error::UnknownKey(_) => ZauthResult::UnknownKey, } } } @@ -253,9 +291,218 @@ impl From for ZauthResult { } fn catch_unwind(f: F) -> ZauthResult - where F: FnOnce() -> ZauthResult + UnwindSafe { +where + F: FnOnce() -> ZauthResult + UnwindSafe, +{ + match panic::catch_unwind(f) { + Ok(x) => x, + Err(_) => ZauthResult::Panic, + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub enum ZauthTokenVerification { + Verified = 0, + Invalid = 1, + Pending = 2, +} + +impl From for ZauthTokenVerification { + fn from(t: TokenVerification) -> ZauthTokenVerification { + match t { + TokenVerification::Verified => ZauthTokenVerification::Verified, + TokenVerification::Invalid => ZauthTokenVerification::Invalid, + TokenVerification::Pending => ZauthTokenVerification::Pending, + } + } +} + +// ------------------------------------------------------------------------ +// OAuth + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub enum OAuthResultStatus { + Ok = 0, + InsufficientScope = 1, + NullArg = 2, + IoError = 3, + Utf8Error = 4, + Panic = 99, +} + +pub struct OAuthPubJwk(String); + +#[repr(C)] +#[derive(Clone, Debug)] +pub struct OAuthResult { + uid: *const libc::c_char, + status: OAuthResultStatus, +} + +fn catch_unwind_with(f: F, r: R) -> R +where + F: FnOnce() -> R + UnwindSafe, +{ match panic::catch_unwind(f) { - Ok(x) => x, - Err(_) => ZauthResult::Panic + Ok(x) => x, + Err(_) => r, + } +} + +impl From for OAuthResultStatus { + fn from(e: OauthError) -> Self { + match e { + OauthError::JsonError(_) => Self::Panic, + OauthError::JwtSimpleError(_) => Self::Panic, + OauthError::Base64DecodeError(_) => Self::Panic, + OauthError::InvalidJwk => Self::Panic, + OauthError::InvalidJwtNoSubject => Self::Panic, + OauthError::InvalidScope => Self::InsufficientScope, + } + } +} + +impl From for OAuthResultStatus { + fn from(_: io::Error) -> Self { + Self::IoError + } +} + +impl From for OAuthResultStatus { + fn from(_: str::Utf8Error) -> Self { + Self::Utf8Error + } +} + +impl From for OAuthResult { + fn from(_: str::Utf8Error) -> Self { + OAuthResult { + uid: ptr::null(), + status: OAuthResultStatus::Utf8Error, + } + } +} + +impl From for OAuthResult { + fn from(_: NulError) -> Self { + OAuthResult { + uid: ptr::null(), + status: OAuthResultStatus::Panic, + } + } +} + +impl From for OAuthResult { + fn from(e: OauthError) -> Self { + OAuthResult { + uid: ptr::null(), + status: e.into(), + } + } +} + +#[no_mangle] +pub extern "C" fn oauth_key_open( + f: *const u8, + n: size_t, + k: *mut *mut OAuthPubJwk, +) -> OAuthResultStatus { + if f.is_null() { + return OAuthResultStatus::NullArg; + } + catch_unwind_with( + || { + let bytes = unsafe { slice::from_raw_parts(f, n) }; + let path = try_unwrap!(str::from_utf8(bytes)); + let mut rdr = BufReader::new(try_unwrap!(File::open(&Path::new(path)))); + let mut txt = String::new(); + try_unwrap!(rdr.read_to_string(&mut txt)); + unsafe { + *k = Box::into_raw(Box::new(OAuthPubJwk(txt))); + } + OAuthResultStatus::Ok + }, + OAuthResultStatus::Panic, + ) +} + +#[no_mangle] +pub extern "C" fn oauth_key_delete(a: *mut OAuthPubJwk) { + catch_unwind_with( + || { + unsafe { + drop(Box::from_raw(a)); + } + OAuthResultStatus::Ok + }, + OAuthResultStatus::Panic, + ); +} + +#[no_mangle] +pub extern "C" fn oauth_verify_token( + jwk: &OAuthPubJwk, + token: *const u8, + token_len: size_t, + scope: *const u8, + scope_len: size_t, + method: *const u8, + method_len: size_t, +) -> OAuthResult { + match panic::catch_unwind(|| { + if token.is_null() { + return OAuthResult { + uid: ptr::null(), + status: OAuthResultStatus::NullArg, + }; + } + if scope.is_null() { + return OAuthResult { + uid: ptr::null(), + status: OAuthResultStatus::NullArg, + }; + } + if method.is_null() { + return OAuthResult { + uid: ptr::null(), + status: OAuthResultStatus::NullArg, + }; + } + let bytes = unsafe { slice::from_raw_parts(token, token_len) }; + let token = try_unwrap!(str::from_utf8(bytes)); + let bytes = unsafe { slice::from_raw_parts(scope, scope_len) }; + let scope = try_unwrap!(str::from_utf8(bytes)); + let bytes = unsafe { slice::from_raw_parts(method, method_len) }; + let method = str::from_utf8(bytes).unwrap(); + let subject = try_unwrap!(verify_oauth_token(&jwk.0, token, scope, method)); + let c_str = try_unwrap!(CString::new(subject)); + OAuthResult { + uid: c_str.into_raw(), + status: OAuthResultStatus::Ok, + } + }) { + Ok(x) => x, + Err(_) => OAuthResult { + uid: ptr::null(), + status: OAuthResultStatus::Panic, + }, } } + +#[no_mangle] +pub extern "C" fn oauth_result_uid_delete(s: *mut libc::c_char) { + catch_unwind_with( + || { + unsafe { + if s.is_null() { + return OAuthResultStatus::Ok; + } + CString::from_raw(s) + }; + OAuthResultStatus::Ok + }, + OAuthResultStatus::Panic, + ); +} diff --git a/libs/libzauth/libzauth-c/src/zauth.h b/libs/libzauth/libzauth-c/src/zauth.h index e8878e3609..33878c8820 100644 --- a/libs/libzauth/libzauth-c/src/zauth.h +++ b/libs/libzauth/libzauth-c/src/zauth.h @@ -39,24 +39,52 @@ typedef enum { ZAUTH_TOKEN_TYPE_LEGAL_HOLD_ACCESS = 6, } ZauthTokenType; +typedef enum { + ZAUTH_TOKEN_VERIFICATION_SUCCESS = 0, + ZAUTH_TOKEN_VERIFICATION_FAILURE = 1, + ZAUTH_TOKEN_VERIFICATION_PENDING = 2, +} ZauthTokenVerification; + typedef struct ZauthAcl ZauthAcl; typedef struct ZauthKeystore ZauthKeystore; typedef struct ZauthToken ZauthToken; +typedef enum { + OAUTH_OK = 0, + OAUTH_INSUFFICIENT_SCOPE = 1, + OAUTH_NULL_ARG = 2, + OAUTH_IO_ERROR = 3, + OAUTH_UTF8_ERROR = 4, + OAUTH_PANIC = 99, +} OAuthResultStatus; + +typedef struct OAuthPubJwk OAuthPubJwk; + +typedef struct { + char * uid; + OAuthResultStatus status; +} OAuthResult; + ZauthResult zauth_keystore_open(uint8_t const * fname, size_t len, ZauthKeystore **); void zauth_keystore_delete(ZauthKeystore * store); ZauthResult zauth_acl_open(uint8_t const * fname, size_t len, ZauthAcl **); void zauth_acl_delete(ZauthAcl * store); -ZauthResult zauth_token_parse(uint8_t const * str, size_t len, ZauthToken **); -ZauthResult zauth_token_verify(ZauthToken const *, ZauthKeystore const *); -ZauthTokenType zauth_token_type(ZauthToken const *); -long zauth_token_time(ZauthToken const *); -uint8_t zauth_token_version(ZauthToken const *); -Range zauth_token_lookup(ZauthToken const *, uint8_t); -ZauthResult zauth_token_allowed(ZauthToken const *, ZauthAcl const *, uint8_t const * path, size_t len, uint8_t * result); -void zauth_token_delete(ZauthToken *); +OAuthResultStatus oauth_key_open(uint8_t const * fname, size_t len, OAuthPubJwk **); +void oauth_key_delete(OAuthPubJwk * store); + +ZauthResult zauth_token_parse(uint8_t const * str, size_t len, ZauthToken **); +ZauthResult zauth_token_verify(ZauthToken const *, ZauthKeystore const *); +ZauthTokenType zauth_token_type(ZauthToken const *); +ZauthTokenVerification zauth_token_verification(ZauthToken const *); +long zauth_token_time(ZauthToken const *); +uint8_t zauth_token_version(ZauthToken const *); +Range zauth_token_lookup(ZauthToken const *, uint8_t); +ZauthResult zauth_token_allowed(ZauthToken const *, ZauthAcl const *, uint8_t const * path, size_t len, uint8_t * result); +void zauth_token_delete(ZauthToken *); +OAuthResult oauth_verify_token(OAuthPubJwk const *, uint8_t const * t, size_t t_len, uint8_t const * s, size_t s_len, uint8_t const * m, size_t m_len); +OAuthResultStatus oauth_result_uid_delete(char *); #ifdef __cplusplus } diff --git a/libs/libzauth/libzauth/Cargo.toml b/libs/libzauth/libzauth/Cargo.toml index 34920bd6f2..f8fa61feda 100644 --- a/libs/libzauth/libzauth/Cargo.toml +++ b/libs/libzauth/libzauth/Cargo.toml @@ -13,6 +13,15 @@ rustc-serialize = ">= 0.3" sodiumoxide = "^0.2.7" regex = "1.6" lazy_static = "1.4" +serde_json = "1.0" +serde = "1.0" +thiserror = "1.0" +base64 = "0.21" [dev-dependencies] clap = ">= 2.0" + +# fork of jwt-simple which supports JWK handling +[dependencies.jwt-simple] +git = "https://github.com/wireapp/rust-jwt-simple" +rev = "15a69f82288d68b74a75c1364e5d4bf681f1c07b" diff --git a/libs/libzauth/libzauth/src/lib.rs b/libs/libzauth/libzauth/src/lib.rs index 7aed0ff371..195d06138b 100644 --- a/libs/libzauth/libzauth/src/lib.rs +++ b/libs/libzauth/libzauth/src/lib.rs @@ -20,13 +20,20 @@ extern crate lazy_static; extern crate regex; extern crate rustc_serialize; extern crate sodiumoxide; +extern crate jwt_simple; +extern crate serde_json; +extern crate serde; +extern crate thiserror; +extern crate base64; pub mod acl; pub mod error; pub mod zauth; +pub mod oauth; mod matcher; pub use acl::Acl; pub use error::Error; -pub use zauth::{Keystore, Token, TokenType}; +pub use zauth::{Keystore, Token, TokenType, TokenVerification}; +pub use oauth::{verify_oauth_token, OauthError}; diff --git a/libs/libzauth/libzauth/src/oauth.rs b/libs/libzauth/libzauth/src/oauth.rs new file mode 100644 index 0000000000..723f91810e --- /dev/null +++ b/libs/libzauth/libzauth/src/oauth.rs @@ -0,0 +1,155 @@ +use base64::Engine; +use jwt_simple::prelude::*; + +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct OAuthToken { + pub scope: String, +} + +pub fn verify_oauth_token( + jwk: &str, + token: &str, + required_scope: &str, + method: &str, +) -> Result { + let jwk = serde_json::from_str::(jwk)?; + let key = try_from_jwk(&jwk)?; + let options = VerificationOptions { + time_tolerance: Some(Duration::from_secs(1)), + ..Default::default() + }; + let claims = key.verify_token::(token, Some(options))?; + let subject = claims.subject.ok_or(OauthError::InvalidJwtNoSubject)?; + verify_scope(&claims.custom.scope, required_scope, method)?; + Ok(subject) +} + +// if method is GET, authorized scopes must contain either read:_, write:_, or admin:_ +// if method is POST, authorized scopes must contain either write:_ or admin:_ +// if method is PUT, authorized scopes must contain either write:_ or admin:_ +// if method is DELETE, authorized scopes must contain admin:_ +// FUTUREWORK: this works for now, but maybe we should consider using a more flexible scope system in the future +// e.g. by using a configuration file with a mapping of scopes to methods and paths +fn verify_scope( + authorized_scopes: &str, + required_scope: &str, + method: &str, +) -> Result<(), OauthError> { + let valid_scopes = match method.to_uppercase().as_str() { + "GET" => Ok(vec!["read", "write", "admin"]), + "POST" => Ok(vec!["write", "admin"]), + "PUT" => Ok(vec!["write", "admin"]), + "DELETE" => Ok(vec!["admin"]), + _ => Err(OauthError::InvalidScope), + }? + .iter() + .map(|s| format!("{}:{}", s, required_scope)) + .collect::>(); + + let valid = authorized_scopes + .split_whitespace() + .any(|s| valid_scopes.contains(&s.to_string())); + + if !valid { + return Err(OauthError::InvalidScope); + } + Ok(()) +} + +fn try_from_jwk(jwk: &Jwk) -> Result { + Ok(match &jwk.algorithm { + AlgorithmParameters::OctetKeyPair(p) => { + let x = base64::prelude::BASE64_URL_SAFE_NO_PAD.decode(&p.x)?; + Ed25519PublicKey::from_bytes(&x)? + } + _ => return Err(OauthError::InvalidJwk), + }) +} + +#[derive(Debug, thiserror::Error)] +pub enum OauthError { + /// Json error + #[error(transparent)] + JsonError(#[from] serde_json::Error), + /// JWT error from jwt-simple crate + #[error(transparent)] + JwtSimpleError(#[from] jwt_simple::Error), + /// Base64 decoding error + #[error(transparent)] + Base64DecodeError(#[from] base64::DecodeError), + /// Invalid JWK + #[error("invalid jwk")] + InvalidJwk, + /// Invalid JWT missing subject + #[error("missing subject")] + InvalidJwtNoSubject, + /// Invalid scope + #[error("invalid scope")] + InvalidScope, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn should_verify_scope_get() { + assert!(verify_scope("read:self foo bar", "self", "GET").is_ok()); + assert!(verify_scope("write:self foo bar", "self", "GET").is_ok()); + assert!(verify_scope("admin:self foo bar", "self", "GET").is_ok()); + assert!(verify_scope("foo bar", "self", "GET").is_err()); + } + + #[test] + fn should_verify_scope_post() { + assert!(verify_scope("write:self foo bar", "self", "POST").is_ok()); + assert!(verify_scope("admin:self foo bar", "self", "POST").is_ok()); + assert!(verify_scope("read:self foo bar", "self", "POST").is_err()); + assert!(verify_scope("foo bar", "self", "POST").is_err()); + } + + #[test] + fn should_verify_scope_put() { + assert!(verify_scope("write:self foo bar", "self", "PUT").is_ok()); + assert!(verify_scope("admin:self foo bar", "self", "PUT").is_ok()); + assert!(verify_scope("read:self foo bar", "self", "PUT").is_err()); + assert!(verify_scope("foo bar", "self", "PUT").is_err()); + } + + #[test] + fn should_verify_scope_delete() { + assert!(verify_scope("admin:self foo bar", "self", "DELETE").is_ok()); + assert!(verify_scope("write:self foo bar", "self", "DELETE").is_err()); + assert!(verify_scope("read:self foo bar", "self", "DELETE").is_err()); + assert!(verify_scope("foo bar", "self", "DELETE").is_err()); + } + + #[test] + fn should_verify_oauth_token() { + let uid = "842ddbc8-56ec-408d-9fa8-7a8c37ad22a7"; + let key = Ed25519KeyPair::generate(); + let jwk = mk_jwk(key.public_key()); + let token = Claims::with_custom_claims( + OAuthToken { + scope: "write:foo read:test admin:bar".to_string(), + }, + Duration::from_secs(3600), + ) + .with_subject(uid); + let jwt = key.sign::(token).unwrap(); + let subject = + verify_oauth_token(&serde_json::to_string(&jwk).unwrap(), &jwt, "test", "GET").unwrap(); + assert_eq!(&subject, uid); + } + fn mk_jwk(key: Ed25519PublicKey) -> Jwk { + let x = base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(&key.to_bytes()); + Jwk { + common: CommonParameters::default(), + algorithm: AlgorithmParameters::OctetKeyPair(OctetKeyPairParameters { + key_type: OctetKeyPairType::OctetKeyPair, + curve: EdwardCurve::Ed25519, + x, + }), + } + } +} diff --git a/libs/libzauth/libzauth/src/zauth.rs b/libs/libzauth/libzauth/src/zauth.rs index 136b6e423b..3010e06a70 100644 --- a/libs/libzauth/libzauth/src/zauth.rs +++ b/libs/libzauth/libzauth/src/zauth.rs @@ -85,6 +85,15 @@ impl FromStr for TokenType { } } +// Token Verification //////////////////////////////////////////////////////// + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum TokenVerification { + Verified, + Invalid, + Pending +} + // Token //////////////////////////////////////////////////////////////////// // Used when parsing tokens. @@ -103,14 +112,15 @@ macro_rules! to_field { #[derive(Debug)] pub struct Token<'r> { - pub signature: Signature, - pub version: u8, - pub key_idx: usize, - pub timestamp: i64, - pub token_type: TokenType, - pub token_tag: Option<&'r str>, - meta: HashMap, - data: &'r [u8] + pub signature: Signature, + pub version: u8, + pub key_idx: usize, + pub timestamp: i64, + pub token_type: TokenType, + pub token_tag: Option<&'r str>, + pub verification: TokenVerification, + meta: HashMap, + data: &'r [u8] } impl<'r> Token<'r> { @@ -141,14 +151,15 @@ impl<'r> Token<'r> { Ok(Token { signature, - version: to_field!(meta.remove(&'v'), "version"), - key_idx: to_field!(meta.remove(&'k'), "key index"), - timestamp: to_field!(meta.remove(&'d'), "timestamp"), - token_type: to_field!(meta.remove(&'t'), "type"), - token_tag: meta.remove(&'l') + version: to_field!(meta.remove(&'v'), "version"), + key_idx: to_field!(meta.remove(&'k'), "key index"), + timestamp: to_field!(meta.remove(&'d'), "timestamp"), + token_type: to_field!(meta.remove(&'t'), "type"), + token_tag: meta.remove(&'l') .and_then(|t| if t == "" { None } else { Some(t) }), + verification: TokenVerification::Pending, meta, - data: data[1..].as_bytes() + data: data[1..].as_bytes() }) } diff --git a/libs/metrics-core/metrics-core.cabal b/libs/metrics-core/metrics-core.cabal index 876aeab315..02225d07ef 100644 --- a/libs/metrics-core/metrics-core.cabal +++ b/libs/metrics-core/metrics-core.cabal @@ -31,6 +31,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -42,6 +43,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/libs/metrics-wai/metrics-wai.cabal b/libs/metrics-wai/metrics-wai.cabal index 63854462ee..b048be98c2 100644 --- a/libs/metrics-wai/metrics-wai.cabal +++ b/libs/metrics-wai/metrics-wai.cabal @@ -34,6 +34,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -45,6 +46,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -106,6 +108,7 @@ test-suite unit DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -117,6 +120,7 @@ test-suite unit MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/libs/polysemy-wire-zoo/default.nix b/libs/polysemy-wire-zoo/default.nix index ea3e21fdf0..8762faf572 100644 --- a/libs/polysemy-wire-zoo/default.nix +++ b/libs/polysemy-wire-zoo/default.nix @@ -3,7 +3,9 @@ # must be regenerated whenever local packages are added or removed, or # dependencies are added or removed. { mkDerivation +, aeson , base +, bytestring , cassandra-util , containers , gitignoreSource @@ -11,12 +13,14 @@ , hspec , hspec-discover , imports +, jose , lib , polysemy , polysemy-check , polysemy-plugin , QuickCheck , saml2-web-sso +, string-conversions , time , tinylog , types-common @@ -29,16 +33,20 @@ mkDerivation { version = "0.1.0"; src = gitignoreSource ./.; libraryHaskellDepends = [ + aeson base + bytestring cassandra-util HsOpenSSL hspec imports + jose polysemy polysemy-check polysemy-plugin QuickCheck saml2-web-sso + string-conversions time tinylog types-common diff --git a/libs/polysemy-wire-zoo/polysemy-wire-zoo.cabal b/libs/polysemy-wire-zoo/polysemy-wire-zoo.cabal index 934d5b0931..38f903ccef 100644 --- a/libs/polysemy-wire-zoo/polysemy-wire-zoo.cabal +++ b/libs/polysemy-wire-zoo/polysemy-wire-zoo.cabal @@ -18,6 +18,7 @@ library Wire.Sem.Concurrency.IO Wire.Sem.Concurrency.Sequential Wire.Sem.FromUTC + Wire.Sem.Jwk Wire.Sem.Logger Wire.Sem.Logger.Level Wire.Sem.Logger.TinyLog @@ -45,6 +46,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -56,6 +58,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -78,16 +81,20 @@ library -Wredundant-constraints build-depends: - base >=4.6 && <5.0 + aeson + , base >=4.6 && <5.0 + , bytestring , cassandra-util , HsOpenSSL , hspec , imports + , jose , polysemy , polysemy-check , polysemy-plugin , QuickCheck , saml2-web-sso + , string-conversions , time , tinylog , types-common @@ -118,6 +125,7 @@ test-suite spec DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -129,6 +137,7 @@ test-suite spec MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/libs/polysemy-wire-zoo/src/Wire/Sem/Jwk.hs b/libs/polysemy-wire-zoo/src/Wire/Sem/Jwk.hs new file mode 100644 index 0000000000..183e427f42 --- /dev/null +++ b/libs/polysemy-wire-zoo/src/Wire/Sem/Jwk.hs @@ -0,0 +1,22 @@ +{-# LANGUAGE TemplateHaskell #-} + +module Wire.Sem.Jwk where + +import Control.Exception +import Crypto.JOSE.JWK +import Data.Aeson +import qualified Data.ByteString as BS +import Data.String.Conversions (cs) +import Imports +import Polysemy + +data Jwk m a where + Get :: FilePath -> Jwk m (Maybe JWK) + +makeSem ''Jwk + +interpretJwk :: Members '[Embed IO] r => Sem (Jwk ': r) a -> Sem r a +interpretJwk = interpret $ \(Get fp) -> liftIO $ readJwk fp + +readJwk :: FilePath -> IO (Maybe JWK) +readJwk fp = try @IOException (BS.readFile fp) <&> either (const Nothing) (decode . cs) diff --git a/libs/ropes/ropes.cabal b/libs/ropes/ropes.cabal index cf23d61256..3744966e27 100644 --- a/libs/ropes/ropes.cabal +++ b/libs/ropes/ropes.cabal @@ -30,6 +30,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -41,6 +42,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/libs/schema-profunctor/schema-profunctor.cabal b/libs/schema-profunctor/schema-profunctor.cabal index 9ad3b7f616..b8925aaf48 100644 --- a/libs/schema-profunctor/schema-profunctor.cabal +++ b/libs/schema-profunctor/schema-profunctor.cabal @@ -27,6 +27,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -38,6 +39,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -96,6 +98,7 @@ test-suite schemas-tests DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -107,6 +110,7 @@ test-suite schemas-tests MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/libs/sodium-crypto-sign/sodium-crypto-sign.cabal b/libs/sodium-crypto-sign/sodium-crypto-sign.cabal index 22b69932b5..20684f1172 100644 --- a/libs/sodium-crypto-sign/sodium-crypto-sign.cabal +++ b/libs/sodium-crypto-sign/sodium-crypto-sign.cabal @@ -30,6 +30,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -41,6 +42,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/libs/ssl-util/ssl-util.cabal b/libs/ssl-util/ssl-util.cabal index 518d7031b3..e1a171cbb6 100644 --- a/libs/ssl-util/ssl-util.cabal +++ b/libs/ssl-util/ssl-util.cabal @@ -28,6 +28,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -39,6 +40,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/libs/tasty-cannon/tasty-cannon.cabal b/libs/tasty-cannon/tasty-cannon.cabal index d17891ee05..9faa341242 100644 --- a/libs/tasty-cannon/tasty-cannon.cabal +++ b/libs/tasty-cannon/tasty-cannon.cabal @@ -27,6 +27,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -38,6 +39,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/libs/types-common-aws/types-common-aws.cabal b/libs/types-common-aws/types-common-aws.cabal index 2926bd1da7..0d5034101b 100644 --- a/libs/types-common-aws/types-common-aws.cabal +++ b/libs/types-common-aws/types-common-aws.cabal @@ -41,6 +41,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -52,6 +53,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/libs/types-common-journal/types-common-journal.cabal b/libs/types-common-journal/types-common-journal.cabal index 476676023b..cee43aa891 100644 --- a/libs/types-common-journal/types-common-journal.cabal +++ b/libs/types-common-journal/types-common-journal.cabal @@ -44,6 +44,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -55,6 +56,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/libs/types-common/default.nix b/libs/types-common/default.nix index 6d974ab32b..b3ac478bfe 100644 --- a/libs/types-common/default.nix +++ b/libs/types-common/default.nix @@ -102,6 +102,7 @@ mkDerivation { swagger2 tagged tasty + tasty-hunit text time time-locale-compat diff --git a/libs/types-common/src/Data/Id.hs b/libs/types-common/src/Data/Id.hs index 526d9c850a..693b9b5584 100644 --- a/libs/types-common/src/Data/Id.hs +++ b/libs/types-common/src/Data/Id.hs @@ -49,6 +49,8 @@ module Data.Id RequestId (..), BotId (..), NoId, + OAuthClientId, + OAuthRefreshTokenId, ) where @@ -97,6 +99,8 @@ data IdTag | Service | Team | ScimToken + | OAuthClient + | OAuthRefreshToken idTagName :: IdTag -> Text idTagName Asset = "Asset" @@ -107,6 +111,8 @@ idTagName Provider = "Provider" idTagName Service = "Service" idTagName Team = "Team" idTagName ScimToken = "ScimToken" +idTagName OAuthClient = "OAuthClient" +idTagName OAuthRefreshToken = "OAuthRefreshToken" class KnownIdTag (t :: IdTag) where idTagValue :: IdTag @@ -127,6 +133,10 @@ instance KnownIdTag 'Team where idTagValue = Team instance KnownIdTag 'ScimToken where idTagValue = ScimToken +instance KnownIdTag 'OAuthClient where idTagValue = OAuthClient + +instance KnownIdTag 'OAuthRefreshToken where idTagValue = OAuthRefreshToken + type AssetId = Id 'Asset type InvitationId = Id 'Invitation @@ -145,6 +155,10 @@ type TeamId = Id 'Team type ScimTokenId = Id 'ScimToken +type OAuthClientId = Id 'OAuthClient + +type OAuthRefreshTokenId = Id 'OAuthRefreshToken + -- Id ------------------------------------------------------------------------- data NoId = NoId deriving (Eq, Show, Generic) diff --git a/libs/types-common/src/Data/Misc.hs b/libs/types-common/src/Data/Misc.hs index 5488c1239e..411bd2e871 100644 --- a/libs/types-common/src/Data/Misc.hs +++ b/libs/types-common/src/Data/Misc.hs @@ -49,8 +49,16 @@ module Data.Misc Fingerprint (..), Rsa, - -- * PlainTextPassword - PlainTextPassword (..), + -- * PlainTextPassword6 + PlainTextPassword' (..), + PlainTextPassword6, + PlainTextPassword8, + plainTextPassword6, + plainTextPassword8, + fromPlainTextPassword, + plainTextPassword8Unsafe, + plainTextPassword6Unsafe, + plainTextPassword8To6, -- * Typesafe FUTUREWORKS FutureWork (..), @@ -75,6 +83,8 @@ import Data.String.Conversions (cs) import qualified Data.Swagger as S import qualified Data.Text as Text import Data.Text.Encoding (decodeUtf8, encodeUtf8) +import GHC.TypeLits (Nat) +import GHC.TypeNats (KnownNat) import Imports import Servant (FromHttpApiData (..)) import Test.QuickCheck (Arbitrary (arbitrary), chooseInteger) @@ -338,23 +348,47 @@ instance Arbitrary (Fingerprint Rsa) where -------------------------------------------------------------------------------- -- Password -newtype PlainTextPassword = PlainTextPassword - {fromPlainTextPassword :: Text} +type PlainTextPassword6 = PlainTextPassword' (6 :: Nat) + +type PlainTextPassword8 = PlainTextPassword' (8 :: Nat) + +plainTextPassword6 :: Text -> Maybe PlainTextPassword6 +plainTextPassword6 = fmap PlainTextPassword' . checked + +plainTextPassword6Unsafe :: Text -> PlainTextPassword6 +plainTextPassword6Unsafe = PlainTextPassword' . unsafeRange + +plainTextPassword8 :: Text -> Maybe PlainTextPassword8 +plainTextPassword8 = fmap PlainTextPassword' . checked + +plainTextPassword8Unsafe :: Text -> PlainTextPassword8 +plainTextPassword8Unsafe = PlainTextPassword' . unsafeRange + +fromPlainTextPassword :: PlainTextPassword' t -> Text +fromPlainTextPassword = fromRange . fromPlainTextPassword' + +-- | Convert a 'PlainTextPassword8' to a legacy 'PlainTextPassword'. +plainTextPassword8To6 :: PlainTextPassword8 -> PlainTextPassword6 +plainTextPassword8To6 = PlainTextPassword' . unsafeRange . fromPlainTextPassword + +newtype PlainTextPassword' (minLen :: Nat) = PlainTextPassword' + {fromPlainTextPassword' :: Range minLen (1024 :: Nat) Text} deriving stock (Eq, Generic) - deriving (FromJSON, ToJSON, S.ToSchema) via Schema PlainTextPassword -instance Show PlainTextPassword where - show _ = "PlainTextPassword " +deriving via (Schema (PlainTextPassword' tag)) instance ToSchema (PlainTextPassword' tag) => FromJSON (PlainTextPassword' tag) -instance ToSchema PlainTextPassword where - schema = - PlainTextPassword - <$> fromPlainTextPassword - .= untypedRangedSchema 6 1024 schema +deriving via (Schema (PlainTextPassword' tag)) instance ToSchema (PlainTextPassword' tag) => ToJSON (PlainTextPassword' tag) + +deriving via (Schema (PlainTextPassword' tag)) instance ToSchema (PlainTextPassword' tag) => S.ToSchema (PlainTextPassword' tag) + +instance Show (PlainTextPassword' minLen) where + show _ = "PlainTextPassword' " + +instance (KnownNat (n :: Nat), Within Text n 1024) => ToSchema (PlainTextPassword' n) where + schema = PlainTextPassword' <$> fromPlainTextPassword' .= schema -instance Arbitrary PlainTextPassword where - -- TODO: why 6..1024? For tests we might want invalid passwords as well, e.g. 3 chars - arbitrary = PlainTextPassword . fromRange <$> genRangeText @6 @1024 arbitrary +instance (KnownNat (n :: Nat), Within Text n 1024) => Arbitrary (PlainTextPassword' n) where + arbitrary = PlainTextPassword' <$> arbitrary -- | Usage: -- 1. Use this type in patterns to mark FUTUREWORKS. diff --git a/libs/types-common/src/Data/Qualified.hs b/libs/types-common/src/Data/Qualified.hs index 82e8006d80..d708b7a05d 100644 --- a/libs/types-common/src/Data/Qualified.hs +++ b/libs/types-common/src/Data/Qualified.hs @@ -167,9 +167,10 @@ deprecatedSchema :: S.HasDescription doc (Maybe Text) => Text -> ValueSchema doc deprecatedSchema new = doc . description ?~ ("Deprecated, use " <> new) qualifiedSchema :: + HasSchemaRef doc => Text -> Text -> - ValueSchema NamedSwaggerDoc a -> + ValueSchema doc a -> ValueSchema NamedSwaggerDoc (Qualified a) qualifiedSchema name fieldName sch = object ("Qualified_" <> name) $ diff --git a/libs/types-common/src/Data/Text/Ascii.hs b/libs/types-common/src/Data/Text/Ascii.hs index 428d7dc12b..1b602acdb2 100644 --- a/libs/types-common/src/Data/Text/Ascii.hs +++ b/libs/types-common/src/Data/Text/Ascii.hs @@ -57,6 +57,7 @@ module Data.Text.Ascii AsciiBase64Url, validateBase64Url, encodeBase64Url, + encodeBase64UrlUnpadded, decodeBase64Url, -- * Base16 (Hex) Characters @@ -310,6 +311,12 @@ validateBase64Url = validate encodeBase64Url :: ByteString -> AsciiBase64Url encodeBase64Url = unsafeFromByteString . B64Url.encode +-- | Encode a bytestring into a text containing only url-safe +-- base-64 characters. The resulting text is always a valid +-- encoding in unpadded form. +encodeBase64UrlUnpadded :: ByteString -> AsciiBase64Url +encodeBase64UrlUnpadded = unsafeFromByteString . B64Url.encodeUnpadded + -- | Decode a text containing only url-safe base-64 characters. -- Decoding only succeeds if the text is a valid encoding and -- a multiple of 4 bytes in length. diff --git a/libs/types-common/src/Test/Tasty/Pending.hs b/libs/types-common/src/Test/Tasty/Pending.hs new file mode 100644 index 0000000000..a8485374cf --- /dev/null +++ b/libs/types-common/src/Test/Tasty/Pending.hs @@ -0,0 +1,30 @@ +-- This file is part of the Wire Server implementation. +-- +-- Copyright (C) 2023 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 . + +-- | related: https://github.com/nomeata/tasty-expected-failure +module Test.Tasty.Pending (flakyTestCase) where + +import Imports +import System.IO.Unsafe (unsafePerformIO) +import Test.Tasty +import Test.Tasty.HUnit + +flakyTestCase :: TestName -> Assertion -> TestTree +flakyTestCase name test = testCase name test' + where + test' = when (runthem == Just "1") test + runthem = unsafePerformIO $ lookupEnv "RUN_FLAKY_TESTS" diff --git a/libs/types-common/types-common.cabal b/libs/types-common/types-common.cabal index 0a414d5cea..471765b90b 100644 --- a/libs/types-common/types-common.cabal +++ b/libs/types-common/types-common.cabal @@ -31,6 +31,7 @@ library Data.SizedHashMap Data.Text.Ascii Data.UUID.Tagged + Test.Tasty.Pending Util.Attoparsec Util.Logging Util.Options @@ -53,6 +54,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -64,6 +66,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -126,6 +129,7 @@ library , swagger2 , tagged >=0.8 , tasty >=0.11 + , tasty-hunit , text >=0.11 , time >=1.6 , time-locale-compat >=0.1 @@ -165,6 +169,7 @@ test-suite tests DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -176,6 +181,7 @@ test-suite tests MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/libs/wai-utilities/wai-utilities.cabal b/libs/wai-utilities/wai-utilities.cabal index df882e230f..0e0babcece 100644 --- a/libs/wai-utilities/wai-utilities.cabal +++ b/libs/wai-utilities/wai-utilities.cabal @@ -37,6 +37,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -48,6 +49,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms 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 158aedfa53..8a0422c5f2 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 @@ -35,6 +35,7 @@ import Wire.API.Conversation.Typing import Wire.API.Error.Galley import Wire.API.Federation.API.Common import Wire.API.Federation.Endpoint +import Wire.API.MLS.Message import Wire.API.MLS.SubConversation import Wire.API.MakesFederatedCall import Wire.API.Message @@ -422,7 +423,7 @@ data MLSMessageResponse = MLSMessageResponseError GalleyError | MLSMessageResponseProtocolError Text | MLSMessageResponseProposalFailure Wai.Error - | MLSMessageResponseUpdates [ConversationUpdate] + | MLSMessageResponseUpdates [ConversationUpdate] UnreachableUsers deriving stock (Eq, Show, Generic) deriving (ToJSON, FromJSON) via (CustomEncoded MLSMessageResponse) diff --git a/libs/wire-api-federation/src/Wire/API/Federation/Client.hs b/libs/wire-api-federation/src/Wire/API/Federation/Client.hs index 0d2b32f987..7ad302dfdc 100644 --- a/libs/wire-api-federation/src/Wire/API/Federation/Client.hs +++ b/libs/wire-api-federation/src/Wire/API/Federation/Client.hs @@ -365,20 +365,30 @@ allocTLSConfig :: SSL -> HTTP2.BufferSize -> IO HTTP2.Config allocTLSConfig ssl bufsize = do buf <- mallocBytes bufsize timmgr <- System.TimeManager.initialize $ 30 * 1000000 - let readData :: Int -> IO ByteString - -- Sometimes the frame header says that the payload length is 0. Reading 0 - -- bytes multiple times seems to be causing errors in openssl. I cannot - -- figure out why. The previous implementation didn't try to read from the - -- socket when trying to read 0 bytes, so special handling for 0 maintains - -- that behaviour. - readData 0 = pure "" - readData n = SSL.read ssl n `catch` \(_ :: SSL.ConnectionAbruptlyTerminated) -> pure mempty + -- Sometimes the frame header says that the payload length is 0. Reading 0 + -- bytes multiple times seems to be causing errors in openssl. I cannot figure + -- out why. The previous implementation didn't try to read from the socket + -- when trying to read 0 bytes, so special handling for 0 maintains that + -- behaviour. + let readData prevChunk 0 = pure prevChunk + readData prevChunk n = do + -- Handling SSL.ConnectionAbruptlyTerminated as a stream end + -- (some sites terminate SSL connection right after returning the data). + chunk <- SSL.read ssl n `catch` \(_ :: SSL.ConnectionAbruptlyTerminated) -> pure mempty + let chunkLen = BS.length chunk + if + | chunkLen == 0 || chunkLen == n -> + pure (prevChunk <> chunk) + | chunkLen > n -> + error "openssl: SSL.read returned more bytes than asked for, this is probably a bug" + | otherwise -> + readData (prevChunk <> chunk) (n - chunkLen) pure HTTP2.Config { HTTP2.confWriteBuffer = buf, HTTP2.confBufferSize = bufsize, HTTP2.confSendAll = SSL.write ssl, - HTTP2.confReadN = readData, + HTTP2.confReadN = readData mempty, HTTP2.confPositionReadMaker = HTTP2.defaultPositionReadMaker, HTTP2.confTimeoutManager = timmgr } diff --git a/libs/wire-api-federation/test/Test/Wire/API/Federation/Golden/GoldenSpec.hs b/libs/wire-api-federation/test/Test/Wire/API/Federation/Golden/GoldenSpec.hs index 871bfbb035..5bc03b0398 100644 --- a/libs/wire-api-federation/test/Test/Wire/API/Federation/Golden/GoldenSpec.hs +++ b/libs/wire-api-federation/test/Test/Wire/API/Federation/Golden/GoldenSpec.hs @@ -23,6 +23,7 @@ import qualified Test.Wire.API.Federation.Golden.ConversationCreated as Conversa import qualified Test.Wire.API.Federation.Golden.ConversationUpdate as ConversationUpdate import qualified Test.Wire.API.Federation.Golden.LeaveConversationRequest as LeaveConversationRequest import qualified Test.Wire.API.Federation.Golden.LeaveConversationResponse as LeaveConversationResponse +import qualified Test.Wire.API.Federation.Golden.MLSMessageSendingStatus as MLSMessageSendingStatus import qualified Test.Wire.API.Federation.Golden.MessageSendResponse as MessageSendResponse import qualified Test.Wire.API.Federation.Golden.NewConnectionRequest as NewConnectionRequest import qualified Test.Wire.API.Federation.Golden.NewConnectionResponse as NewConnectionResponse @@ -32,11 +33,16 @@ spec :: Spec spec = describe "Golden tests" $ do testObjects - [ (MessageSendResponse.testObject_MessageSendReponse1, "testObject_MessageSendReponse1.json"), - (MessageSendResponse.testObject_MessageSendReponse2, "testObject_MessageSendReponse2.json"), - (MessageSendResponse.testObject_MessageSendReponse3, "testObject_MessageSendReponse3.json"), - (MessageSendResponse.testObject_MessageSendReponse4, "testObject_MessageSendReponse4.json"), - (MessageSendResponse.testObject_MessageSendReponse5, "testObject_MessageSendReponse5.json") + [ (MessageSendResponse.testObject_MessageSendResponse1, "testObject_MessageSendResponse1.json"), + (MessageSendResponse.testObject_MessageSendResponse2, "testObject_MessageSendResponse2.json"), + (MessageSendResponse.testObject_MessageSendResponse3, "testObject_MessageSendResponse3.json"), + (MessageSendResponse.testObject_MessageSendResponse4, "testObject_MessageSendResponse4.json"), + (MessageSendResponse.testObject_MessageSendResponse5, "testObject_MessageSendResponse5.json") + ] + testObjects + [ (MLSMessageSendingStatus.testObject_MLSMessageSendingStatus1, "testObject_MLSMessageSendingStatus1.json"), + (MLSMessageSendingStatus.testObject_MLSMessageSendingStatus2, "testObject_MLSMessageSendingStatus2.json"), + (MLSMessageSendingStatus.testObject_MLSMessageSendingStatus3, "testObject_MLSMessageSendingStatus3.json") ] testObjects [(LeaveConversationRequest.testObject_LeaveConversationRequest1, "testObject_LeaveConversationRequest1.json")] testObjects diff --git a/libs/wire-api-federation/test/Test/Wire/API/Federation/Golden/MLSMessageSendingStatus.hs b/libs/wire-api-federation/test/Test/Wire/API/Federation/Golden/MLSMessageSendingStatus.hs new file mode 100644 index 0000000000..0e228bec42 --- /dev/null +++ b/libs/wire-api-federation/test/Test/Wire/API/Federation/Golden/MLSMessageSendingStatus.hs @@ -0,0 +1,63 @@ +-- 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.Federation.Golden.MLSMessageSendingStatus where + +import Data.Domain +import Data.Id +import Data.Json.Util +import Data.Qualified +import qualified Data.UUID as UUID +import Imports +import Wire.API.MLS.Message + +testObject_MLSMessageSendingStatus1 :: MLSMessageSendingStatus +testObject_MLSMessageSendingStatus1 = + MLSMessageSendingStatus + { mmssEvents = [], + mmssTime = toUTCTimeMillis (read "1864-04-12 12:22:43.673 UTC"), + mmssUnreachableUsers = UnreachableUsers [] + } + +testObject_MLSMessageSendingStatus2 :: MLSMessageSendingStatus +testObject_MLSMessageSendingStatus2 = + MLSMessageSendingStatus + { mmssEvents = [], + mmssTime = toUTCTimeMillis (read "2001-04-12 12:22:43.673 UTC"), + mmssUnreachableUsers = failed1 + } + +testObject_MLSMessageSendingStatus3 :: MLSMessageSendingStatus +testObject_MLSMessageSendingStatus3 = + MLSMessageSendingStatus + { mmssEvents = [], + mmssTime = toUTCTimeMillis (read "1999-04-12 12:22:43.673 UTC"), + mmssUnreachableUsers = failed2 + } + +failed1 :: UnreachableUsers +failed1 = + let domain = Domain "offline.example.com" + in UnreachableUsers [Qualified (Id . fromJust . UUID.fromString $ "00000000-0000-0000-0000-000200000008") domain] + +failed2 :: UnreachableUsers +failed2 = + let domain = Domain "golden.example.com" + in UnreachableUsers + [ Qualified (Id . fromJust . UUID.fromString $ "00000000-0000-0000-0000-000200000008") domain, + Qualified (Id . fromJust . UUID.fromString $ "00000000-0000-0000-0000-000100000007") domain + ] diff --git a/libs/wire-api-federation/test/Test/Wire/API/Federation/Golden/MessageSendResponse.hs b/libs/wire-api-federation/test/Test/Wire/API/Federation/Golden/MessageSendResponse.hs index efbcb2d93e..93d1603bd0 100644 --- a/libs/wire-api-federation/test/Test/Wire/API/Federation/Golden/MessageSendResponse.hs +++ b/libs/wire-api-federation/test/Test/Wire/API/Federation/Golden/MessageSendResponse.hs @@ -100,8 +100,8 @@ failed = ] } -testObject_MessageSendReponse1 :: MessageSendResponse -testObject_MessageSendReponse1 = +testObject_MessageSendResponse1 :: MessageSendResponse +testObject_MessageSendResponse1 = MessageSendResponse $ Right MessageSendingStatus @@ -112,11 +112,11 @@ testObject_MessageSendReponse1 = mssFailedToSend = failed } -testObject_MessageSendReponse2 :: MessageSendResponse -testObject_MessageSendReponse2 = MessageSendResponse . Left $ MessageNotSentLegalhold +testObject_MessageSendResponse2 :: MessageSendResponse +testObject_MessageSendResponse2 = MessageSendResponse . Left $ MessageNotSentLegalhold -testObject_MessageSendReponse3 :: MessageSendResponse -testObject_MessageSendReponse3 = +testObject_MessageSendResponse3 :: MessageSendResponse +testObject_MessageSendResponse3 = MessageSendResponse . Left . MessageNotSentClientMissing $ MessageSendingStatus { mssTime = toUTCTimeMillis (read "1864-04-12 12:22:43.673 UTC"), @@ -126,8 +126,8 @@ testObject_MessageSendReponse3 = mssFailedToSend = failed } -testObject_MessageSendReponse4 :: MessageSendResponse -testObject_MessageSendReponse4 = MessageSendResponse . Left $ MessageNotSentConversationNotFound +testObject_MessageSendResponse4 :: MessageSendResponse +testObject_MessageSendResponse4 = MessageSendResponse . Left $ MessageNotSentConversationNotFound -testObject_MessageSendReponse5 :: MessageSendResponse -testObject_MessageSendReponse5 = MessageSendResponse . Left $ MessageNotSentUnknownClient +testObject_MessageSendResponse5 :: MessageSendResponse +testObject_MessageSendResponse5 = MessageSendResponse . Left $ MessageNotSentUnknownClient diff --git a/libs/wire-api-federation/test/golden/testObject_MLSMessageSendingStatus1.json b/libs/wire-api-federation/test/golden/testObject_MLSMessageSendingStatus1.json new file mode 100644 index 0000000000..9323f7742e --- /dev/null +++ b/libs/wire-api-federation/test/golden/testObject_MLSMessageSendingStatus1.json @@ -0,0 +1,5 @@ +{ + "events": [], + "time": "1864-04-12T12:22:43.673Z", + "failed_to_send": [] +} diff --git a/libs/wire-api-federation/test/golden/testObject_MLSMessageSendingStatus2.json b/libs/wire-api-federation/test/golden/testObject_MLSMessageSendingStatus2.json new file mode 100644 index 0000000000..fb932f0059 --- /dev/null +++ b/libs/wire-api-federation/test/golden/testObject_MLSMessageSendingStatus2.json @@ -0,0 +1,10 @@ +{ + "events": [], + "time": "2001-04-12T12:22:43.673Z", + "failed_to_send": [ + { + "domain": "offline.example.com", + "id": "00000000-0000-0000-0000-000200000008" + } + ] +} diff --git a/libs/wire-api-federation/test/golden/testObject_MLSMessageSendingStatus3.json b/libs/wire-api-federation/test/golden/testObject_MLSMessageSendingStatus3.json new file mode 100644 index 0000000000..87c9262448 --- /dev/null +++ b/libs/wire-api-federation/test/golden/testObject_MLSMessageSendingStatus3.json @@ -0,0 +1,14 @@ +{ + "events": [], + "time": "1999-04-12T12:22:43.673Z", + "failed_to_send": [ + { + "domain": "golden.example.com", + "id": "00000000-0000-0000-0000-000200000008" + }, + { + "domain": "golden.example.com", + "id": "00000000-0000-0000-0000-000100000007" + } + ] +} diff --git a/libs/wire-api-federation/test/golden/testObject_MessageSendReponse1.json b/libs/wire-api-federation/test/golden/testObject_MessageSendResponse1.json similarity index 100% rename from libs/wire-api-federation/test/golden/testObject_MessageSendReponse1.json rename to libs/wire-api-federation/test/golden/testObject_MessageSendResponse1.json diff --git a/libs/wire-api-federation/test/golden/testObject_MessageSendReponse2.json b/libs/wire-api-federation/test/golden/testObject_MessageSendResponse2.json similarity index 100% rename from libs/wire-api-federation/test/golden/testObject_MessageSendReponse2.json rename to libs/wire-api-federation/test/golden/testObject_MessageSendResponse2.json diff --git a/libs/wire-api-federation/test/golden/testObject_MessageSendReponse3.json b/libs/wire-api-federation/test/golden/testObject_MessageSendResponse3.json similarity index 100% rename from libs/wire-api-federation/test/golden/testObject_MessageSendReponse3.json rename to libs/wire-api-federation/test/golden/testObject_MessageSendResponse3.json diff --git a/libs/wire-api-federation/test/golden/testObject_MessageSendReponse4.json b/libs/wire-api-federation/test/golden/testObject_MessageSendResponse4.json similarity index 100% rename from libs/wire-api-federation/test/golden/testObject_MessageSendReponse4.json rename to libs/wire-api-federation/test/golden/testObject_MessageSendResponse4.json diff --git a/libs/wire-api-federation/test/golden/testObject_MessageSendReponse5.json b/libs/wire-api-federation/test/golden/testObject_MessageSendResponse5.json similarity index 100% rename from libs/wire-api-federation/test/golden/testObject_MessageSendReponse5.json rename to libs/wire-api-federation/test/golden/testObject_MessageSendResponse5.json diff --git a/libs/wire-api-federation/wire-api-federation.cabal b/libs/wire-api-federation/wire-api-federation.cabal index 42151f8e48..294b062fa4 100644 --- a/libs/wire-api-federation/wire-api-federation.cabal +++ b/libs/wire-api-federation/wire-api-federation.cabal @@ -43,6 +43,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -54,6 +55,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -134,6 +136,7 @@ test-suite spec Test.Wire.API.Federation.Golden.LeaveConversationRequest Test.Wire.API.Federation.Golden.LeaveConversationResponse Test.Wire.API.Federation.Golden.MessageSendResponse + Test.Wire.API.Federation.Golden.MLSMessageSendingStatus Test.Wire.API.Federation.Golden.NewConnectionRequest Test.Wire.API.Federation.Golden.NewConnectionResponse Test.Wire.API.Federation.Golden.Runner @@ -152,6 +155,7 @@ test-suite spec DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -163,6 +167,7 @@ test-suite spec MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/libs/wire-api/default.nix b/libs/wire-api/default.nix index 8b7f20896e..8981908490 100644 --- a/libs/wire-api/default.nix +++ b/libs/wire-api/default.nix @@ -43,6 +43,9 @@ , hex , hostname-validate , hscim +, HsOpenSSL +, hspec +, hspec-wai , http-api-data , http-media , http-types @@ -51,6 +54,7 @@ , iproute , iso3166-country-codes , iso639 +, jose , lens , lib , memory @@ -70,6 +74,7 @@ , saml2-web-sso , schema-profunctor , scientific +, scrypt , servant , servant-client , servant-client-core @@ -87,6 +92,7 @@ , tagged , tasty , tasty-expected-failure +, tasty-hspec , tasty-hunit , tasty-quickcheck , text @@ -146,6 +152,7 @@ mkDerivation { hashable hostname-validate hscim + HsOpenSSL http-api-data http-media http-types @@ -154,6 +161,7 @@ mkDerivation { iproute iso3166-country-codes iso639 + jose lens memory metrics-wai @@ -170,6 +178,7 @@ mkDerivation { saml2-web-sso schema-profunctor scientific + scrypt servant servant-client servant-client-core @@ -223,6 +232,9 @@ mkDerivation { filepath hex hscim + hspec + hspec-wai + http-types imports iso3166-country-codes iso639 @@ -238,11 +250,13 @@ mkDerivation { saml2-web-sso schema-profunctor servant + servant-server servant-swagger-ui string-conversions swagger2 tasty tasty-expected-failure + tasty-hspec tasty-hunit tasty-quickcheck text @@ -253,6 +267,7 @@ mkDerivation { uri-bytestring uuid vector + wai wire-message-proto-lens ]; license = lib.licenses.agpl3Only; diff --git a/libs/wire-api/src/Wire/API/Conversation.hs b/libs/wire-api/src/Wire/API/Conversation.hs index ee9b7c6e2a..e1d444b29e 100644 --- a/libs/wire-api/src/Wire/API/Conversation.hs +++ b/libs/wire-api/src/Wire/API/Conversation.hs @@ -34,6 +34,7 @@ module Wire.API.Conversation cnvMessageTimer, cnvReceiptMode, cnvAccessRoles, + CreateGroupConversation (..), ConversationCoverView (..), ConversationList (..), ListConversations (..), @@ -88,12 +89,14 @@ import Control.Lens ((?~)) import Data.Aeson (FromJSON (..), ToJSON (..)) import qualified Data.Aeson as A import qualified Data.ByteString.Lazy as LBS +import Data.Domain import Data.Id import Data.List.Extra (disjointOrd) import Data.List.NonEmpty (NonEmpty) import Data.List1 +import qualified Data.Map as Map import Data.Misc -import Data.Qualified (Qualified (qUnqualified), deprecatedSchema) +import Data.Qualified import Data.Range (Range, fromRange, rangedSchema) import Data.SOP import Data.Schema @@ -264,6 +267,16 @@ instance ToSchema Conversation where instance ToSchema (Versioned 'V2 Conversation) where schema = Versioned <$> unVersioned .= conversationSchema V2 +conversationObjectSchema :: Version -> ObjectSchema SwaggerDoc Conversation +conversationObjectSchema v = + Conversation + <$> cnvQualifiedId .= field "qualified_id" schema + <* (qUnqualified . cnvQualifiedId) + .= optional (field "id" (deprecatedSchema "qualified_id" schema)) + <*> cnvMetadata .= conversationMetadataObjectSchema (accessRolesVersionedSchema v) + <*> cnvMembers .= field "members" schema + <*> cnvProtocol .= protocolSchema + conversationSchema :: Version -> ValueSchema NamedSwaggerDoc Conversation @@ -271,19 +284,42 @@ conversationSchema v = objectWithDocModifier "Conversation" (description ?~ "A conversation object as returned from the server") - $ Conversation - <$> cnvQualifiedId .= field "qualified_id" schema - <* (qUnqualified . cnvQualifiedId) - .= optional (field "id" (deprecatedSchema "qualified_id" schema)) - <*> cnvMetadata .= conversationMetadataObjectSchema (accessRolesVersionedSchema v) - <*> cnvMembers .= field "members" schema - <*> cnvProtocol .= protocolSchema + (conversationObjectSchema v) + +-- | The public-facing conversation type extended with information on which +-- remote users could not be added when creating the conversation. +data CreateGroupConversation = CreateGroupConversation + { cgcConversation :: Conversation, + -- | Remote users that could not be added to the created group conversation + -- because their backend was not reachable. + cgcFailedToAdd :: Map Domain (Set UserId) + } + deriving stock (Eq, Show, Generic) + deriving (Arbitrary) via (GenericUniform CreateGroupConversation) + deriving (ToJSON, FromJSON, S.ToSchema) via Schema CreateGroupConversation + +instance ToSchema CreateGroupConversation where + schema = + objectWithDocModifier + "CreateGroupConversation" + (description ?~ "A created group-conversation object extended with a list of failed-to-add users") + $ CreateGroupConversation + <$> cgcConversation .= conversationObjectSchema V4 + <*> (toFlatList . cgcFailedToAdd) + .= field "failed_to_add" (fromFlatList <$> array schema) + where + toFlatList :: Map Domain (Set a) -> [Qualified a] + toFlatList m = + (\(d, s) -> flip Qualified d <$> Set.toList s) =<< Map.assocs m + fromFlatList :: Ord a => [Qualified a] -> Map Domain (Set a) + fromFlatList = fmap Set.fromList . indexQualified -- | Limited view of a 'Conversation'. Is used to inform users with an invite -- link about the conversation. data ConversationCoverView = ConversationCoverView { cnvCoverConvId :: ConvId, - cnvCoverName :: Maybe Text + cnvCoverName :: Maybe Text, + cnvCoverHasPassword :: Bool } deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform ConversationCoverView) @@ -299,6 +335,7 @@ instance ToSchema ConversationCoverView where $ ConversationCoverView <$> cnvCoverConvId .= field "id" schema <*> cnvCoverName .= optField "name" (maybeWithDefault A.Null schema) + <*> cnvCoverHasPassword .= field "has_password" schema data ConversationList a = ConversationList { convList :: [a], @@ -886,3 +923,8 @@ namespaceMLSSelfConv = instance AsHeaders '[ConvId] Conversation Conversation where toHeaders c = (I (qUnqualified (cnvQualifiedId c)) :* Nil, c) fromHeaders = snd + +instance AsHeaders '[ConvId] CreateGroupConversation CreateGroupConversation where + toHeaders c = + ((I . qUnqualified . cnvQualifiedId . cgcConversation $ c) :* Nil, c) + fromHeaders = snd diff --git a/libs/wire-api/src/Wire/API/Conversation/Code.hs b/libs/wire-api/src/Wire/API/Conversation/Code.hs index 03e3dc30aa..839b425c6e 100644 --- a/libs/wire-api/src/Wire/API/Conversation/Code.hs +++ b/libs/wire-api/src/Wire/API/Conversation/Code.hs @@ -1,3 +1,5 @@ +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE StrictData #-} -- This file is part of the Wire Server implementation. @@ -21,7 +23,10 @@ module Wire.API.Conversation.Code ( -- * ConversationCode ConversationCode (..), - mkConversationCode, + CreateConversationCodeRequest (..), + JoinConversationByCode (..), + ConversationCodeInfo (..), + mkConversationCodeInfo, -- * re-exports Code.Key (..), @@ -34,13 +39,48 @@ import Data.Aeson (FromJSON, ToJSON) import Data.ByteString.Conversion (toByteString') -- FUTUREWORK: move content of Data.Code here? import Data.Code as Code -import Data.Misc (HttpsUrl (HttpsUrl)) +import Data.Misc import Data.Schema import qualified Data.Swagger as S import Imports import qualified URI.ByteString as URI import Wire.Arbitrary (Arbitrary, GenericUniform (..)) +newtype CreateConversationCodeRequest = CreateConversationCodeRequest + { password :: Maybe PlainTextPassword8 + } + deriving stock (Eq, Show, Generic) + deriving (Arbitrary) via (GenericUniform CreateConversationCodeRequest) + deriving (FromJSON, ToJSON, S.ToSchema) via Schema CreateConversationCodeRequest + +instance ToSchema CreateConversationCodeRequest where + schema :: ValueSchema NamedSwaggerDoc CreateConversationCodeRequest + schema = + objectWithDocModifier + "CreateConversationCodeRequest" + (description ?~ "Request body for creating a conversation code") + $ CreateConversationCodeRequest + <$> (.password) .= maybe_ (optFieldWithDocModifier "password" desc schema) + where + desc = description ?~ "Password for accessing the conversation via guest link. Set to null or omit for no password." + +data JoinConversationByCode = JoinConversationByCode + { code :: ConversationCode, + password :: Maybe PlainTextPassword8 + } + deriving stock (Eq, Show, Generic) + deriving (Arbitrary) via (GenericUniform JoinConversationByCode) + deriving (FromJSON, ToJSON, S.ToSchema) via Schema JoinConversationByCode + +instance ToSchema JoinConversationByCode where + schema = + objectWithDocModifier + "JoinConversationByCode" + (description ?~ "Request body for joining a conversation by code") + $ JoinConversationByCode + <$> (.code) .= conversationCodeObjectSchema + <*> (.password) .= maybe_ (optField "password" schema) + data ConversationCode = ConversationCode { conversationKey :: Code.Key, conversationCode :: Code.Value, @@ -50,37 +90,54 @@ data ConversationCode = ConversationCode deriving (Arbitrary) via (GenericUniform ConversationCode) deriving (FromJSON, ToJSON, S.ToSchema) via Schema ConversationCode +conversationCodeObjectSchema :: ObjectSchema SwaggerDoc ConversationCode +conversationCodeObjectSchema = + ConversationCode + <$> conversationKey + .= fieldWithDocModifier + "key" + (description ?~ "Stable conversation identifier") + schema + <*> conversationCode + .= fieldWithDocModifier + "code" + (description ?~ "Conversation code (random)") + schema + <*> conversationUri + .= maybe_ + ( optFieldWithDocModifier + "uri" + (description ?~ "Full URI (containing key/code) to join a conversation") + schema + ) + instance ToSchema ConversationCode where schema = objectWithDocModifier "ConversationCode" (description ?~ "Contains conversation properties to update") - $ ConversationCode - <$> conversationKey - .= fieldWithDocModifier - "key" - (description ?~ "Stable conversation identifier") - schema - <*> conversationCode - .= fieldWithDocModifier - "code" - (description ?~ "Conversation code (random)") - schema - <*> conversationUri - .= maybe_ - ( optFieldWithDocModifier - "uri" - (description ?~ "Full URI (containing key/code) to join a conversation") - schema - ) + conversationCodeObjectSchema -mkConversationCode :: Code.Key -> Code.Value -> HttpsUrl -> ConversationCode -mkConversationCode k v (HttpsUrl prefix) = - ConversationCode - { conversationKey = k, - conversationCode = v, - conversationUri = Just (HttpsUrl link) - } +data ConversationCodeInfo = ConversationCodeInfo + { code :: ConversationCode, + hasPassword :: Bool + } + deriving stock (Eq, Show, Generic) + deriving (Arbitrary) via (GenericUniform ConversationCodeInfo) + deriving (FromJSON, ToJSON, S.ToSchema) via Schema ConversationCodeInfo + +instance ToSchema ConversationCodeInfo where + schema = + objectWithDocModifier + "ConversationCodeInfo" + (description ?~ "Contains conversation properties to update") + $ ConversationCodeInfo + <$> (.code) .= conversationCodeObjectSchema + <*> (.hasPassword) .= fieldWithDocModifier "has_password" (description ?~ "Whether the conversation has a password") schema + +mkConversationCodeInfo :: Bool -> Code.Key -> Code.Value -> HttpsUrl -> ConversationCodeInfo +mkConversationCodeInfo hasPw k v (HttpsUrl prefix) = + ConversationCodeInfo (ConversationCode k v (Just (HttpsUrl link))) hasPw where q = [("key", toByteString' k), ("code", toByteString' v)] link = prefix & (URI.queryL . URI.queryPairsL) .~ q diff --git a/libs/wire-api/src/Wire/API/Error/Galley.hs b/libs/wire-api/src/Wire/API/Error/Galley.hs index 5019282d33..790f6405ee 100644 --- a/libs/wire-api/src/Wire/API/Error/Galley.hs +++ b/libs/wire-api/src/Wire/API/Error/Galley.hs @@ -93,6 +93,8 @@ data GalleyError | ConvMemberNotFound | GuestLinksDisabled | CodeNotFound + | InvalidConversationPassword + | CreateConversationCodeConflict | InvalidPermissions | InvalidTeamStatusUpdate | AccessDenied @@ -235,6 +237,10 @@ type instance MapError 'GuestLinksDisabled = 'StaticError 409 "guest-links-disab type instance MapError 'CodeNotFound = 'StaticError 404 "no-conversation-code" "Conversation code not found" +type instance MapError 'InvalidConversationPassword = 'StaticError 403 "invalid-conversation-password" "Invalid conversation password" + +type instance MapError 'CreateConversationCodeConflict = 'StaticError 409 "create-conv-code-conflict" "Conversation code already exists with a different password setting than the requested one." + type instance MapError 'InvalidPermissions = 'StaticError 403 "invalid-permissions" "The specified permissions are invalid" type instance MapError 'InvalidTeamStatusUpdate = 'StaticError 403 "invalid-team-status-update" "Cannot use this endpoint to update the team to the given status." diff --git a/libs/wire-api/src/Wire/API/Event/Conversation.hs b/libs/wire-api/src/Wire/API/Event/Conversation.hs index 2b3f21610e..e831835eee 100644 --- a/libs/wire-api/src/Wire/API/Event/Conversation.hs +++ b/libs/wire-api/src/Wire/API/Event/Conversation.hs @@ -80,7 +80,7 @@ import Imports import qualified Test.QuickCheck as QC import URI.ByteString () import Wire.API.Conversation -import Wire.API.Conversation.Code (ConversationCode (..)) +import Wire.API.Conversation.Code (ConversationCode (..), ConversationCodeInfo) import Wire.API.Conversation.Role import Wire.API.Conversation.Typing import Wire.API.MLS.SubConversation @@ -168,7 +168,7 @@ data EventData | EdConvDelete | EdConvAccessUpdate ConversationAccessData | EdConvMessageTimerUpdate ConversationMessageTimerUpdate - | EdConvCodeUpdate ConversationCode + | EdConvCodeUpdate ConversationCodeInfo | EdConvCodeDelete | EdMemberUpdate MemberUpdateData | EdConversation Conversation @@ -321,7 +321,7 @@ memberUpdateDataObjectSchema = data AddCodeResult = CodeAdded Event - | CodeAlreadyExisted ConversationCode + | CodeAlreadyExisted ConversationCodeInfo data OtrMessage = OtrMessage { otrSender :: ClientId, @@ -430,7 +430,7 @@ instance S.ToSchema Event where -- MultiVerb instances instance - (ResponseType r1 ~ ConversationCode, ResponseType r2 ~ Event) => + (ResponseType r1 ~ ConversationCodeInfo, ResponseType r2 ~ Event) => AsUnion '[r1, r2] AddCodeResult where toUnion (CodeAlreadyExisted c) = Z (I c) diff --git a/libs/wire-api/src/Wire/API/MLS/Message.hs b/libs/wire-api/src/Wire/API/MLS/Message.hs index c70f736bfb..1787ceab4b 100644 --- a/libs/wire-api/src/Wire/API/MLS/Message.hs +++ b/libs/wire-api/src/Wire/API/MLS/Message.hs @@ -37,6 +37,7 @@ module Wire.API.MLS.Message MLSCipherTextSym0, MLSMessageSendingStatus (..), KnownFormatTag (..), + UnreachableUsers (..), verifyMessageSignature, mkSignedMessage, ) @@ -49,8 +50,10 @@ import Data.Binary import Data.Binary.Get import Data.Binary.Put import qualified Data.ByteArray as BA +import Data.Id import Data.Json.Util import Data.Kind +import Data.Qualified import Data.Schema import Data.Singletons.TH import qualified Data.Swagger as S @@ -315,10 +318,24 @@ instance SerialiseMLS (MessagePayload 'MLSPlainText) where -- so the next case is left as a stub serialiseMLS _ = pure () +newtype UnreachableUsers = UnreachableUsers {unreachableUsers :: [Qualified UserId]} + deriving stock (Eq, Show) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via Schema UnreachableUsers + deriving newtype (Semigroup, Monoid) + +instance ToSchema UnreachableUsers where + schema = + named "UnreachableUsers" $ + UnreachableUsers + <$> unreachableUsers + .= array schema + data MLSMessageSendingStatus = MLSMessageSendingStatus { mmssEvents :: [Event], - mmssTime :: UTCTimeMillis + mmssTime :: UTCTimeMillis, + mmssUnreachableUsers :: UnreachableUsers } + deriving (Eq, Show) deriving (A.ToJSON, A.FromJSON, S.ToSchema) via Schema MLSMessageSendingStatus instance ToSchema MLSMessageSendingStatus where @@ -335,6 +352,11 @@ instance ToSchema MLSMessageSendingStatus where "time" (description ?~ "The time of sending the message.") schema + <*> mmssUnreachableUsers + .= fieldWithDocModifier + "failed_to_send" + (description ?~ "List of federated users who could not be reached and did not receive the message") + schema verifyMessageSignature :: CipherSuiteTag -> Message 'MLSPlainText -> ByteString -> Bool verifyMessageSignature cs msg pubkey = diff --git a/libs/wire-api/src/Wire/API/Message.hs b/libs/wire-api/src/Wire/API/Message.hs index 204c99b519..4cbf9f436f 100644 --- a/libs/wire-api/src/Wire/API/Message.hs +++ b/libs/wire-api/src/Wire/API/Message.hs @@ -84,7 +84,7 @@ import qualified Proto.Otr_Fields as Proto.Otr import Servant (FromHttpApiData (..)) import qualified Wire.API.Message.Proto as Proto import Wire.API.ServantProto (FromProto (..), ToProto (..)) -import Wire.API.User.Client (QualifiedUserClientMap (QualifiedUserClientMap), QualifiedUserClients, UserClientMap (..), UserClients (..)) +import Wire.API.User.Client import Wire.Arbitrary (Arbitrary (..), GenericUniform (..)) -------------------------------------------------------------------------------- @@ -523,38 +523,42 @@ data MessageSendingStatus = MessageSendingStatus instance ToSchema MessageSendingStatus where schema = - object "MessageSendingStatus" $ - MessageSendingStatus - <$> mssTime - .= fieldWithDocModifier - "time" - (description ?~ "Time of sending message.") - schema - <*> mssMissingClients - .= fieldWithDocModifier - "missing" - (description ?~ "Clients that the message /should/ have been encrypted for, but wasn't.") - schema - <*> mssRedundantClients - .= fieldWithDocModifier - "redundant" - (description ?~ "Clients that the message /should not/ have been encrypted for, but was.") - schema - <*> mssDeletedClients - .= fieldWithDocModifier - "deleted" - (description ?~ "Clients that were deleted.") - schema - <*> mssFailedToSend - .= fieldWithDocModifier - "failed_to_send" - ( description - ?~ "When message sending fails for some clients but succeeds for others,\ - \this field will contain the list of clients for which the message sending \ - \failed. This list should be empty when message sending is not even tried, \ - \like when some clients are missing." - ) - schema + objectWithDocModifier + "MessageSendingStatus" + (description ?~ combinedDesc) + $ MessageSendingStatus + <$> mssTime .= field "time" schema + <*> mssMissingClients .= field "missing" schema + <*> mssRedundantClients .= field "redundant" schema + <*> mssDeletedClients .= field "deleted" schema + <*> mssFailedToSend .= field "failed_to_send" schema + where + combinedDesc = + "The Proteus message sending status. It has these fields:\n\ + \- `time`: " + <> timeDesc + <> "\n\ + \- `missing`: " + <> missingDesc + <> "\n\ + \- `redundant`: " + <> redundantDesc + <> "\n\ + \- `deleted`: " + <> deletedDesc + <> "\n\ + \- `failed_to_send`: " + <> failedToSendDesc + timeDesc = "Time of sending message." + missingDesc = "Clients that the message /should/ have been encrypted for, but wasn't." + redundantDesc = "Clients that the message /should not/ have been encrypted for, but was." + deletedDesc = "Clients that were deleted." + failedToSendDesc = + "When message sending fails for some clients but succeeds for others, \ + \e.g., because a remote backend is unreachable, \ + \this field will contain the list of clients for which the message sending \ + \failed. This list should be empty when message sending is not even tried, \ + \like when some clients are missing." -- QueryParams diff --git a/libs/wire-api/src/Wire/API/OAuth.hs b/libs/wire-api/src/Wire/API/OAuth.hs new file mode 100644 index 0000000000..21d8b3a85e --- /dev/null +++ b/libs/wire-api/src/Wire/API/OAuth.hs @@ -0,0 +1,699 @@ +-- 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 . +{-# LANGUAGE GeneralizedNewtypeDeriving #-} + +module Wire.API.OAuth where + +import Cassandra hiding (Set) +import Control.Lens (preview, view, (%~), (?~)) +import Control.Monad.Except +import Crypto.Hash as Crypto +import Crypto.JWT hiding (Context, params, uri, verify) +import qualified Data.Aeson.KeyMap as M +import qualified Data.Aeson.Types as A +import Data.ByteArray (convert) +import Data.ByteString.Conversion +import Data.ByteString.Lazy (toStrict) +import Data.Either.Combinators (mapLeft) +import qualified Data.HashMap.Strict as HM +import Data.Id as Id +import Data.Range +import Data.Schema +import qualified Data.Set as Set +import Data.String.Conversions (cs) +import Data.Swagger (ToParamSchema (..)) +import qualified Data.Swagger as S +import qualified Data.Text as T +import Data.Text.Ascii +import qualified Data.Text.Encoding as TE +import Data.Text.Encoding.Error as TErr +import Data.Time +import GHC.TypeLits (Nat, symbolVal) +import Imports hiding (exp, head) +import Prelude.Singletons (Show_) +import Servant hiding (Handler, JSON, Tagged, addHeader, respond) +import Servant.Swagger.Internal.Orphans () +import Test.QuickCheck (Arbitrary (..)) +import URI.ByteString +import qualified URI.ByteString.QQ as URI.QQ +import Web.FormUrlEncoded (Form (..), FromForm (..), ToForm (..), parseUnique) +import Wire.API.Error +import Wire.Arbitrary (GenericUniform (..)) + +-------------------------------------------------------------------------------- +-- Types + +newtype RedirectUrl = RedirectUrl {unRedirectUrl :: URIRef Absolute} + deriving (Eq, Show, Generic) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema RedirectUrl) + +addParams :: [(ByteString, ByteString)] -> RedirectUrl -> RedirectUrl +addParams ps (RedirectUrl uri) = uri & (queryL . queryPairsL) %~ (ps <>) & RedirectUrl + +instance ToParamSchema RedirectUrl where + toParamSchema _ = toParamSchema (Proxy @Text) + +instance ToByteString RedirectUrl where + builder = serializeURIRef . unRedirectUrl + +instance FromByteString RedirectUrl where + parser = RedirectUrl <$> uriParser strictURIParserOptions + +instance ToSchema RedirectUrl where + schema = + (TE.decodeUtf8 . serializeURIRef' . unRedirectUrl) + .= (RedirectUrl <$> parsedText "RedirectUrl" (runParser (uriParser strictURIParserOptions) . TE.encodeUtf8)) + +instance ToHttpApiData RedirectUrl where + toUrlPiece = TE.decodeUtf8With TErr.lenientDecode . toHeader + toHeader = serializeURIRef' . unRedirectUrl + +instance FromHttpApiData RedirectUrl where + parseUrlPiece = parseHeader . TE.encodeUtf8 + parseHeader = bimap (T.pack . show) RedirectUrl . parseURI strictURIParserOptions + +instance Arbitrary RedirectUrl where + arbitrary = pure $ RedirectUrl [URI.QQ.uri|https://example.com|] + +type OAuthApplicationNameMinLength = (6 :: Nat) + +type OAuthApplicationNameMaxLength = (256 :: Nat) + +newtype OAuthApplicationName = OAuthApplicationName {unOAuthApplicationName :: Range OAuthApplicationNameMinLength OAuthApplicationNameMaxLength Text} + deriving (Eq, Show, Generic, Ord, Arbitrary) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema OAuthApplicationName) + +instance ToSchema OAuthApplicationName where + schema = OAuthApplicationName <$> unOAuthApplicationName .= schema + +data RegisterOAuthClientRequest = RegisterOAuthClientRequest + { applicationName :: OAuthApplicationName, + redirectUrl :: RedirectUrl + } + deriving (Eq, Show, Generic) + deriving (Arbitrary) via (GenericUniform RegisterOAuthClientRequest) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema RegisterOAuthClientRequest) + +instance ToSchema RegisterOAuthClientRequest where + schema = + object "RegisterOAuthClientRequest" $ + RegisterOAuthClientRequest + <$> applicationName .= fieldWithDocModifier "application_name" applicationNameDescription schema + <*> (.redirectUrl) .= fieldWithDocModifier "redirect_url" redirectUrlDescription schema + where + applicationNameDescription = description ?~ "The name of the application. This will be shown to the user when they are asked to authorize the application. The name must be between " <> minL <> " and " <> maxL <> " characters long." + redirectUrlDescription = description ?~ "The URL to redirect to after the user has authorized the application." + minL = cs @String @Text $ symbolVal $ Proxy @(Show_ OAuthApplicationNameMinLength) + maxL = cs @String @Text $ symbolVal $ Proxy @(Show_ OAuthApplicationNameMaxLength) + +newtype OAuthClientPlainTextSecret = OAuthClientPlainTextSecret {unOAuthClientPlainTextSecret :: AsciiBase16} + deriving (Eq, Generic, Arbitrary) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema OAuthClientPlainTextSecret) + +instance Show OAuthClientPlainTextSecret where + show _ = "" + +instance ToSchema OAuthClientPlainTextSecret where + schema :: ValueSchema NamedSwaggerDoc OAuthClientPlainTextSecret + schema = (toText . unOAuthClientPlainTextSecret) .= parsedText "OAuthClientPlainTextSecret" (fmap OAuthClientPlainTextSecret . validateBase16) + +instance FromHttpApiData OAuthClientPlainTextSecret where + parseQueryParam = bimap cs OAuthClientPlainTextSecret . validateBase16 . cs + +instance ToHttpApiData OAuthClientPlainTextSecret where + toQueryParam = toText . unOAuthClientPlainTextSecret + +data OAuthClientCredentials = OAuthClientCredentials + { clientId :: OAuthClientId, + clientSecret :: OAuthClientPlainTextSecret + } + deriving (Eq, Show, Generic) + deriving (Arbitrary) via (GenericUniform OAuthClientCredentials) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema OAuthClientCredentials) + +instance ToSchema OAuthClientCredentials where + schema = + object "OAuthClientCredentials" $ + OAuthClientCredentials + <$> (.clientId) .= fieldWithDocModifier "client_id" clientIdDescription schema + <*> (.clientSecret) .= fieldWithDocModifier "client_secret" clientSecretDescription schema + where + clientIdDescription = description ?~ "The ID of the application." + clientSecretDescription = description ?~ "The secret of the application." + +data OAuthClient = OAuthClient + { clientId :: OAuthClientId, + name :: OAuthApplicationName, + redirectUrl :: RedirectUrl + } + deriving (Eq, Show, Generic) + deriving (Arbitrary) via (GenericUniform OAuthClient) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema OAuthClient) + +instance ToSchema OAuthClient where + schema = + object "OAuthClient" $ + OAuthClient + <$> (.clientId) .= field "client_id" schema + <*> (.name) .= field "application_name" schema + <*> (.redirectUrl) .= field "redirect_url" schema + +data OAuthResponseType = OAuthResponseTypeCode + deriving (Eq, Show, Generic) + deriving (Arbitrary) via (GenericUniform OAuthResponseType) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema OAuthResponseType) + +instance ToSchema OAuthResponseType where + schema :: ValueSchema NamedSwaggerDoc OAuthResponseType + schema = + enum @Text "OAuthResponseType" $ + mconcat + [ element "code" OAuthResponseTypeCode + ] + +-- | The OAuth scopes that are supported by the backend. +-- This type is a bit redundant and unfortunately has to be kept in sync +-- with the supported scopes defined in the nginx configs. +-- However, having this typed makes it easier to handle scopes in the backend, +-- and e.g. provide more meaningful error messages when the scope is invalid. +data OAuthScope + = ReadFeatureConfigs + | ReadSelf + | WriteConversations + | WriteConversationsCode + deriving (Eq, Show, Generic, Ord) + deriving (Arbitrary) via (GenericUniform OAuthScope) + +class IsOAuthScope scope where + toOAuthScope :: OAuthScope + +instance IsOAuthScope 'WriteConversations where + toOAuthScope = WriteConversations + +instance IsOAuthScope 'WriteConversationsCode where + toOAuthScope = WriteConversationsCode + +instance IsOAuthScope 'ReadSelf where + toOAuthScope = ReadSelf + +instance IsOAuthScope 'ReadFeatureConfigs where + toOAuthScope = ReadFeatureConfigs + +instance ToByteString OAuthScope where + builder = \case + WriteConversations -> "write:conversations" + WriteConversationsCode -> "write:conversations_code" + ReadSelf -> "read:self" + ReadFeatureConfigs -> "read:feature_configs" + +instance FromByteString OAuthScope where + parser = do + s <- parser + case T.toLower s of + "write:conversations" -> pure WriteConversations + "write:conversations_code" -> pure WriteConversationsCode + "read:self" -> pure ReadSelf + "read:feature_configs" -> pure ReadFeatureConfigs + _ -> fail "invalid scope" + +newtype OAuthScopes = OAuthScopes {unOAuthScopes :: Set OAuthScope} + deriving (Eq, Show, Generic, Monoid, Semigroup, Arbitrary) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema OAuthScopes) + +instance ToSchema OAuthScopes where + schema = OAuthScopes <$> (oauthScopesToText . unOAuthScopes) .= withParser schema oauthScopeParser + where + oauthScopesToText :: Set OAuthScope -> Text + oauthScopesToText = T.intercalate " " . fmap (cs . toByteString') . Set.toList + + oauthScopeParser :: Text -> A.Parser (Set OAuthScope) + oauthScopeParser scope = + pure $ (not . T.null) `filter` T.splitOn " " scope & maybe Set.empty Set.fromList . mapM (fromByteString' . cs) + +data CodeChallengeMethod = S256 + deriving (Eq, Show, Generic) + deriving (Arbitrary) via (GenericUniform CodeChallengeMethod) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema CodeChallengeMethod) + +instance ToSchema CodeChallengeMethod where + schema :: ValueSchema NamedSwaggerDoc CodeChallengeMethod + schema = + enum @Text "CodeChallengeMethod" $ + mconcat + [ element "S256" S256 + ] + +newtype OAuthCodeVerifier = OAuthCodeVerifier {unOAuthCodeVerifier :: Range 43 128 Text} + deriving (Eq, Show, Generic) + deriving (Arbitrary) via (GenericUniform OAuthCodeVerifier) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema OAuthCodeVerifier) + +instance ToSchema OAuthCodeVerifier where + schema :: ValueSchema NamedSwaggerDoc OAuthCodeVerifier + schema = OAuthCodeVerifier <$> unOAuthCodeVerifier .= schema + +instance FromHttpApiData OAuthCodeVerifier where + parseQueryParam = fmap OAuthCodeVerifier . mapLeft cs . checkedEither + +instance ToHttpApiData OAuthCodeVerifier where + toQueryParam = fromRange . unOAuthCodeVerifier + +newtype OAuthCodeChallenge = OAuthCodeChallenge {unOAuthCodeChallenge :: Text} + deriving (Eq, Show, Generic) + deriving (Arbitrary) via (GenericUniform OAuthCodeChallenge) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema OAuthCodeChallenge) + +instance ToSchema OAuthCodeChallenge where + schema = named "OAuthCodeChallenge" $ unOAuthCodeChallenge .= fmap OAuthCodeChallenge (unnamed schema) + +instance ToByteString OAuthCodeChallenge where + builder = builder . unOAuthCodeChallenge + +instance FromByteString OAuthCodeChallenge where + parser = OAuthCodeChallenge <$> parser + +verifyCodeChallenge :: OAuthCodeVerifier -> OAuthCodeChallenge -> Bool +verifyCodeChallenge verifier challenge = challenge == mkChallenge verifier + +mkChallenge :: OAuthCodeVerifier -> OAuthCodeChallenge +mkChallenge = + OAuthCodeChallenge + . toText + . encodeBase64UrlUnpadded + . convert + . Crypto.hash @ByteString @Crypto.SHA256 + . cs + . fromRange + . unOAuthCodeVerifier + +data CreateOAuthAuthorizationCodeRequest = CreateOAuthAuthorizationCodeRequest + { clientId :: OAuthClientId, + scope :: OAuthScopes, + responseType :: OAuthResponseType, + redirectUri :: RedirectUrl, + state :: Text, + codeChallengeMethod :: CodeChallengeMethod, + codeChallenge :: OAuthCodeChallenge + } + deriving (Eq, Show, Generic) + deriving (Arbitrary) via (GenericUniform CreateOAuthAuthorizationCodeRequest) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema CreateOAuthAuthorizationCodeRequest) + +instance ToSchema CreateOAuthAuthorizationCodeRequest where + schema = + object "CreateOAuthAuthorizationCodeRequest" $ + CreateOAuthAuthorizationCodeRequest + <$> (.clientId) .= fieldWithDocModifier "client_id" clientIdDescription schema + <*> (.scope) .= fieldWithDocModifier "scope" scopeDescription schema + <*> (.responseType) .= fieldWithDocModifier "response_type" responseTypeDescription schema + <*> (.redirectUri) .= fieldWithDocModifier "redirect_uri" redirectUriDescription schema + <*> (.state) .= fieldWithDocModifier "state" stateDescription schema + <*> (.codeChallengeMethod) .= fieldWithDocModifier "code_challenge_method" codeChallengeMethodDescription schema + <*> (.codeChallenge) .= fieldWithDocModifier "code_challenge" codeChallengeDescription schema + where + clientIdDescription = description ?~ "The ID of the OAuth client" + scopeDescription = description ?~ "The scopes which are requested to get authorization for, separated by a space" + responseTypeDescription = description ?~ "Indicates which authorization flow to use. Use `code` for authorization code flow." + redirectUriDescription = description ?~ "The URL to which to redirect the browser after authorization has been granted by the user." + stateDescription = description ?~ "An opaque value used by the client to maintain state between the request and callback. The authorization server includes this value when redirecting the user-agent back to the client. The parameter SHOULD be used for preventing cross-site request forgery" + codeChallengeMethodDescription = description ?~ "The method used to encode the code challenge. Only `S256` is supported." + codeChallengeDescription = description ?~ "Generated by the client from the code verifier (unpadded base64url-encoded SHA256 hash of the code verifier)" + +newtype OAuthAuthorizationCode = OAuthAuthorizationCode {unOAuthAuthorizationCode :: AsciiBase16} + deriving (Eq, Generic, Arbitrary) + +instance Show OAuthAuthorizationCode where + show _ = "" + +instance ToSchema OAuthAuthorizationCode where + schema = (toText . unOAuthAuthorizationCode) .= parsedText "OAuthAuthorizationCode" (fmap OAuthAuthorizationCode . validateBase16) + +instance ToByteString OAuthAuthorizationCode where + builder = builder . unOAuthAuthorizationCode + +instance FromByteString OAuthAuthorizationCode where + parser = OAuthAuthorizationCode <$> parser + +instance FromHttpApiData OAuthAuthorizationCode where + parseQueryParam = bimap cs OAuthAuthorizationCode . validateBase16 . cs + +instance ToHttpApiData OAuthAuthorizationCode where + toQueryParam = toText . unOAuthAuthorizationCode + +data OAuthGrantType = OAuthGrantTypeAuthorizationCode | OAuthGrantTypeRefreshToken + deriving (Eq, Show, Generic) + deriving (Arbitrary) via (GenericUniform OAuthGrantType) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema OAuthGrantType) + +instance ToSchema OAuthGrantType where + schema = + enum @Text "OAuthGrantType" $ + mconcat + [ element "authorization_code" OAuthGrantTypeAuthorizationCode, + element "refresh_token" OAuthGrantTypeRefreshToken + ] + +instance FromByteString OAuthGrantType where + parser = do + s <- parser + case T.toLower s of + "authorization_code" -> pure OAuthGrantTypeAuthorizationCode + "refresh_token" -> pure OAuthGrantTypeRefreshToken + _ -> fail "invalid OAuthGrantType" + +instance ToByteString OAuthGrantType where + builder = \case + OAuthGrantTypeAuthorizationCode -> "authorization_code" + OAuthGrantTypeRefreshToken -> "refresh_token" + +instance FromHttpApiData OAuthGrantType where + parseQueryParam = maybe (Left "invalid OAuthGrantType") pure . fromByteString . cs + +instance ToHttpApiData OAuthGrantType where + toQueryParam = cs . toByteString + +data OAuthAccessTokenRequest = OAuthAccessTokenRequest + { grantType :: OAuthGrantType, + clientId :: OAuthClientId, + codeVerifier :: OAuthCodeVerifier, + code :: OAuthAuthorizationCode, + redirectUri :: RedirectUrl + } + deriving (Eq, Show, Generic) + deriving (Arbitrary) via (GenericUniform OAuthAccessTokenRequest) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema OAuthAccessTokenRequest) + +instance ToSchema OAuthAccessTokenRequest where + schema = + object "OAuthAccessTokenRequest" $ + OAuthAccessTokenRequest + <$> (.grantType) .= fieldWithDocModifier "grant_type" grantTypeDescription schema + <*> (.clientId) .= fieldWithDocModifier "client_id" clientIdDescription schema + <*> (.codeVerifier) .= fieldWithDocModifier "code_verifier" codeVerifierDescription schema + <*> (.code) .= fieldWithDocModifier "code" codeDescription schema + <*> (.redirectUri) .= fieldWithDocModifier "redirect_uri" redirectUrlDescription schema + where + grantTypeDescription = description ?~ "Indicates which authorization flow to use. Use `authorization_code` for authorization code flow." + clientIdDescription = description ?~ "The ID of the OAuth client" + codeVerifierDescription = description ?~ "The code verifier to complete the code challenge" + codeDescription = description ?~ "The authorization code" + redirectUrlDescription = description ?~ "The URL must match the URL that was used to generate the authorization code." + +instance FromForm OAuthAccessTokenRequest where + fromForm f = + OAuthAccessTokenRequest + <$> parseUnique "grant_type" f + <*> parseUnique "client_id" f + <*> parseUnique "code_verifier" f + <*> parseUnique "code" f + <*> parseUnique "redirect_uri" f + +instance ToForm OAuthAccessTokenRequest where + toForm req = + Form $ + mempty + & HM.insert "grant_type" [toQueryParam (req.grantType)] + & HM.insert "client_id" [toQueryParam (req.clientId)] + & HM.insert "code_verifier" [toQueryParam (req.codeVerifier)] + & HM.insert "code" [toQueryParam (req.code)] + & HM.insert "redirect_uri" [toQueryParam (req.redirectUri)] + +data OAuthAccessTokenType = OAuthAccessTokenTypeBearer + deriving (Eq, Show, Generic) + deriving (Arbitrary) via (GenericUniform OAuthAccessTokenType) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema OAuthAccessTokenType) + +instance ToSchema OAuthAccessTokenType where + schema = + enum @Text "OAuthAccessTokenType" $ + mconcat + [ element "Bearer" OAuthAccessTokenTypeBearer + ] + +data TokenTag = Access | Refresh + +newtype OAuthToken a = OAuthToken {unOAuthToken :: SignedJWT} + deriving (Show, Eq, Generic) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via Schema (OAuthToken a) + +instance ToByteString (OAuthToken a) where + builder = builder . encodeCompact . unOAuthToken + +instance FromByteString (OAuthToken a) where + parser = do + t <- parser @Text + case decodeCompact (cs (TE.encodeUtf8 t)) of + Left (err :: JWTError) -> fail $ show err + Right jwt -> pure $ OAuthToken jwt + +instance ToHttpApiData (OAuthToken a) where + toHeader = toByteString' + toUrlPiece = cs . toHeader + +instance FromHttpApiData (OAuthToken a) where + parseHeader = either (Left . cs) pure . runParser parser . cs + parseUrlPiece = parseHeader . cs + +instance ToSchema (OAuthToken a) where + schema = (TE.decodeUtf8 . toByteString') .= withParser schema (either fail pure . runParser parser . cs) + +type OAuthAccessToken = OAuthToken 'Access + +type OAuthRefreshToken = OAuthToken 'Refresh + +data OAuthAccessTokenResponse = OAuthAccessTokenResponse + { accessToken :: OAuthAccessToken, + tokenType :: OAuthAccessTokenType, + expiresIn :: NominalDiffTime, + refreshToken :: OAuthRefreshToken + } + deriving (Eq, Show, Generic) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema OAuthAccessTokenResponse) + +instance ToSchema OAuthAccessTokenResponse where + schema = + object "OAuthAccessTokenResponse" $ + OAuthAccessTokenResponse + <$> accessToken .= fieldWithDocModifier "access_token" accessTokenDescription schema + <*> tokenType .= fieldWithDocModifier "token_type" tokenTypeDescription schema + <*> expiresIn .= fieldWithDocModifier "expires_in" expiresInDescription (fromIntegral <$> roundDiffTime .= schema) + <*> (.refreshToken) .= fieldWithDocModifier "refresh_token" refreshTokenDescription schema + where + roundDiffTime :: NominalDiffTime -> Int32 + roundDiffTime = round + accessTokenDescription = description ?~ "The access token, which has a relatively short lifetime" + tokenTypeDescription = description ?~ "The type of the access token. Currently only `Bearer` is supported." + expiresInDescription = description ?~ "The lifetime of the access token in seconds" + refreshTokenDescription = description ?~ "The refresh token, which has a relatively long lifetime, and can be used to obtain a new access token" + +data OAuthClaimsSet = OAuthClaimsSet {jwtClaims :: ClaimsSet, scope :: OAuthScopes} + deriving (Eq, Show, Generic) + +instance HasClaimsSet OAuthClaimsSet where + claimsSet f s = fmap (\a' -> s {jwtClaims = a'}) (f (jwtClaims s)) + +instance A.FromJSON OAuthClaimsSet where + parseJSON = A.withObject "OAuthClaimsSet" $ \o -> + OAuthClaimsSet + <$> A.parseJSON (A.Object o) + <*> o A..: "scope" + +instance A.ToJSON OAuthClaimsSet where + toJSON s = + ins "scope" (s.scope) (A.toJSON (jwtClaims s)) + where + ins k v (A.Object o) = A.Object $ M.insert k (A.toJSON v) o + ins _ _ a = a + +hcsSub :: HasClaimsSet hcs => hcs -> Maybe (Id a) +hcsSub = + view claimSub + >=> preview string + >=> either (const Nothing) pure . parseIdFromText + +-- | Verify a JWT and return the claims set. Use this function if you have a custom claims set. +verify :: JWK -> SignedJWT -> IO (Either JWTError OAuthClaimsSet) +verify key token = do + let audCheck = const True + runJOSE $ verifyJWT (defaultJWTValidationSettings audCheck) key token + +-- | Verify a JWT and return the claims set. Use this if you are using the default claims set. +verify' :: JWK -> SignedJWT -> IO (Either JWTError ClaimsSet) +verify' key token = do + let audCheck = const True + runJOSE (verifyClaims (defaultJWTValidationSettings audCheck) key token) + +data OAuthRefreshTokenInfo = OAuthRefreshTokenInfo + { refreshTokenId :: OAuthRefreshTokenId, + clientId :: OAuthClientId, + userId :: UserId, + scopes :: OAuthScopes, + createdAt :: UTCTime + } + deriving (Eq, Show, Generic) + +data OAuthRefreshAccessTokenRequest = OAuthRefreshAccessTokenRequest + { grantType :: OAuthGrantType, + clientId :: OAuthClientId, + refreshToken :: OAuthRefreshToken + } + deriving (Eq, Show, Generic) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema OAuthRefreshAccessTokenRequest) + +instance ToSchema OAuthRefreshAccessTokenRequest where + schema :: ValueSchema NamedSwaggerDoc OAuthRefreshAccessTokenRequest + schema = + object "OAuthRefreshAccessTokenRequest" $ + OAuthRefreshAccessTokenRequest + <$> (.grantType) .= fieldWithDocModifier "grant_type" grantTypeDescription schema + <*> (.clientId) .= fieldWithDocModifier "client_id" clientIdDescription schema + <*> (.refreshToken) .= fieldWithDocModifier "refresh_token" refreshTokenDescription schema + where + grantTypeDescription = description ?~ "The grant type. Must be `refresh_token`" + clientIdDescription = description ?~ "The OAuth client's ID" + refreshTokenDescription = description ?~ "The refresh token" + +instance FromForm OAuthRefreshAccessTokenRequest where + fromForm :: Form -> Either Text OAuthRefreshAccessTokenRequest + fromForm f = + OAuthRefreshAccessTokenRequest + <$> parseUnique "grant_type" f + <*> parseUnique "client_id" f + <*> parseUnique "refresh_token" f + +instance ToForm OAuthRefreshAccessTokenRequest where + toForm req = + Form $ + mempty + & HM.insert "grant_type" [toQueryParam (req.grantType)] + & HM.insert "client_id" [toQueryParam (req.clientId)] + & HM.insert "refresh_token" [toQueryParam (req.refreshToken)] + +instance FromForm (Either OAuthAccessTokenRequest OAuthRefreshAccessTokenRequest) where + fromForm :: Form -> Either Text (Either OAuthAccessTokenRequest OAuthRefreshAccessTokenRequest) + fromForm f = choose (fromForm @OAuthAccessTokenRequest f) (fromForm @OAuthRefreshAccessTokenRequest f) + where + choose :: Either Text OAuthAccessTokenRequest -> Either Text OAuthRefreshAccessTokenRequest -> Either Text (Either OAuthAccessTokenRequest OAuthRefreshAccessTokenRequest) + choose (Right a) _ = Right (Left a) + choose _ (Right a) = Right (Right a) + choose (Left err) _ = Left err + +data OAuthRevokeRefreshTokenRequest = OAuthRevokeRefreshTokenRequest + { clientId :: OAuthClientId, + refreshToken :: OAuthRefreshToken + } + deriving (Eq, Show, Generic) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema OAuthRevokeRefreshTokenRequest) + +instance ToSchema OAuthRevokeRefreshTokenRequest where + schema = + object "OAuthRevokeRefreshTokenRequest" $ + OAuthRevokeRefreshTokenRequest + <$> (.clientId) .= fieldWithDocModifier "client_id" clientIdDescription schema + <*> (.refreshToken) .= fieldWithDocModifier "refresh_token" refreshTokenDescription schema + where + clientIdDescription = description ?~ "The OAuth client's ID" + refreshTokenDescription = description ?~ "The refresh token" + +data OAuthApplication = OAuthApplication + { applicationId :: OAuthClientId, + name :: OAuthApplicationName + } + deriving (Eq, Show, Ord, Generic) + deriving (Arbitrary) via (GenericUniform OAuthApplication) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema OAuthApplication) + +instance ToSchema OAuthApplication where + schema = + object "OAuthApplication" $ + OAuthApplication + <$> applicationId .= fieldWithDocModifier "id" idDescription schema + <*> (.name) .= fieldWithDocModifier "name" nameDescription schema + where + idDescription = description ?~ "The OAuth client's ID" + nameDescription = description ?~ "The OAuth client's name" + +-------------------------------------------------------------------------------- +-- Errors + +data OAuthError + = OAuthClientNotFound + | OAuthRedirectUrlMissMatch + | OAuthUnsupportedResponseType + | OAuthJwtError + | OAuthAuthorizationCodeNotFound + | OAuthFeatureDisabled + | OAuthInvalidClientCredentials + | OAuthInvalidGrantType + | OAuthInvalidRefreshToken + | OAuthInvalidGrant + +instance KnownError (MapError e) => IsSwaggerError (e :: OAuthError) where + addToSwagger = addStaticErrorToSwagger @(MapError e) + +type instance MapError 'OAuthClientNotFound = 'StaticError 404 "not-found" "OAuth client not found" + +type instance MapError 'OAuthRedirectUrlMissMatch = 'StaticError 400 "redirect-url-miss-match" "The redirect URL does not match the one registered with the client" + +type instance MapError 'OAuthUnsupportedResponseType = 'StaticError 400 "unsupported-response-type" "Unsupported response type" + +type instance MapError 'OAuthJwtError = 'StaticError 500 "jwt-error" "Internal error while handling JWT token" + +type instance MapError 'OAuthAuthorizationCodeNotFound = 'StaticError 404 "not-found" "OAuth authorization code not found" + +type instance MapError 'OAuthFeatureDisabled = 'StaticError 403 "forbidden" "OAuth is disabled" + +type instance MapError 'OAuthInvalidClientCredentials = 'StaticError 403 "forbidden" "Invalid client credentials" + +type instance MapError 'OAuthInvalidGrantType = 'StaticError 403 "forbidden" "Invalid grant type" + +type instance MapError 'OAuthInvalidRefreshToken = 'StaticError 403 "forbidden" "Invalid refresh token" + +type instance MapError 'OAuthInvalidGrant = 'StaticError 403 "invalid_grant" "Invalid grant" + +-------------------------------------------------------------------------------- +-- CQL instances + +instance Cql OAuthApplicationName where + ctype = Tagged TextColumn + toCql = CqlText . fromRange . unOAuthApplicationName + fromCql (CqlText t) = checkedEither t <&> OAuthApplicationName + fromCql _ = Left "OAuthApplicationName: Text expected" + +instance Cql RedirectUrl where + ctype = Tagged BlobColumn + toCql = CqlBlob . toByteString + fromCql (CqlBlob t) = runParser parser (toStrict t) + fromCql _ = Left "RedirectUrl: Blob expected" + +instance Cql OAuthAuthorizationCode where + ctype = Tagged AsciiColumn + toCql = CqlAscii . toText . unOAuthAuthorizationCode + fromCql (CqlAscii t) = OAuthAuthorizationCode <$> validateBase16 t + fromCql _ = Left "OAuthAuthorizationCode: Ascii expected" + +instance Cql OAuthScope where + ctype = Tagged TextColumn + toCql = CqlText . cs . toByteString' + fromCql (CqlText t) = maybe (Left "invalid oauth scope") Right $ fromByteString' (cs t) + fromCql _ = Left "OAuthScope: Text expected" + +instance Cql OAuthCodeChallenge where + ctype = Tagged BlobColumn + toCql = CqlBlob . toByteString + fromCql (CqlBlob t) = runParser parser (toStrict t) + fromCql _ = Left "OAuthCodeChallenge: Blob expected" diff --git a/services/brig/src/Brig/Password.hs b/libs/wire-api/src/Wire/API/Password.hs similarity index 87% rename from services/brig/src/Brig/Password.hs rename to libs/wire-api/src/Wire/API/Password.hs index 9c52d9268e..349c5f5b72 100644 --- a/services/brig/src/Brig/Password.hs +++ b/libs/wire-api/src/Wire/API/Password.hs @@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Brig.Password +module Wire.API.Password ( Password, genPassword, mkSafePassword, @@ -27,7 +27,7 @@ import Cassandra import Crypto.Scrypt import qualified Data.ByteString.Base64 as B64 import Data.ByteString.Lazy (fromStrict, toStrict) -import Data.Misc (PlainTextPassword (..)) +import Data.Misc import qualified Data.Text.Encoding as Text import Imports import OpenSSL.Random (randBytes) @@ -49,20 +49,20 @@ instance Cql Password where -- | Generate a strong, random plaintext password of length 16 -- containing only alphanumeric characters, '+' and '/'. -genPassword :: MonadIO m => m PlainTextPassword +genPassword :: MonadIO m => m PlainTextPassword8 genPassword = - liftIO . fmap (PlainTextPassword . Text.decodeUtf8 . B64.encode) $ + liftIO . fmap (plainTextPassword8Unsafe . Text.decodeUtf8 . B64.encode) $ randBytes 12 -- | Stretch a plaintext password so that it can be safely stored. -mkSafePassword :: MonadIO m => PlainTextPassword -> m Password +mkSafePassword :: MonadIO m => PlainTextPassword' t -> m Password mkSafePassword = liftIO . fmap Password . encryptPassIO' . pass where pass = Pass . Text.encodeUtf8 . fromPlainTextPassword -- | Verify a plaintext password from user input against a stretched -- password from persistent storage. -verifyPassword :: PlainTextPassword -> Password -> Bool +verifyPassword :: PlainTextPassword' t -> Password -> Bool verifyPassword plain opaque = let actual = Pass . Text.encodeUtf8 $ fromPlainTextPassword plain expected = fromPassword opaque diff --git a/libs/wire-api/src/Wire/API/Provider.hs b/libs/wire-api/src/Wire/API/Provider.hs index 146517bf9c..67bec9b77c 100644 --- a/libs/wire-api/src/Wire/API/Provider.hs +++ b/libs/wire-api/src/Wire/API/Provider.hs @@ -56,7 +56,7 @@ import Data.Aeson import Data.Aeson.TH import Data.Id import Data.Json.Util -import Data.Misc (HttpsUrl (..), PlainTextPassword (..)) +import Data.Misc (HttpsUrl (..), PlainTextPassword6, PlainTextPassword8) import Data.Range import Imports import Wire.API.Conversation.Code as Code @@ -116,7 +116,7 @@ data NewProvider = NewProvider newProviderUrl :: HttpsUrl, newProviderDescr :: Range 1 1024 Text, -- | If none provided, a password is generated. - newProviderPassword :: Maybe PlainTextPassword + newProviderPassword :: Maybe PlainTextPassword6 } deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform NewProvider) @@ -145,7 +145,7 @@ data NewProviderResponse = NewProviderResponse { rsNewProviderId :: ProviderId, -- | The generated password, if none was provided -- in the 'NewProvider' request. - rsNewProviderPassword :: Maybe PlainTextPassword + rsNewProviderPassword :: Maybe PlainTextPassword8 } deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform NewProviderResponse) @@ -214,7 +214,7 @@ instance FromJSON ProviderActivationResponse where -- | Input data for a provider login request. data ProviderLogin = ProviderLogin { providerLoginEmail :: Email, - providerLoginPassword :: PlainTextPassword + providerLoginPassword :: PlainTextPassword6 } deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform ProviderLogin) @@ -237,7 +237,7 @@ instance FromJSON ProviderLogin where -- | Input data for a provider deletion request. newtype DeleteProvider = DeleteProvider - {deleteProviderPassword :: PlainTextPassword} + {deleteProviderPassword :: PlainTextPassword6} deriving stock (Eq, Show) deriving newtype (Arbitrary) @@ -265,7 +265,7 @@ deriveJSON toJSONFieldName ''PasswordReset data CompletePasswordReset = CompletePasswordReset { cpwrKey :: Code.Key, cpwrCode :: Code.Value, - cpwrPassword :: PlainTextPassword + cpwrPassword :: PlainTextPassword6 } deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform CompletePasswordReset) @@ -274,8 +274,8 @@ deriveJSON toJSONFieldName ''CompletePasswordReset -- | The payload for changing a password. data PasswordChange = PasswordChange - { cpOldPassword :: PlainTextPassword, - cpNewPassword :: PlainTextPassword + { cpOldPassword :: PlainTextPassword6, + cpNewPassword :: PlainTextPassword6 } deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform PasswordChange) diff --git a/libs/wire-api/src/Wire/API/Provider/Service.hs b/libs/wire-api/src/Wire/API/Provider/Service.hs index 484c5fe6c6..f32e0cfe80 100644 --- a/libs/wire-api/src/Wire/API/Provider/Service.hs +++ b/libs/wire-api/src/Wire/API/Provider/Service.hs @@ -60,7 +60,7 @@ import Data.ByteString.Conversion import Data.Id import Data.Json.Util ((#)) import Data.List1 (List1) -import Data.Misc (HttpsUrl (..), PlainTextPassword (..)) +import Data.Misc (HttpsUrl (..), PlainTextPassword6) import Data.PEM (PEM, pemParseBS, pemWriteLBS) import Data.Proxy import Data.Range (Range) @@ -419,7 +419,7 @@ instance FromJSON UpdateService where -- | Update service connection information. -- This operation requires re-authentication via password. data UpdateServiceConn = UpdateServiceConn - { updateServiceConnPassword :: PlainTextPassword, + { updateServiceConnPassword :: PlainTextPassword6, updateServiceConnUrl :: Maybe HttpsUrl, updateServiceConnKeys :: Maybe (Range 1 2 [ServiceKeyPEM]), updateServiceConnTokens :: Maybe (Range 1 2 [ServiceToken]), @@ -428,7 +428,7 @@ data UpdateServiceConn = UpdateServiceConn deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform UpdateServiceConn) -mkUpdateServiceConn :: PlainTextPassword -> UpdateServiceConn +mkUpdateServiceConn :: PlainTextPassword6 -> UpdateServiceConn mkUpdateServiceConn pw = UpdateServiceConn pw Nothing Nothing Nothing Nothing instance ToJSON UpdateServiceConn where @@ -455,7 +455,7 @@ instance FromJSON UpdateServiceConn where -- | Input data for a service deletion request. newtype DeleteService = DeleteService - {deleteServicePassword :: PlainTextPassword} + {deleteServicePassword :: PlainTextPassword6} deriving stock (Eq, Show) deriving newtype (Arbitrary) diff --git a/libs/wire-api/src/Wire/API/RawJson.hs b/libs/wire-api/src/Wire/API/RawJson.hs index 4039108673..5a3f621589 100644 --- a/libs/wire-api/src/Wire/API/RawJson.hs +++ b/libs/wire-api/src/Wire/API/RawJson.hs @@ -27,7 +27,7 @@ import Test.QuickCheck import Test.QuickCheck.Instances () -- | Wrap json content as plain 'LByteString' --- This type is intented to be used to receive json content as 'LText'. +-- This type is intended to be used to receive json content as 'LText'. -- Warning: There is no validation of the json content. It may be any string. newtype RawJson = RawJson {rawJsonBytes :: LByteString} deriving (Eq, Show) 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 0743610bb6..e6de219115 100644 --- a/libs/wire-api/src/Wire/API/Routes/Internal/Brig.hs +++ b/libs/wire-api/src/Wire/API/Routes/Internal/Brig.hs @@ -55,6 +55,7 @@ import Wire.API.MLS.KeyPackage import Wire.API.MakesFederatedCall import Wire.API.Routes.Internal.Brig.Connection import Wire.API.Routes.Internal.Brig.EJPD +import Wire.API.Routes.Internal.Brig.OAuth (OAuthAPI) import qualified Wire.API.Routes.Internal.Galley.TeamFeatureNoConfigMulti as Multi import Wire.API.Routes.MultiVerb import Wire.API.Routes.Named @@ -325,6 +326,7 @@ type API = :<|> TeamsAPI :<|> UserAPI :<|> AuthAPI + :<|> OAuthAPI ) type TeamsAPI = diff --git a/libs/wire-api/src/Wire/API/Routes/Internal/Brig/OAuth.hs b/libs/wire-api/src/Wire/API/Routes/Internal/Brig/OAuth.hs new file mode 100644 index 0000000000..a4e24b58ec --- /dev/null +++ b/libs/wire-api/src/Wire/API/Routes/Internal/Brig/OAuth.hs @@ -0,0 +1,39 @@ +-- 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 Wire.API.Routes.Internal.Brig.OAuth where + +import Servant (JSON) +import Servant hiding (Handler, JSON, Tagged, addHeader, respond) +import Servant.Swagger.Internal.Orphans () +import Wire.API.Error +import Wire.API.OAuth +import Wire.API.Routes.Named (Named (..)) + +-------------------------------------------------------------------------------- +-- API Internal + +type OAuthAPI = + Named + "create-oauth-client" + ( Summary "Register an OAuth client" + :> CanThrow 'OAuthFeatureDisabled + :> "oauth" + :> "clients" + :> ReqBody '[JSON] RegisterOAuthClientRequest + :> Post '[JSON] OAuthClientCredentials + ) diff --git a/libs/wire-api/src/Wire/API/Routes/Public.hs b/libs/wire-api/src/Wire/API/Routes/Public.hs index bd2222e736..c5708109b2 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public.hs @@ -30,16 +30,19 @@ module Wire.API.Routes.Public ZBot, ZConversation, ZProvider, + DescriptionOAuthScope, ) where -import Control.Lens ((<>~)) +import Control.Lens ((%~), (<>~)) +import Data.ByteString.Conversion (toByteString) import Data.Domain import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap import Data.Id as Id import Data.Kind import Data.Metrics.Servant import Data.Qualified +import Data.String.Conversions import Data.Swagger import GHC.Base (Symbol) import GHC.TypeLits (KnownSymbol) @@ -50,6 +53,7 @@ import Servant.API.Modifiers import Servant.Server.Internal.Delayed import Servant.Server.Internal.DelayedIO import Servant.Swagger (HasSwagger (toSwagger)) +import qualified Wire.API.OAuth as OAuth mapRequestArgument :: forall mods a b. @@ -251,3 +255,20 @@ instance RoutesToPaths api => RoutesToPaths (ZAuthServant ztype opts :> api) whe -- FUTUREWORK: Make a PR to the servant-swagger package with this instance instance ToSchema a => ToSchema (Headers ls a) where declareNamedSchema _ = declareNamedSchema (Proxy @a) + +data DescriptionOAuthScope (scope :: OAuth.OAuthScope) + +instance (HasSwagger api, OAuth.IsOAuthScope scope) => HasSwagger (DescriptionOAuthScope scope :> api) where + toSwagger _ = toSwagger (Proxy @api) & addScopeDescription + where + addScopeDescription :: Swagger -> Swagger + addScopeDescription = allOperations . description %~ Just . (<> "\nOAuth scope: `" <> cs (toByteString (OAuth.toOAuthScope @scope)) <> "`") . fold + +instance (HasServer api ctx) => HasServer (DescriptionOAuthScope scope :> api) ctx where + type ServerT (DescriptionOAuthScope scope :> api) m = ServerT api m + + route _ = route (Proxy @api) + hoistServerWithContext _ = hoistServerWithContext (Proxy @api) + +instance RoutesToPaths api => RoutesToPaths (DescriptionOAuthScope scope :> api) where + getRoutes = getRoutes @api 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 365c82621a..bb71588a62 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs @@ -49,12 +49,14 @@ import Wire.API.Error.Empty import Wire.API.MLS.KeyPackage import Wire.API.MLS.Servant import Wire.API.MakesFederatedCall +import Wire.API.OAuth import Wire.API.Properties import Wire.API.Routes.Bearer import Wire.API.Routes.Cookies import Wire.API.Routes.MultiVerb import Wire.API.Routes.Named import Wire.API.Routes.Public +import Wire.API.Routes.Public.Brig.OAuth (OAuthAPI) import Wire.API.Routes.Public.Util import Wire.API.Routes.QualifiedCapture import Wire.API.Routes.Version @@ -89,6 +91,7 @@ type BrigAPI = :<|> CallingAPI :<|> TeamsAPI :<|> SystemSettingsAPI + :<|> OAuthAPI brigSwagger :: Swagger brigSwagger = toSwagger (Proxy @BrigAPI) @@ -220,14 +223,26 @@ type UserAPI = :> QueryParam' [Optional, Strict, Description "Handles of users to fetch, min 1 and max 4 (the check for handles is rather expensive)"] "handles" (Range 1 4 (CommaSeparatedList Handle)) :> Get '[JSON] [UserProfile] ) + :<|> Named + "list-users-by-ids-or-handles" + ( Summary "List users" + :> Description "The 'qualified_ids' and 'qualified_handles' parameters are mutually exclusive." + :> MakesFederatedCall 'Brig "get-users-by-ids" + :> ZUser + :> From 'V4 + :> "list-users" + :> ReqBody '[JSON] ListUsersQuery + :> Post '[JSON] ListUsersById + ) :<|> -- See Note [ephemeral user sideeffect] Named - "list-users-by-ids-or-handles" + "list-users-by-ids-or-handles@V3" ( Summary "List users" :> Description "The 'qualified_ids' and 'qualified_handles' parameters are mutually exclusive." :> MakesFederatedCall 'Brig "get-users-by-ids" :> ZUser + :> Until 'V4 :> "list-users" :> ReqBody '[JSON] ListUsersQuery :> Post '[JSON] [UserProfile] @@ -259,6 +274,7 @@ type SelfAPI = Named "get-self" ( Summary "Get your own profile" + :> DescriptionOAuthScope 'ReadSelf :> ZUser :> "self" :> Get '[JSON] SelfProfile @@ -651,18 +667,33 @@ type PrekeyAPI = :> Post '[JSON] UserClientPrekeyMap ) :<|> Named - "get-multi-user-prekey-bundle-qualified" + "get-multi-user-prekey-bundle-qualified@v3" ( Summary "Given a map of domain to (map of user IDs to client IDs) return a \ \prekey for each one. You can't request information for more users than \ \maximum conversation size." :> MakesFederatedCall 'Brig "claim-multi-prekey-bundle" :> ZUser + :> Until 'V4 :> "users" :> "list-prekeys" :> ReqBody '[JSON] QualifiedUserClients :> Post '[JSON] QualifiedUserClientPrekeyMap ) + :<|> Named + "get-multi-user-prekey-bundle-qualified" + ( Summary + "Given a map of domain to (map of user IDs to client IDs) return a \ + \prekey for each one. You can't request information for more users than \ + \maximum conversation size." + :> MakesFederatedCall 'Brig "claim-multi-prekey-bundle" + :> ZUser + :> From 'V4 + :> "users" + :> "list-prekeys" + :> ReqBody '[JSON] QualifiedUserClients + :> Post '[JSON] QualifiedUserClientPrekeyMapV4 + ) type UserClientAPI = -- User Client API ---------------------------------------------------- @@ -1497,6 +1528,7 @@ type SystemSettingsAPI = :<|> Named "get-system-settings" ( Summary "Returns a curated set of system configuration settings for authorized users." + :> From 'V4 :> ZUser :> "system" :> "settings" diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Brig/OAuth.hs b/libs/wire-api/src/Wire/API/Routes/Public/Brig/OAuth.hs new file mode 100644 index 0000000000..2a37bd71c6 --- /dev/null +++ b/libs/wire-api/src/Wire/API/Routes/Public/Brig/OAuth.hs @@ -0,0 +1,160 @@ +-- 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 Wire.API.Routes.Public.Brig.OAuth where + +import Data.Id as Id +import Data.SOP +import Data.Swagger (Swagger) +import Imports hiding (exp, head) +import Servant (JSON) +import Servant hiding (Handler, JSON, Tagged, addHeader, respond) +import Servant.Swagger +import Servant.Swagger.Internal.Orphans () +import Wire.API.Error +import Wire.API.OAuth +import Wire.API.Routes.MultiVerb +import Wire.API.Routes.Named (Named (..)) +import Wire.API.Routes.Public + +type OAuthAPI = + Named + "get-oauth-client" + ( Summary "Get OAuth client information" + :> CanThrow 'OAuthFeatureDisabled + :> CanThrow 'OAuthClientNotFound + :> ZUser + :> "oauth" + :> "clients" + :> Capture' '[Description "The ID of the OAuth client"] "OAuthClientId" OAuthClientId + :> MultiVerb + 'GET + '[JSON] + '[ ErrorResponse 'OAuthClientNotFound, + Respond 200 "OAuth client found" OAuthClient + ] + (Maybe OAuthClient) + ) + :<|> Named + "create-oauth-auth-code" + ( Summary "Create an OAuth authorization code" + :> Description "Currently only supports the 'code' response type, which corresponds to the authorization code flow." + :> ZUser + :> "oauth" + :> "authorization" + :> "codes" + :> ReqBody '[JSON] CreateOAuthAuthorizationCodeRequest + :> MultiVerb + 'POST + '[JSON] + CreateOAuthAuthorizationCodeResponses + CreateOAuthCodeResponse + ) + :<|> Named + "create-oauth-access-token" + ( Summary "Create an OAuth access token" + :> Description "Obtain a new access token from an authorization code or a refresh token." + :> CanThrow 'OAuthJwtError + :> CanThrow 'OAuthAuthorizationCodeNotFound + :> CanThrow 'OAuthClientNotFound + :> CanThrow 'OAuthFeatureDisabled + :> CanThrow 'OAuthInvalidRefreshToken + :> CanThrow 'OAuthInvalidGrantType + :> CanThrow 'OAuthInvalidClientCredentials + :> CanThrow 'OAuthInvalidGrant + :> "oauth" + :> "token" + :> ReqBody '[FormUrlEncoded] (Either OAuthAccessTokenRequest OAuthRefreshAccessTokenRequest) + :> Post '[JSON] OAuthAccessTokenResponse + ) + :<|> Named + "revoke-oauth-refresh-token" + ( Summary "Revoke an OAuth refresh token" + :> Description "Revoke an access token." + :> CanThrow 'OAuthJwtError + :> CanThrow 'OAuthInvalidRefreshToken + :> CanThrow 'OAuthClientNotFound + :> "oauth" + :> "revoke" + :> ReqBody '[JSON] OAuthRevokeRefreshTokenRequest + :> Post '[JSON] () + ) + :<|> Named + "get-oauth-applications" + ( Summary "Get OAuth applications with account access" + :> Description "Get all OAuth applications with active account access for a user." + :> ZUser + :> "oauth" + :> "applications" + :> MultiVerb1 + 'GET + '[JSON] + (Respond 200 "OAuth applications found" [OAuthApplication]) + ) + :<|> Named + "revoke-oauth-account-access" + ( Summary "Revoke account access from an OAuth application" + :> ZUser + :> "oauth" + :> "applications" + :> Capture' '[Description "The ID of the OAuth client"] "OAuthClientId" OAuthClientId + :> MultiVerb + 'DELETE + '[JSON] + '[RespondEmpty 204 "OAuth application access revoked"] + () + ) + +type CreateOAuthAuthorizationCodeHeaders = '[Header "Location" RedirectUrl] + +type CreateOAuthAuthorizationCodeResponses = + '[ -- success + WithHeaders CreateOAuthAuthorizationCodeHeaders RedirectUrl (RespondEmpty 201 "Created"), + -- feature disabled + WithHeaders CreateOAuthAuthorizationCodeHeaders RedirectUrl (RespondEmpty 403 "Forbidden"), + -- unsupported response type + WithHeaders CreateOAuthAuthorizationCodeHeaders RedirectUrl (RespondEmpty 400 "Bad Request"), + -- client not found + WithHeaders CreateOAuthAuthorizationCodeHeaders RedirectUrl (RespondEmpty 404 "Not Found"), + -- redirect url mismatch + ErrorResponse 'OAuthRedirectUrlMissMatch + ] + +data CreateOAuthCodeResponse + = CreateOAuthCodeSuccess RedirectUrl + | CreateOAuthCodeFeatureDisabled RedirectUrl + | CreateOAuthCodeUnsupportedResponseType RedirectUrl + | CreateOAuthCodeClientNotFound RedirectUrl + | CreateOAuthCodeRedirectUrlMissMatch + +instance AsUnion CreateOAuthAuthorizationCodeResponses CreateOAuthCodeResponse where + toUnion :: CreateOAuthCodeResponse -> Union (ResponseTypes CreateOAuthAuthorizationCodeResponses) + toUnion (CreateOAuthCodeSuccess url) = Z (I url) + toUnion (CreateOAuthCodeFeatureDisabled url) = S (Z (I url)) + toUnion (CreateOAuthCodeUnsupportedResponseType url) = S (S (Z (I url))) + toUnion (CreateOAuthCodeClientNotFound url) = S (S (S (Z (I url)))) + toUnion CreateOAuthCodeRedirectUrlMissMatch = S (S (S (S (Z (I (dynError @(MapError 'OAuthRedirectUrlMissMatch))))))) + fromUnion :: Union (ResponseTypes CreateOAuthAuthorizationCodeResponses) -> CreateOAuthCodeResponse + fromUnion (Z (I url)) = CreateOAuthCodeSuccess url + fromUnion (S (Z (I url))) = CreateOAuthCodeFeatureDisabled url + fromUnion (S (S (Z (I url)))) = CreateOAuthCodeUnsupportedResponseType url + fromUnion (S (S (S (Z (I url))))) = CreateOAuthCodeClientNotFound url + fromUnion (S (S (S (S (Z (I _)))))) = CreateOAuthCodeRedirectUrlMissMatch + fromUnion (S (S (S (S (S x))))) = case x of {} + +swaggerDoc :: Swagger +swaggerDoc = toSwagger (Proxy @OAuthAPI) diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs b/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs index 908c5c88eb..1bc6db5988 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs @@ -21,10 +21,12 @@ import qualified Data.Code as Code import Data.CommaSeparatedList import Data.Id import Data.Range +import Data.SOP (I (..), NS (..)) import Imports hiding (head) import Servant hiding (WithStatus) import Servant.Swagger.Internal.Orphans () import Wire.API.Conversation +import Wire.API.Conversation.Code import Wire.API.Conversation.Role import Wire.API.Conversation.Typing import Wire.API.Error @@ -33,6 +35,7 @@ import Wire.API.Event.Conversation import Wire.API.MLS.PublicGroupState import Wire.API.MLS.Servant import Wire.API.MakesFederatedCall +import Wire.API.OAuth import Wire.API.Routes.MultiVerb import Wire.API.Routes.Named import Wire.API.Routes.Public @@ -44,6 +47,23 @@ import Wire.API.Team.Feature type ConversationResponse = ResponseForExistedCreated Conversation +-- | A type similar to 'ConversationResponse' introduced to allow for a failure +-- to add remote members while creating a conversation. +data CreateGroupConversationResponse + = GroupConversationExisted Conversation + | GroupConversationCreated CreateGroupConversation + +instance + (ResponseType r1 ~ Conversation, ResponseType r2 ~ CreateGroupConversation) => + AsUnion '[r1, r2] CreateGroupConversationResponse + where + toUnion (GroupConversationExisted x) = Z (I x) + toUnion (GroupConversationCreated x) = S (Z (I x)) + + fromUnion (Z (I x)) = GroupConversationExisted x + fromUnion (S (Z (I x))) = GroupConversationCreated x + fromUnion (S (S x)) = case x of {} + type ConversationHeaders = '[DescHeader "Location" "Conversation ID" ConvId] type ConversationVerb = @@ -61,6 +81,21 @@ type ConversationVerb = ] ConversationResponse +type CreateGroupConversationVerb = + MultiVerb + 'POST + '[JSON] + '[ WithHeaders + ConversationHeaders + Conversation + (Respond 200 "Conversation existed" Conversation), + WithHeaders + ConversationHeaders + CreateGroupConversation + (Respond 201 "Conversation created" CreateGroupConversation) + ] + CreateGroupConversationResponse + type ConversationV2Verb = MultiVerb 'POST @@ -80,7 +115,7 @@ type CreateConversationCodeVerb = MultiVerb 'POST '[JSON] - '[ Respond 200 "Conversation code already exists." ConversationCode, + '[ Respond 200 "Conversation code already exists." ConversationCodeInfo, Respond 201 "Conversation code created." Event ] AddCodeResult @@ -315,6 +350,7 @@ type ConversationAPI = "get-conversation-by-reusable-code" ( Summary "Get limited conversation information by key/code pair" :> CanThrow 'CodeNotFound + :> CanThrow 'InvalidConversationPassword :> CanThrow 'ConvNotFound :> CanThrow 'ConvAccessDenied :> CanThrow 'GuestLinksDisabled @@ -329,6 +365,7 @@ type ConversationAPI = :<|> Named "create-group-conversation@v2" ( Summary "Create a new conversation" + :> DescriptionOAuthScope 'WriteConversations :> MakesFederatedCall 'Galley "on-conversation-created" :> Until 'V3 :> CanThrow 'ConvAccessDenied @@ -342,16 +379,18 @@ type ConversationAPI = :> Description "This returns 201 when a new conversation is created, and 200 when the conversation already existed" :> ZLocalUser :> ZOptClient - :> ZConn + :> ZOptConn :> "conversations" :> VersionedReqBody 'V2 '[Servant.JSON] NewConv :> ConversationV2Verb ) :<|> Named - "create-group-conversation" + "create-group-conversation@v3" ( Summary "Create a new conversation" + :> DescriptionOAuthScope 'WriteConversations :> MakesFederatedCall 'Galley "on-conversation-created" :> From 'V3 + :> Until 'V4 :> CanThrow 'ConvAccessDenied :> CanThrow 'MLSMissingSenderClient :> CanThrow 'MLSNonEmptyMemberList @@ -363,11 +402,32 @@ type ConversationAPI = :> Description "This returns 201 when a new conversation is created, and 200 when the conversation already existed" :> ZLocalUser :> ZOptClient - :> ZConn + :> ZOptConn :> "conversations" :> ReqBody '[Servant.JSON] NewConv :> ConversationVerb ) + :<|> Named + "create-group-conversation" + ( Summary "Create a new conversation" + :> MakesFederatedCall 'Galley "on-conversation-created" + :> From 'V4 + :> CanThrow 'ConvAccessDenied + :> CanThrow 'MLSMissingSenderClient + :> CanThrow 'MLSNonEmptyMemberList + :> CanThrow 'MLSNotEnabled + :> CanThrow 'NotConnected + :> CanThrow 'NotATeamMember + :> CanThrow OperationDenied + :> CanThrow 'MissingLegalholdConsent + :> Description "This returns 201 when a new conversation is created, and 200 when the conversation already existed" + :> ZLocalUser + :> ZOptClient + :> ZOptConn + :> "conversations" + :> ReqBody '[Servant.JSON] NewConv + :> CreateGroupConversationVerb + ) :<|> Named "create-self-conversation@v2" ( Summary "Create a self-conversation" @@ -553,6 +613,7 @@ type ConversationAPI = :> MakesFederatedCall 'Galley "on-conversation-updated" :> MakesFederatedCall 'Galley "on-new-remote-conversation" :> CanThrow 'CodeNotFound + :> CanThrow 'InvalidConversationPassword :> CanThrow 'ConvAccessDenied :> CanThrow 'ConvNotFound :> CanThrow 'GuestLinksDisabled @@ -563,7 +624,7 @@ type ConversationAPI = :> ZConn :> "conversations" :> "join" - :> ReqBody '[Servant.JSON] ConversationCode + :> ReqBody '[Servant.JSON] JoinConversationByCode :> MultiVerb 'POST '[Servant.JSON] ConvJoinResponses (UpdateResult Event) ) :<|> Named @@ -574,6 +635,7 @@ type ConversationAPI = \Note that this is currently inconsistent (for backwards compatibility reasons) with `POST /conversations/join` which responds with 409 GuestLinksDisabled if guest links are disabled." :> CanThrow 'CodeNotFound :> CanThrow 'ConvNotFound + :> CanThrow 'InvalidConversationPassword :> "conversations" :> "code-check" :> ReqBody '[Servant.JSON] ConversationCode @@ -585,17 +647,39 @@ type ConversationAPI = ) -- this endpoint can lead to the following events being sent: -- - ConvCodeUpdate event to members, if code didn't exist before + :<|> Named + "create-conversation-code-unqualified@v3" + ( Summary "Create or recreate a conversation code" + :> Until 'V4 + :> DescriptionOAuthScope 'WriteConversationsCode + :> CanThrow 'ConvAccessDenied + :> CanThrow 'ConvNotFound + :> CanThrow 'GuestLinksDisabled + :> CanThrow 'CreateConversationCodeConflict + :> ZUser + :> ZOptConn + :> "conversations" + :> Capture' '[Description "Conversation ID"] "cnv" ConvId + :> "code" + :> CreateConversationCodeVerb + ) + -- this endpoint can lead to the following events being sent: + -- - ConvCodeUpdate event to members, if code didn't exist before :<|> Named "create-conversation-code-unqualified" ( Summary "Create or recreate a conversation code" + :> From 'V4 + :> DescriptionOAuthScope 'WriteConversationsCode :> CanThrow 'ConvAccessDenied :> CanThrow 'ConvNotFound :> CanThrow 'GuestLinksDisabled + :> CanThrow 'CreateConversationCodeConflict :> ZUser - :> ZConn + :> ZOptConn :> "conversations" :> Capture' '[Description "Conversation ID"] "cnv" ConvId :> "code" + :> ReqBody '[JSON] CreateConversationCodeRequest :> CreateConversationCodeVerb ) :<|> Named @@ -642,8 +726,8 @@ type ConversationAPI = :> MultiVerb 'GET '[JSON] - '[Respond 200 "Conversation Code" ConversationCode] - ConversationCode + '[Respond 200 "Conversation Code" ConversationCodeInfo] + ConversationCodeInfo ) -- This endpoint can lead to the following events being sent: -- - Typing event to members diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Galley/Feature.hs b/libs/wire-api/src/Wire/API/Routes/Public/Galley/Feature.hs index 370a2a3d0f..fab21cd2db 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Galley/Feature.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Galley/Feature.hs @@ -26,6 +26,7 @@ import Wire.API.Conversation.Role import Wire.API.Error import Wire.API.Error.Galley import Wire.API.MakesFederatedCall +import Wire.API.OAuth import Wire.API.Routes.MultiVerb import Wire.API.Routes.Named import Wire.API.Routes.Public @@ -216,6 +217,7 @@ type AllFeatureConfigsUserGet = :> Description "Gets feature configs for a user. If the user is a member of a team and has the required permissions, this will return the team's feature configs.\ \If the user is not a member of a team, this will return the personal feature configs (the server defaults)." + :> DescriptionOAuthScope 'ReadFeatureConfigs :> ZUser :> CanThrow 'NotATeamMember :> CanThrow OperationDenied diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Galley/Team.hs b/libs/wire-api/src/Wire/API/Routes/Public/Galley/Team.hs index 0a81f55c27..3d3571dd23 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Galley/Team.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Galley/Team.hs @@ -26,6 +26,7 @@ import Wire.API.Error.Galley import Wire.API.Routes.MultiVerb import Wire.API.Routes.Named import Wire.API.Routes.Public +import Wire.API.Routes.Version import Wire.API.Team import Wire.API.Team.Permission @@ -33,7 +34,7 @@ type TeamAPI = Named "create-non-binding-team" ( Summary "Create a new non binding team" - -- FUTUREWORK: deprecated in https://github.com/wireapp/wire-server/pull/2607 + :> Until 'V4 :> ZUser :> ZConn :> CanThrow 'NotConnected @@ -69,7 +70,7 @@ type TeamAPI = :<|> Named "get-teams" ( Summary "Get teams (deprecated); use `GET /teams/:tid`" - -- FUTUREWORK: deprecated in https://github.com/wireapp/wire-server/pull/2607 + :> Until 'V4 :> ZUser :> "teams" :> Get '[JSON] TeamList diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Galley/TeamMember.hs b/libs/wire-api/src/Wire/API/Routes/Public/Galley/TeamMember.hs index 7f9e99dcaa..205d51c2a8 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Galley/TeamMember.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Galley/TeamMember.hs @@ -31,6 +31,7 @@ import Wire.API.Routes.LowLevelStream import Wire.API.Routes.MultiVerb import Wire.API.Routes.Named import Wire.API.Routes.Public +import Wire.API.Routes.Version import Wire.API.Team.Member import qualified Wire.API.User as User @@ -96,7 +97,7 @@ type TeamMemberAPI = :<|> Named "add-team-member" ( Summary "Add a new team member" - -- FUTUREWORK: deprecated in https://github.com/wireapp/wire-server/pull/2607 + :> Until 'V4 :> CanThrow 'InvalidPermissions :> CanThrow 'NoAddToBinding :> CanThrow 'NotATeamMember @@ -142,7 +143,7 @@ type TeamMemberAPI = :<|> Named "delete-non-binding-team-member" ( Summary "Remove an existing team member" - -- FUTUREWORK: deprecated in https://github.com/wireapp/wire-server/pull/2607 + :> Until 'V4 :> CanThrow AuthenticationError :> CanThrow 'AccessDenied :> CanThrow 'TeamMemberNotFound diff --git a/libs/wire-api/src/Wire/API/Routes/Version.hs b/libs/wire-api/src/Wire/API/Routes/Version.hs index 0c4d4f2d4d..87327d699d 100644 --- a/libs/wire-api/src/Wire/API/Routes/Version.hs +++ b/libs/wire-api/src/Wire/API/Routes/Version.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE StandaloneKindSignatures #-} {-# LANGUAGE TemplateHaskell #-} -- This file is part of the Wire Server implementation. @@ -28,11 +29,9 @@ module Wire.API.Routes.Version -- * Version Version (..), + VersionNumber (..), supportedVersions, developmentVersions, - readVersionNumber, - mkVersion, - toPathComponent, -- * Servant combinators Until, @@ -40,11 +39,13 @@ module Wire.API.Routes.Version ) where +import Control.Error (note) import Control.Lens ((?~)) import Data.Aeson (FromJSON, ToJSON (..)) import qualified Data.Aeson as Aeson import Data.Bifunctor -import Data.ByteString.Conversion (ToByteString (builder)) +import qualified Data.Binary.Builder as Builder +import Data.ByteString.Conversion (ToByteString (builder), toByteString') import qualified Data.ByteString.Lazy as LBS import Data.Domain import Data.Schema @@ -57,50 +58,87 @@ import Servant import Servant.Swagger import Wire.API.Routes.Named import Wire.API.VersionInfo +import Wire.Arbitrary (Arbitrary, GenericUniform (GenericUniform)) --- | Version of the public API. +-- | Version of the public API. Serializes to `"v"`. See 'VersionNumber' below for one +-- that serializes to ``. See `/libs/wire-api/test/unit/Test/Wire/API/Routes/Version.hs` +-- for serialization rules. +-- +-- If you add or remove versions from this type, make sure 'versionInt', 'supportedVersions', +-- and 'developmentVersions' stay in sync; everything else here should keep working without +-- change. See also documentation in the *docs* directory. +-- https://docs.wire.com/developer/developer/api-versioning.html#version-bump-checklist data Version = V0 | V1 | V2 | V3 | V4 - deriving stock (Eq, Ord, Bounded, Enum, Show) + deriving stock (Eq, Ord, Bounded, Enum, Show, Generic) deriving (FromJSON, ToJSON) via (Schema Version) + deriving (Arbitrary) via (GenericUniform Version) + +-- | Manual enumeration of version integrals (the `` in the constructor `V`). +-- +-- This is not the same as 'fromEnum': we will remove unsupported versions in the future, +-- which will cause `` and `fromEnum V` to diverge. `Enum` should not be understood as +-- a bijection between meaningful integers and versions, but merely as a convenient way to say +-- `allVersions = [minBound..]`. +versionInt :: Integral i => Version -> i +versionInt V0 = 0 +versionInt V1 = 1 +versionInt V2 = 2 +versionInt V3 = 3 +versionInt V4 = 4 + +supportedVersions :: [Version] +supportedVersions = [minBound .. V4] + +developmentVersions :: [Version] +developmentVersions = [V4] + +---------------------------------------------------------------------- + +versionText :: Version -> Text +versionText = ("v" <>) . toUrlPiece . versionInt @Int + +versionByteString :: Version -> ByteString +versionByteString = ("v" <>) . toByteString' . versionInt @Int instance ToSchema Version where - schema = - enum @Integer "Version" . mconcat $ - [ element 0 V0, - element 1 V1, - element 2 V2, - element 3 V3, - element 4 V4 - ] - -mkVersion :: Integer -> Maybe Version -mkVersion n = case Aeson.fromJSON (Aeson.Number (fromIntegral n)) of - Aeson.Error _ -> Nothing - Aeson.Success v -> pure v + schema = enum @Text "Version" . mconcat $ (\v -> element (versionText v) v) <$> [minBound ..] instance FromHttpApiData Version where - parseHeader = first Text.pack . Aeson.eitherDecode . LBS.fromStrict - parseUrlPiece = parseHeader . Text.encodeUtf8 + parseQueryParam v = note ("Unknown version: " <> v) $ + getAlt $ + flip foldMap [minBound ..] $ \s -> + guard (versionText s == v) $> s instance ToHttpApiData Version where - toHeader = LBS.toStrict . Aeson.encode - toUrlPiece = Text.decodeUtf8 . toHeader + toHeader = versionByteString + toUrlPiece = versionText instance ToByteString Version where - builder = toEncodedUrlPiece + builder = Builder.fromByteString . versionByteString + +-- | Wrapper around 'Version' that serializes to integers ``, as needed in +-- eg. `VersionInfo`. See `/libs/wire-api/test/unit/Test/Wire/API/Routes/Version.hs` for +-- serialization rules. +newtype VersionNumber = VersionNumber {fromVersionNumber :: Version} + deriving stock (Eq, Ord, Show, Generic) + deriving newtype (Bounded, Enum) + deriving (FromJSON, ToJSON) via (Schema VersionNumber) + deriving (Arbitrary) via (GenericUniform Version) + +instance ToSchema VersionNumber where + schema = + enum @Integer "VersionNumber" . mconcat $ (\v -> element (versionInt v) (VersionNumber v)) <$> [minBound ..] --- | `Version` as it appears in an URL path --- --- >>> toPathComponent V1 --- "v1" -toPathComponent :: Version -> ByteString -toPathComponent v = "v" <> toHeader v +instance FromHttpApiData VersionNumber where + parseHeader = first Text.pack . Aeson.eitherDecode . LBS.fromStrict + parseUrlPiece = parseHeader . Text.encodeUtf8 -supportedVersions :: [Version] -supportedVersions = [minBound .. maxBound] +instance ToHttpApiData VersionNumber where + toHeader = LBS.toStrict . Aeson.encode + toUrlPiece = Text.decodeUtf8 . toHeader -developmentVersions :: [Version] -developmentVersions = [V4] +instance ToByteString VersionNumber where + builder = toEncodedUrlPiece -- | Information related to the public API version. -- @@ -109,8 +147,8 @@ developmentVersions = [V4] -- backend, in order to decide how to form request paths, and how to deal with -- federated backends and qualified user IDs. data VersionInfo = VersionInfo - { vinfoSupported :: [Version], - vinfoDevelopment :: [Version], + { vinfoSupported :: [VersionNumber], + vinfoDevelopment :: [VersionNumber], vinfoFederation :: Bool, vinfoDomain :: Domain } @@ -128,7 +166,7 @@ instance ToSchema VersionInfo where example :: VersionInfo example = VersionInfo - { vinfoSupported = supportedVersions, + { vinfoSupported = VersionNumber <$> supportedVersions, vinfoDevelopment = [maxBound], vinfoFederation = False, vinfoDomain = Domain "example.com" diff --git a/libs/wire-api/src/Wire/API/Routes/Version/Wai.hs b/libs/wire-api/src/Wire/API/Routes/Version/Wai.hs index 545acdeae4..3392a5a40e 100644 --- a/libs/wire-api/src/Wire/API/Routes/Version/Wai.hs +++ b/libs/wire-api/src/Wire/API/Routes/Version/Wai.hs @@ -15,38 +15,69 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Wire.API.Routes.Version.Wai where +module Wire.API.Routes.Version.Wai (versionMiddleware) where +import Control.Monad.Except (throwError) import Data.ByteString.Conversion -import qualified Data.Text.Lazy as LText +import Data.EitherR (fmapL) +import Data.String.Conversions (cs) +import qualified Data.Text as T import Imports import qualified Network.HTTP.Types as HTTP import Network.Wai import Network.Wai.Middleware.Rewrite import Network.Wai.Utilities.Error import Network.Wai.Utilities.Response +import Web.HttpApiData (parseUrlPiece, toUrlPiece) import Wire.API.Routes.Version -- | Strip off version prefix. Return 404 if the version is not supported. versionMiddleware :: Set Version -> Middleware versionMiddleware disabledAPIVersions app req k = case parseVersion (removeVersionHeader req) of - Nothing -> app req k - Just (req', n) -> case mkVersion n of - Just v | v `notElem` disabledAPIVersions -> app (addVersionHeader v req') k - _ -> - k $ - errorRs' $ - mkError HTTP.status404 "unsupported-version" $ - "Version " <> LText.pack (show n) <> " is not supported" - -parseVersion :: Request -> Maybe (Request, Integer) + Right (req', v) -> do + if v `elem` disabledAPIVersions && requestIsDisableable req' + then err (toUrlPiece v) + else app (addVersionHeader v req') k + Left (BadVersion v) -> err v + Left NoVersion -> app req k + Left InternalApisAreUnversioned -> errint + where + err :: Text -> IO ResponseReceived + err v = + k . errorRs' . mkError HTTP.status404 "unsupported-version" $ + "Version " <> cs v <> " is not supported" + + errint :: IO ResponseReceived + errint = + k . errorRs' . mkError HTTP.status404 "unsupported-version" $ + "Internal APIs (`/i/...`) are not under version control" + +data ParseVersionError = NoVersion | BadVersion Text | InternalApisAreUnversioned + +parseVersion :: Request -> Either ParseVersionError (Request, Version) parseVersion req = do (version, pinfo) <- case pathInfo req of - [] -> Nothing - (x : xs) -> pure (x, xs) - n <- readVersionNumber version + [] -> throwError NoVersion + (x : xs) -> do + unless (looksLikeVersion x) $ + throwError NoVersion + case xs of + ("i" : _) -> throwError InternalApisAreUnversioned + ("api-internal" : _) -> throwError InternalApisAreUnversioned + _ -> pure (x, xs) + n <- fmapL (const $ BadVersion version) $ parseUrlPiece version pure (rewriteRequestPure (\(_, q) _ -> (pinfo, q)) req, n) +looksLikeVersion :: Text -> Bool +looksLikeVersion version = case T.splitAt 1 version of (h, t) -> h == "v" && T.all isDigit t + +-- | swagger-delivering end-points are not disableable: they should work for all versions. +requestIsDisableable :: Request -> Bool +requestIsDisableable (pathInfo -> path) = case path of + ["api", "swagger-ui"] -> False + ["api", "swagger.json"] -> False + _ -> True + removeVersionHeader :: Request -> Request removeVersionHeader req = req diff --git a/libs/wire-api/src/Wire/API/Team.hs b/libs/wire-api/src/Wire/API/Team.hs index adae16e742..558ad85fed 100644 --- a/libs/wire-api/src/Wire/API/Team.hs +++ b/libs/wire-api/src/Wire/API/Team.hs @@ -75,7 +75,7 @@ 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.Misc (PlainTextPassword6) import Data.Range import Data.Schema import qualified Data.Swagger as S @@ -107,15 +107,18 @@ newTeam tid uid nme ico tb = Team tid uid nme ico Nothing tb DefaultIcon instance ToSchema Team where schema = - object "Team" $ + objectWithDocModifier "Team" desc $ Team <$> _teamId .= field "id" schema <*> _teamCreator .= field "creator" schema <*> _teamName .= field "name" schema <*> _teamIcon .= field "icon" schema <*> _teamIconKey .= maybe_ (optField "icon_key" schema) - <*> _teamBinding .= (fromMaybe Binding <$> optField "binding" schema) + <*> _teamBinding .= (fromMaybe Binding <$> optFieldWithDocModifier "binding" bindingDesc schema) <*> _teamSplashScreen .= (fromMaybe DefaultIcon <$> optField "splash_screen" schema) + where + desc = description ?~ "`binding` is deprecated, and should be ignored. The non-binding teams API is not used (and will not be supported from API version V4 onwards), and `binding` will always be `true`." + bindingDesc = description ?~ "Deprecated, please ignore." -- | How a team "binds" its members (users) -- @@ -290,7 +293,7 @@ instance ToSchema TeamUpdateData where -- TeamDeleteData data TeamDeleteData = TeamDeleteData - { _tdAuthPassword :: Maybe PlainTextPassword, + { _tdAuthPassword :: Maybe PlainTextPassword6, _tdVerificationCode :: Maybe Code.Value } deriving stock (Eq, Show) @@ -299,10 +302,10 @@ data TeamDeleteData = TeamDeleteData instance Arbitrary TeamDeleteData where arbitrary = TeamDeleteData <$> arbitrary <*> arbitrary -newTeamDeleteData :: Maybe PlainTextPassword -> TeamDeleteData +newTeamDeleteData :: Maybe PlainTextPassword6 -> TeamDeleteData newTeamDeleteData = flip TeamDeleteData Nothing -newTeamDeleteDataWithCode :: Maybe PlainTextPassword -> Maybe Code.Value -> TeamDeleteData +newTeamDeleteDataWithCode :: Maybe PlainTextPassword6 -> Maybe Code.Value -> TeamDeleteData newTeamDeleteDataWithCode = TeamDeleteData instance ToSchema TeamDeleteData where diff --git a/libs/wire-api/src/Wire/API/Team/LegalHold.hs b/libs/wire-api/src/Wire/API/Team/LegalHold.hs index d6794ae92a..00701ed696 100644 --- a/libs/wire-api/src/Wire/API/Team/LegalHold.hs +++ b/libs/wire-api/src/Wire/API/Team/LegalHold.hs @@ -165,7 +165,7 @@ instance ToSchema UserLegalHoldStatusResponse where -- RemoveLegalHoldSettingsRequest data RemoveLegalHoldSettingsRequest = RemoveLegalHoldSettingsRequest - { rmlhsrPassword :: Maybe PlainTextPassword + { rmlhsrPassword :: Maybe PlainTextPassword6 } deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform RemoveLegalHoldSettingsRequest) @@ -181,7 +181,7 @@ instance ToSchema RemoveLegalHoldSettingsRequest where -- DisableLegalHoldForUserRequest data DisableLegalHoldForUserRequest = DisableLegalHoldForUserRequest - { dlhfuPassword :: Maybe PlainTextPassword + { dlhfuPassword :: Maybe PlainTextPassword6 } deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform DisableLegalHoldForUserRequest) @@ -197,7 +197,7 @@ instance ToSchema DisableLegalHoldForUserRequest where -- ApproveLegalHoldForUserRequest data ApproveLegalHoldForUserRequest = ApproveLegalHoldForUserRequest - { alhfuPassword :: Maybe PlainTextPassword + { alhfuPassword :: Maybe PlainTextPassword6 } deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform ApproveLegalHoldForUserRequest) diff --git a/libs/wire-api/src/Wire/API/Team/Member.hs b/libs/wire-api/src/Wire/API/Team/Member.hs index 2fc27f12d5..7400617513 100644 --- a/libs/wire-api/src/Wire/API/Team/Member.hs +++ b/libs/wire-api/src/Wire/API/Team/Member.hs @@ -73,7 +73,7 @@ import Data.Id (UserId) import Data.Json.Util import Data.Kind import Data.LegalHold (UserLegalHoldStatus (..), defUserLegalHoldStatus) -import Data.Misc (PlainTextPassword (..)) +import Data.Misc (PlainTextPassword6) import Data.Proxy import Data.Schema import Data.Swagger (ToParamSchema (..)) @@ -390,7 +390,7 @@ instance ToSchema NewTeamMember where -- TeamMemberDeleteData newtype TeamMemberDeleteData = TeamMemberDeleteData - { _tmdAuthPassword :: Maybe PlainTextPassword + { _tmdAuthPassword :: Maybe PlainTextPassword6 } deriving stock (Eq, Show) deriving newtype (Arbitrary) @@ -401,7 +401,7 @@ instance ToSchema TeamMemberDeleteData where objectWithDocModifier "TeamMemberDeleteData" (description ?~ "Data for a team member deletion request in case of binding teams.") $ TeamMemberDeleteData <$> _tmdAuthPassword .= optFieldWithDocModifier "password" (description ?~ "The account password to authorise the deletion.") (maybeWithDefault Null schema) -newTeamMemberDeleteData :: Maybe PlainTextPassword -> TeamMemberDeleteData +newTeamMemberDeleteData :: Maybe PlainTextPassword6 -> TeamMemberDeleteData newTeamMemberDeleteData = TeamMemberDeleteData makeLenses ''TeamMember' diff --git a/libs/wire-api/src/Wire/API/User.hs b/libs/wire-api/src/Wire/API/User.hs index 77143b91ab..eda6949be3 100644 --- a/libs/wire-api/src/Wire/API/User.hs +++ b/libs/wire-api/src/Wire/API/User.hs @@ -20,7 +20,8 @@ -- with this program. If not, see . module Wire.API.User - ( UserIdList (..), + ( ListUsersById (..), + UserIdList (..), QualifiedUserIdList (..), LimitedQualifiedUserIdList (..), ScimUserInfo (..), @@ -130,7 +131,8 @@ import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap import Data.Id import Data.Json.Util (UTCTimeMillis, (#)) import Data.LegalHold (UserLegalHoldStatus) -import Data.Misc (PlainTextPassword (..)) +import Data.List.NonEmpty +import Data.Misc (PlainTextPassword6, PlainTextPassword8) import Data.Qualified import Data.Range import Data.SOP @@ -166,6 +168,21 @@ import Wire.API.User.Profile import Wire.API.User.RichInfo import Wire.Arbitrary (Arbitrary (arbitrary), GenericUniform (..)) +------- Paritial Successes +data ListUsersById = ListUsersById + { listUsersByIdFound :: [UserProfile], + listUsersByIdFailed :: Maybe (NonEmpty (Qualified UserId)) + } + deriving (Eq, Show) + deriving (ToJSON, FromJSON, S.ToSchema) via Schema ListUsersById + +instance ToSchema ListUsersById where + schema = + object "ListUsersById" $ + ListUsersById + <$> listUsersByIdFound .= field "found" (array schema) + <*> listUsersByIdFailed .= maybe_ (optField "failed" $ nonEmptyArray schema) + -------------------------------------------------------------------------------- -- UserIdList @@ -711,7 +728,7 @@ data NewUser = NewUser newUserOrigin :: Maybe NewUserOrigin, newUserLabel :: Maybe CookieLabel, newUserLocale :: Maybe Locale, - newUserPassword :: Maybe PlainTextPassword, + newUserPassword :: Maybe PlainTextPassword8, newUserExpiresIn :: Maybe ExpiresIn, newUserManagedBy :: Maybe ManagedBy } @@ -759,7 +776,7 @@ data NewUserRaw = NewUserRaw newUserRawTeamId :: Maybe TeamId, newUserRawLabel :: Maybe CookieLabel, newUserRawLocale :: Maybe Locale, - newUserRawPassword :: Maybe PlainTextPassword, + newUserRawPassword :: Maybe PlainTextPassword8, newUserRawExpiresIn :: Maybe ExpiresIn, newUserRawManagedBy :: Maybe ManagedBy } @@ -1130,8 +1147,8 @@ instance (res ~ PutSelfResponses) => AsUnion res (Maybe UpdateProfileError) wher -- | The payload for setting or changing a password. data PasswordChange = PasswordChange - { cpOldPassword :: Maybe PlainTextPassword, - cpNewPassword :: PlainTextPassword + { cpOldPassword :: Maybe PlainTextPassword6, + cpNewPassword :: PlainTextPassword8 } deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform PasswordChange) @@ -1326,7 +1343,7 @@ instance -- | Payload for requesting account deletion. newtype DeleteUser = DeleteUser - { deleteUserPassword :: Maybe PlainTextPassword + { deleteUserPassword :: Maybe PlainTextPassword6 } deriving stock (Eq, Show, Generic) deriving newtype (Arbitrary) @@ -1339,7 +1356,7 @@ instance ToSchema DeleteUser where <$> deleteUserPassword .= maybe_ (optField "password" schema) -mkDeleteUser :: Maybe PlainTextPassword -> DeleteUser +mkDeleteUser :: Maybe PlainTextPassword6 -> DeleteUser mkDeleteUser = DeleteUser instance ToJSON DeleteUser where diff --git a/libs/wire-api/src/Wire/API/User/Auth.hs b/libs/wire-api/src/Wire/API/User/Auth.hs index 8a5160daac..467672f40f 100644 --- a/libs/wire-api/src/Wire/API/User/Auth.hs +++ b/libs/wire-api/src/Wire/API/User/Auth.hs @@ -70,7 +70,7 @@ import Data.Code as Code import Data.Handle (Handle) import Data.Id import Data.Json.Util -import Data.Misc (PlainTextPassword (..)) +import Data.Misc (PlainTextPassword6) import Data.SOP import Data.Schema import qualified Data.Swagger as S @@ -312,7 +312,7 @@ data Login data PasswordLoginData = PasswordLoginData { plId :: LoginId, - plPassword :: PlainTextPassword, + plPassword :: PlainTextPassword6, plLabel :: Maybe CookieLabel, plCode :: Maybe Code.Value } @@ -371,7 +371,7 @@ loginLabel (SmsLogin sl) = slLabel sl -- RemoveCookies data RemoveCookies = RemoveCookies - { rmCookiesPassword :: PlainTextPassword, + { rmCookiesPassword :: PlainTextPassword6, rmCookiesLabels :: [CookieLabel], rmCookiesIdents :: [CookieId] } diff --git a/libs/wire-api/src/Wire/API/User/Auth/LegalHold.hs b/libs/wire-api/src/Wire/API/User/Auth/LegalHold.hs index 473c0c9d0e..56446aa0af 100644 --- a/libs/wire-api/src/Wire/API/User/Auth/LegalHold.hs +++ b/libs/wire-api/src/Wire/API/User/Auth/LegalHold.hs @@ -31,7 +31,7 @@ import Wire.API.User.Auth -- tokens. data LegalHoldLogin = LegalHoldLogin { lhlUserId :: !UserId, - lhlPassword :: !(Maybe PlainTextPassword), + lhlPassword :: !(Maybe PlainTextPassword6), lhlLabel :: !(Maybe CookieLabel) } deriving (FromJSON, ToJSON, S.ToSchema) via Schema LegalHoldLogin diff --git a/libs/wire-api/src/Wire/API/User/Auth/ReAuth.hs b/libs/wire-api/src/Wire/API/User/Auth/ReAuth.hs index b6c7e02e8c..ff3e8cec4e 100644 --- a/libs/wire-api/src/Wire/API/User/Auth/ReAuth.hs +++ b/libs/wire-api/src/Wire/API/User/Auth/ReAuth.hs @@ -33,7 +33,7 @@ import Wire.API.User -- | Certain operations might require reauth of the user. These are available -- only for users that have already set a password. data ReAuthUser = ReAuthUser - { reAuthPassword :: Maybe PlainTextPassword, + { reAuthPassword :: Maybe PlainTextPassword6, reAuthCode :: Maybe Value, reAuthCodeAction :: Maybe VerificationAction } diff --git a/libs/wire-api/src/Wire/API/User/Client.hs b/libs/wire-api/src/Wire/API/User/Client.hs index 230e926982..908b393804 100644 --- a/libs/wire-api/src/Wire/API/User/Client.hs +++ b/libs/wire-api/src/Wire/API/User/Client.hs @@ -32,6 +32,7 @@ module Wire.API.User.Client mkUserClientPrekeyMap, QualifiedUserClientMap (..), QualifiedUserClientPrekeyMap (..), + QualifiedUserClientPrekeyMapV4 (..), mkQualifiedUserClientPrekeyMap, qualifiedUserClientPrekeyMapFromList, UserClientsFull (..), @@ -39,6 +40,7 @@ module Wire.API.User.Client UserClients (..), mkUserClients, QualifiedUserClients (..), + qualifiedUserClientsValueSchema, filterClients, filterClientsFull, @@ -80,7 +82,7 @@ import Data.Domain (Domain) import Data.Id import Data.Json.Util import qualified Data.Map.Strict as Map -import Data.Misc (Latitude (..), Location, Longitude (..), PlainTextPassword (..), latitude, location, longitude) +import Data.Misc (Latitude (..), Location, Longitude (..), PlainTextPassword6, latitude, location, longitude) import Data.Qualified import Data.Schema import qualified Data.Set as Set @@ -278,8 +280,34 @@ qualifiedUserClientMapSchema sch = (schemaDoc innerSchema ^. Swagger.schema . Swagger.example) ) +data QualifiedUserClientPrekeyMapV4 = QualifiedUserClientPrekeyMapV4 + { qualifiedUserClientPrekeys :: QualifiedUserClientMap (Maybe Prekey), + failedToList :: Maybe [Qualified UserId] + } + deriving stock (Eq, Show) + deriving (FromJSON, ToJSON, Swagger.ToSchema) via Schema QualifiedUserClientPrekeyMapV4 + +instance Arbitrary QualifiedUserClientPrekeyMapV4 where + arbitrary = + QualifiedUserClientPrekeyMapV4 + <$> arbitrary + <*> arbitrary + +instance ToSchema QualifiedUserClientPrekeyMapV4 where + schema = + object "QualifiedUserClientPrekeyMapV4" $ + QualifiedUserClientPrekeyMapV4 + <$> fmap to' (from' .= field "qualified_user_client_prekeys" (map_ schema)) + <*> failedToList .= maybe_ (optField "failed_to_list" (array schema)) + where + from' :: QualifiedUserClientPrekeyMapV4 -> Map Domain UserClientPrekeyMap + from' = coerce . qualifiedUserClientPrekeys + to' :: Map Domain UserClientPrekeyMap -> QualifiedUserClientMap (Maybe Prekey) + to' = coerce + newtype QualifiedUserClientPrekeyMap = QualifiedUserClientPrekeyMap - {getQualifiedUserClientPrekeyMap :: QualifiedUserClientMap (Maybe Prekey)} + { getQualifiedUserClientPrekeyMap :: QualifiedUserClientMap (Maybe Prekey) + } deriving stock (Eq, Show) deriving newtype (Arbitrary) deriving (FromJSON, ToJSON, Swagger.ToSchema) via Schema QualifiedUserClientPrekeyMap @@ -401,9 +429,13 @@ instance Monoid QualifiedUserClients where instance Arbitrary QualifiedUserClients where arbitrary = QualifiedUserClients <$> mapOf' arbitrary (mapOf' arbitrary (setOf' arbitrary)) +qualifiedUserClientsValueSchema :: ValueSchema SwaggerDoc QualifiedUserClients +qualifiedUserClientsValueSchema = + QualifiedUserClients <$> qualifiedUserClients .= map_ (map_ (set schema)) + instance ToSchema QualifiedUserClients where schema = - addDoc . named "QualifiedUserClients" $ QualifiedUserClients <$> qualifiedUserClients .= map_ (map_ (set schema)) + addDoc . named "QualifiedUserClients" $ qualifiedUserClientsValueSchema where addDoc sch = sch @@ -558,7 +590,7 @@ data NewClient = NewClient newClientLabel :: Maybe Text, newClientClass :: Maybe ClientClass, newClientCookie :: Maybe CookieLabel, - newClientPassword :: Maybe PlainTextPassword, + newClientPassword :: Maybe PlainTextPassword6, newClientModel :: Maybe Text, newClientCapabilities :: Maybe (Set ClientCapability), newClientMLSPublicKeys :: MLSPublicKeys, @@ -701,7 +733,7 @@ instance ToSchema UpdateClient where -- RmClient newtype RmClient = RmClient - { rmPassword :: Maybe PlainTextPassword + { rmPassword :: Maybe PlainTextPassword6 } deriving stock (Eq, Show, Generic) deriving newtype (Arbitrary) diff --git a/libs/wire-api/src/Wire/API/User/Password.hs b/libs/wire-api/src/Wire/API/User/Password.hs index 555082c573..796be20b87 100644 --- a/libs/wire-api/src/Wire/API/User/Password.hs +++ b/libs/wire-api/src/Wire/API/User/Password.hs @@ -35,7 +35,7 @@ import Control.Lens ((?~)) import qualified Data.Aeson as A import Data.Aeson.Types (Parser) import Data.ByteString.Conversion -import Data.Misc (PlainTextPassword (..)) +import Data.Misc (PlainTextPassword8) import Data.Proxy (Proxy (Proxy)) import Data.Range (Ranged (..)) import Data.Schema as Schema @@ -103,7 +103,7 @@ instance ToSchema NewPasswordReset where data CompletePasswordReset = CompletePasswordReset { cpwrIdent :: PasswordResetIdentity, cpwrCode :: PasswordResetCode, - cpwrPassword :: PlainTextPassword + cpwrPassword :: PlainTextPassword8 } deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform CompletePasswordReset) @@ -195,7 +195,7 @@ newtype PasswordResetCode = PasswordResetCode data PasswordReset = PasswordReset { pwrCode :: PasswordResetCode, - pwrPassword :: PlainTextPassword + pwrPassword :: PlainTextPassword8 } deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform PasswordReset) diff --git a/libs/wire-api/src/Wire/API/User/Profile.hs b/libs/wire-api/src/Wire/API/User/Profile.hs index 864d068a36..9a4fbce416 100644 --- a/libs/wire-api/src/Wire/API/User/Profile.hs +++ b/libs/wire-api/src/Wire/API/User/Profile.hs @@ -71,7 +71,6 @@ import Wire.Arbitrary (Arbitrary (arbitrary), GenericUniform (..)) -- Name -- | Usually called display name. --- -- Length is between 1 and 128 characters. newtype Name = Name {fromName :: Text} diff --git a/libs/wire-api/src/Wire/API/User/Scim.hs b/libs/wire-api/src/Wire/API/User/Scim.hs index 27ce75cd20..589877d6dc 100644 --- a/libs/wire-api/src/Wire/API/User/Scim.hs +++ b/libs/wire-api/src/Wire/API/User/Scim.hs @@ -1,6 +1,5 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE DerivingVia #-} -{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} @@ -59,7 +58,7 @@ import Data.Handle (Handle) import Data.Id (ScimTokenId, TeamId, UserId) import Data.Json.Util ((#)) import qualified Data.Map as Map -import Data.Misc (PlainTextPassword) +import Data.Misc (PlainTextPassword6) import Data.Proxy import Data.String.Conversions (cs) import Data.Swagger hiding (Operation) @@ -380,7 +379,7 @@ data CreateScimToken = CreateScimToken { -- | Token description (as memory aid for whoever is creating the token) createScimTokenDescr :: !Text, -- | User password, which we ask for because creating a token is a "powerful" operation - createScimTokenPassword :: !(Maybe PlainTextPassword), + createScimTokenPassword :: !(Maybe PlainTextPassword6), -- | User code (sent by email), for 2nd factor to 'createScimTokenPassword' createScimTokenCode :: !(Maybe Code.Value) } diff --git a/libs/wire-api/src/Wire/API/VersionInfo.hs b/libs/wire-api/src/Wire/API/VersionInfo.hs index 7809b0411f..a50b3407cd 100644 --- a/libs/wire-api/src/Wire/API/VersionInfo.hs +++ b/libs/wire-api/src/Wire/API/VersionInfo.hs @@ -20,7 +20,6 @@ module Wire.API.VersionInfo vinfoObjectSchema, -- * Version utilities - readVersionNumber, versionHeader, VersionHeader, @@ -36,8 +35,6 @@ import qualified Data.CaseInsensitive as CI import Data.Metrics.Servant import Data.Schema import Data.Singletons -import qualified Data.Text as Text -import qualified Data.Text.Read as Text import GHC.TypeLits import Imports import qualified Network.Wai as Wai @@ -51,13 +48,6 @@ import Wire.API.Routes.ClientAlgebra vinfoObjectSchema :: ValueSchema NamedSwaggerDoc v -> ObjectSchema SwaggerDoc [v] vinfoObjectSchema sch = field "supported" (array sch) -readVersionNumber :: Text -> Maybe Integer -readVersionNumber v = do - ('v', rest) <- Text.uncons v - case Text.decimal rest of - Right (n, "") -> pure n - _ -> Nothing - type VersionHeader = "X-Wire-API-Version" versionHeader :: CI.CI ByteString 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 ef53079d9b..fd2de7dc6d 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 @@ -154,6 +154,7 @@ import qualified Test.Wire.API.Golden.Generated.PubClient_user import qualified Test.Wire.API.Golden.Generated.PushTokenList_user import qualified Test.Wire.API.Golden.Generated.PushToken_user import qualified Test.Wire.API.Golden.Generated.Push_2eToken_2eTransport_user +import qualified Test.Wire.API.Golden.Generated.QualifiedUserClientPrekeyMapV4_user import qualified Test.Wire.API.Golden.Generated.QueuedNotificationList_user import qualified Test.Wire.API.Golden.Generated.QueuedNotification_user import qualified Test.Wire.API.Golden.Generated.RTCConfiguration_user @@ -1438,5 +1439,11 @@ tests = (Test.Wire.API.Golden.Generated.Event_conversation.testObject_Event_conversation_9, "testObject_Event_conversation_9.json"), (Test.Wire.API.Golden.Generated.Event_conversation.testObject_Event_conversation_11, "testObject_Event_conversation_11.json"), (Test.Wire.API.Golden.Generated.Event_conversation.testObject_Event_conversation_10, "testObject_Event_conversation_10.json") + ], + testGroup "Golden: QualifiedUserClientPrekeyMapV4" $ + testObjects + [ (Test.Wire.API.Golden.Generated.QualifiedUserClientPrekeyMapV4_user.testObject_QualifiedUserClientPrekeyMapV4_user_1, "testObject_QualifiedUserClientPrekeyMapV4_1.json"), + (Test.Wire.API.Golden.Generated.QualifiedUserClientPrekeyMapV4_user.testObject_QualifiedUserClientPrekeyMapV4_user_2, "testObject_QualifiedUserClientPrekeyMapV4_2.json"), + (Test.Wire.API.Golden.Generated.QualifiedUserClientPrekeyMapV4_user.testObject_QualifiedUserClientPrekeyMapV4_user_3, "testObject_QualifiedUserClientPrekeyMapV4_3.json") ] ] diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/ApproveLegalHoldForUserRequest_team.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/ApproveLegalHoldForUserRequest_team.hs index 71ecb65e08..303539fccc 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/ApproveLegalHoldForUserRequest_team.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/ApproveLegalHoldForUserRequest_team.hs @@ -17,7 +17,7 @@ module Test.Wire.API.Golden.Generated.ApproveLegalHoldForUserRequest_team where -import Data.Misc (PlainTextPassword (PlainTextPassword)) +import Data.Misc import Imports (Maybe (Just, Nothing)) import Wire.API.Team.LegalHold (ApproveLegalHoldForUserRequest (..)) @@ -26,7 +26,7 @@ testObject_ApproveLegalHoldForUserRequest_team_1 = ApproveLegalHoldForUserRequest { alhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\GS\SYN\16721\DC3\1072491\&0\DEL\1064641\US5gln\EOTkXzCU\bi4_7\STX\STXSq\987235Yu\165532\141126.yA\"\EOTI!\1093626h\181665\1055060\83086\&7\1112546\995607im},F+\DC4D!J~\1017150\\T\NULT\DC4\SO(\FS@\r^\65446\CAN:.ou\FSC\1055156<5\\g.E9\ACK\1071979\&9\74180\42820\75029gOB'5*9}\STXI\1043337@r\ENQ\SUBZ\1016999\123596.'2w$\158770\&1\STX6\100594\DC2\SOH\ESCQ]\1057984n\f\NAKXK\1058825+\DC4v$d\NAKXQS\SOH\65880\FS\SOb\1036114TOG.{:k\47942e\1017359\1067966V\159215O\DEL*{\DC3\ESC\1099699Sm\176090\1106134\\A\1071724\174461N5\1002255 \SIyK\EM\EOT\1067214nc\55045\1046825/\ENQ\DLEG\54798^cy\RS\EOTTu?S=\167055\149685k\146840:$O\EOT\bk\SUBx\SUB\163179&b87\41130N\EOT\NUL\166531\tB*jHQ\"\\\189113ou{\SUB\1099314h\DC2\1106818H" ) } @@ -49,7 +49,7 @@ testObject_ApproveLegalHoldForUserRequest_team_4 = ApproveLegalHoldForUserRequest { alhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "l\SUB\992657\n3\1041620\US\SI\170968\NAK=W \1048582%T(%\EM5*M\55040\185510gD\1106035W\DC2f\\p8\CAN\1019346E#\33337MEn\NAKH7O\26360 wg|`\23458J\SI\DC1H|\ACK=\\\167687[i\59566\ACKq\SI\1092446\RSiW3A'\43742\1015508;\SOH9MN\1006945ub3\EOTCa<\DEL\1014083z2\154271S:\992695P\1003858q\1384oc$\179951AgO\169642>\nn\SYNi\ETBPP\USJ\1026192B\163736\1002872{d\SYN|c\983184V\167968\3442\1018054Nny(E\1065786_\100113Q\1063353\31589\165527JR\1010273\SOH\1032238@\NAKX\147135\994536\1033894v\GS\1099702\36074\1106850K\NUL\46458K%MB,\v+l/u2c\STXK\1021255\1004066\&9*4X]B\DC3i\n\CAN\42061\131535\&4\f\1003525\ACK\RSC52G?\DEL\f\f\ESC'b\a\SO\149819\SUBoeA\1091504\DEL\51717\66213\SO\DEL;\n|`r\1043851H?\1010037\1048200I\SI448\144084D[\43196RF\DC48I\917825W\NULU\1035653\DC2\68246\ENQ\9407UF\DC4C\\?\59742\ESC-\1072136*k_\1015908$K\NAK)BY{\1076932\CAN\190877\ENQ\NAKr\DLE:\a+7}\999357_d?\1051969<\ENQ\95728>=\1071374u\1024805\FS\"\1019026\1079552\DLE\1105908xg;3mnC,qn)ga6\SI\1037460\1071589\147516\nW\US\1024994\EOT\CAN\146533\RSH\1028407h\EM\EMh\70871X\185925Kh\181438\GSN\1057123\&8\1052016K\ETBr\157259xm\NAK:5[8\24201V\1069390\&6rON*po1\1057495ZsD&r.\138897aGj\1076763#\"s6\31558C\rH\31132>\1006611\&9\1077537\&1\146627r!\1002447`\137608\998415n\170089\SYN\SUB\SO\98091?~K]3u\96366\985577\\\ACK\1084517|X\1080668t\1061671uB\SUB\SUB\GSJ\149857\29777\DC4jpr\1002968~&7\CAN\1033765v\17096\RS=R\n\ETBs\999283\1105560iW~\134758}zR\rr1EzK\132679=d[\133713\&8L\1052369\DC1+{%\DC2!\SOH.B\151691\1049662\ETB\US\EM\131301j_\tz\154395\12893\ba\1008470;~X\63318`\a))7\1023497DB)#(\DC1\SOH7\tm\1061764\1025160L&\1040349u6Q\f\18516Dz\14251t\50987\DC2OtzQX;\ENQ\b\1037777\nj\SOh\ETX\ETX\"\1065307\53379yQj\153684Y\164029\NUL[\48925+Wf]m\DELH\156602\RS\182785gD~\RSJ:Ca\EOT#?_ ,\DC4\r\1061518,\18107q:" ) } @@ -59,7 +59,7 @@ testObject_ApproveLegalHoldForUserRequest_team_5 = ApproveLegalHoldForUserRequest { alhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "U\ESC\1043282Z2\170464\DC1\1023898\63881V\1039763\NAK/jl\a*\1006304\60419G\50221\NUL\1095161\1056946Q\ACK\160980}l| s\172304\EOTD*wg_\NULV\98251\ACK\1022610!\CAN!j\992107*@\37590B\US\146625!sz.h\GSb\1090351\1001366\STXgEkkG\SO\bK\SUB\17163u@l\141153\NAKZ(\39449\1060966\999470\CANs\1035897$\1054106\140429)ER\CAN&\181867\v\CAN\74574\STX)}o\167876\CAN`a3\EOT+c\126488tN2\GS\v\a\EM\1099582t\1062176\1008162\12125\187933U;\1101054?\ETBu,\n\b\986267\50322(\57598D*\STXT\DC3\EOTa`h_\986389\&5\"l^e\1040859O\49937\1049321K\SYN\131385\1026142\97070w\34413A\DC3@1$\144723Wv\15147\4230\999591?\DC4z;\2210 :\ETB\1027816n\1105106_\143378p>4\SOHy\1073906:\176861\173546\95595\STX\EM{\1057504\DC1\1111503Ka<}\172329BK\1098302O\128375\1029726j)\78225q*\145697U\SUBZ\1021039\1088250\181008\EOTd%`Ko\110827\n\rQ\1053059\SIk&\SO\1099552\ENQ\FSe.oe5fz5\SI\RSG#pd\158428@\ETX\DC1>" ) } @@ -69,7 +69,7 @@ testObject_ApproveLegalHoldForUserRequest_team_6 = ApproveLegalHoldForUserRequest { alhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "~\1079375{|\1033435Z63p\r(n\ACK.\19927X\51437X\994169\163593\&9*^kx\1087542M4\164906\1028784^lE\1005112\RS\DLE`c \25741I\SO\RS\ba\1033255\ETB\\\8341\174141$O\1079812\ETXi\78477 iP\128111u\1071588\1030101M=|\118921\&0oo\ESC\154180\1103643\SOWb\51090p(\96262<\DC1y\6488\990999C\92649\1055161:\18702n\129378 \v(\189578,%\138335\b-P#Uj(Fo\rEp\4270\1106150\160743\1075708cKv\ENQ\SO\NAKy<(&-D\14658C\162899rD\ruj!\98014\1010958A=\12414\62351=\78218\n@\t#\DEL5\CAN\US)\a^\DEL\v\SO\51760I!3\96224\1064338D'\DELO\DC3\t\135035\ty\1035916>\1001158h\183056\20117=Y\GSNv\b\DC3\v\1107945\CAN\31309\"}nT\US\11419S#/\DLE9\1050295S\f8Kn^mTJ\992736kV\1050390\165056 K(\\a\SI\15630\ACK~\1073657\f\1072834*A\EOT(\ACK\1093124\CAN\184920E\EOTYp\ETB~\174211A-%Ya<\EOT1\1054921\&94\1073042\51625\&4\ve\ESC|cN\1010527*z\1024856\&3>$\69849L.\37439\177492-{\991463j\RS\1045753\144709\1089699\988004G\19845\"\184823\1017291\995037\NUL\175697_w\31126\&8&#\78070-\182728\1055236\ESCfE\131500\&0VC6\ESC\DC4v\1111212^\ENQ\1080782o[\SIwDx\984971\&5k2*\1054497\STX\1045582Q'\RSMM*\36761O?\1092453\1056899h0\1029278m6\t1_2\1050427P\1003646\1073176\rau\EOT[VhQ\1108816\984373.\58623/o\ENQ\1073577\&3x\SOH\n$_\NULZT#ag;\1099654\GS\70119\NUL\119664\&2\147728a\DC3K\1074276S\49730*\6203\EOT\SIz" ) } @@ -79,7 +79,7 @@ testObject_ApproveLegalHoldForUserRequest_team_7 = ApproveLegalHoldForUserRequest { alhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "^4bA\983361\1074854\1095398\SYNc\1001825&|UH\1097125\fx\fKk9wj\ACK\NAK,\SOH3oA\131479n\1058909dV`\1009707\989352fL<9H\NUL\1105450R\992897\163873\1035255\&4\4407\r}mK\1011986 -\1091626\33261\"\173104\GS\NAKSUT\1099448\188736\CAN\1024643ttJZ.\142113T=|\1081589N4O\DLE.5&\37926[\38858\1109998\3844|M\1083479\EM\989076\EM\EM%\1060199eA,#f%\1035049!o\1102189\11792n^\156331\21324|PV^rJM,\22606\a\57787l\\\1088233e\995170\&1}3E\1025250\1017927\1097433TFn\999488\EM-\EOT5B*\1026822\1058807;UR\138415\SO\SUB\DEL\63442\NUL\991398bdvRAg\154601\143909R\1073816\1014504\120146E\FS\fjh8\40132\bjI%\ENQ\1105171*+\1089585$\US\989372\990101y>\119216\11708M\DC3W:\SI\185311\61677+\49171\1056839\NUL\21280\1003564xua[vR\186735(\NUL\n q{8C\1017383y\5814xfB\177684=\ETB\DC4\194622K\443\SYN\58975NO\10441Q\998062\SI$|OS\EMlh\n\1022077\NAKBx\1042908_1\DLET\39308\165249@\a3\SYNc\1062696qL!\1023079]\172572\147018E\1103791\FS\17549\47403\1098744JEj\4845\128687\1097200j\133008es\SOr\r\62229e{\1085980\18070;=\174117dA#\1056566=;\1058163\1103246*\1056212\DC1'\57676\&9\993847'0\SI(Y\SOmtJo\EOTo \1099200\48786n\DEL\994857Xz\1002598w\NAK\ETB\US\SYN\8934\1024958h7\985510\SI\STX'FLR\187478\DC4\37372(*3\158187^\4123\nt\GS\1050515lJ5!`;\NAKYX\ACK\97894\GS2m\ENQ\ACK#\29041\DELW$ksH\161206\DEL\97522z\ENQ\989313n\34975S\30544D\FS\b\EOT\"C\71368\STX\65347j\37649I\RS}\SI\RS`K\a\CANn'#" ) } @@ -89,7 +89,7 @@ testObject_ApproveLegalHoldForUserRequest_team_8 = ApproveLegalHoldForUserRequest { alhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "f$\1100810\175735o\1069961\159454\SIE\"y\156532G\44908\&9v#\ACK\181356m$ tPH'\CANZ&\1005288{@\147620fI\1011530\ESCS0\EOTZ}F\DC4 !\1074266\1045684\DC4\nm\ETB\1045670k\\\ETXuyA\SYNb\f\USS\1009030Wc\SI\8890\1013360[\10656\1096003\175620\74020D\ETB_\NAK\64782\1098454\GS;S\138229\67703\ACK\DC3C\63259\1002316yi\67204\7376'_4\USvM\1002300\ENQ\\\172382\1110125\143885Svk&(\162485/?\US#b\DLE\1090567\&4$?a9%Y([\96985S \SUB\CANe\STXtS\1028131\ACK\4595&\NULWB\1028337In\a<@!M\189998;\173714 D\SUB\1056876\&6\DC1B >pF\"7uiI&\143040\"\STX0\1111461\1047510V]R\1095197\CANn\149468\1042536\ACK;\GS\48880\fKS*o\NAKlC\SOH\STX\1057983\SUB3L-MG\b\93033\USo\EOT\ETB\134029M)B\16878x\4816\1047859A\163263\SUB>\131826\NAKGnW\51453;^\GS\154124Fr\\\60445\153198\DLE\EOT\1108521\ESC5p\nZ\SI#!\1025727[\156124SMH\DLE\1019412B_cqH5^8\167260\1050431\54880MD~l\96503.\190059" ) } @@ -99,7 +99,7 @@ testObject_ApproveLegalHoldForUserRequest_team_9 = ApproveLegalHoldForUserRequest { alhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "l\ESC9\SYNem\45307\10495~\DC2qM\NAK\DC1~\1005945o\NAK|\GSJ\139424\"\986732V$mZOGV\151997J}\ESC\95576!\SUB0\18594*\1001461\139783\1008537m\128716\ACKtO\f\1023598\157248u\26340\128450\&9\US]\64592\3654\ENQ\1113675\33829\1092986\&1,\64686)Q\ETX\"\140354q\1059341\1068651/U\\\EM\aQ\"s\1070528\NAK\64183>$H\1009468\&8cxi:\DC3\149939\49175\DC36)rq\SOH\1062910U\1113061\1061325Z\17706(\121397\63490\DELO\40230\&3x\ACKBg\SOH3\93780LwS\111173\ESCr\31750J\"\94880\STXA\DC3\1036900u\1051773\62273zX*9\NAK\983466`0M\EOT`\1103551\160876\&5\1023370X\99815$=\152547\f\f\1066739\63699\1027640B\\oFZeS*SN\4726\1106106\&0\nCIIrk\1036787w{{\NAK\ACK\167868\t\1111048u\68630`J;\f\t\51494JM\150492\STXlDa\EM\EMLOD\37506GlOE\143708\158832\5352G\1030385\63636\151806\95473\SO\EOT8\45810\a\v\SO\189301\FS\ESC.6\27162L:\SUBFX1\1049721\EMX\DC1WO\ENQn\151526\1097780\&1b\1033128\v\1094128\1018058Am\1074802yP\994522\SOH_EXX\33708\CANbioZekl\FS\FSs\1101617R\2142\DLE\DEL\ETB\96629\brNq\DEL2V\1079045 :\32170\1039053~S\121270\151871\DLED\1070223\US\128575M\1058178Z6\1103357\51689\ACK%9\ACKS\a5cXr*D<\STXV\DC3\19389+\EMy0\143791\STX\174741\121203\&6\t\178550\&8\v\ACKlG\ENQe}9Y\SUB|\1012454`\33256\DC2\28024\CANDv\bc\"\EOTls\1088547\&9\134247!\1059613\6302L\1079485o#\FS]>k\ETX\FSRb\DC1\1002363\20102\bC\151413\169862\174232\RS\1110082|l\ESC\EOTJl%\138436<\184032'p \r\EOTQte\60935>B\DC1<\26603\142943e\STX_\DC2\SUB)\54586Zg\1039749\1099648rz\1055768)\1067796[,0x\STX\SOtx\a\EM%41p\STXOJnJ$\ENQ\1036216\\n\165661\28326E&\NUL]D {=Zo\16162\DC4\1072142d\168238\SOH[\tC\b\65221+lcP\DC3+\CAN\181073\ACK\1034729\154183\SOo\DC4\9999*\72127\151396\\\vV]\SI\STXd\SYN\DC2\159929\GS\ETBba\1062270\185757\120542\SIWIN\189249TF\NUL\DC4]?<\189773\&1i}\rj\186764D\67090'A\STXY{\DC3]\165432\166422\184640o+\1097942q\29888M@\57867\162349q\131603gD'No\SI!k-Zm;QKu2,\ETBq0l\176781Vr\1112858#\EM\1070243\DC1\DLET3:5 AMSh/\60858;!?\60520\n\a\\\SYN\DC1\63883\1092802\&2F\1016360\94718\&1\SYNk1k?\v;L,\1067750%\161105\52986G/5\1036631\f\1108994\36632%\\\tw\990733(\DC4t\120856\&7\50398\165729~'a\162186[i_lZ\FS3\179048xU_\DC3\NAKjI\EM\83247O\aJ/&\DC4x\GS\142000" ) } @@ -119,7 +119,7 @@ testObject_ApproveLegalHoldForUserRequest_team_11 = ApproveLegalHoldForUserRequest { alhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "#OlsoB +V<\138963\188085{)\ETX\EM\DC1\1006154\125037\ACKyc\169285\1046715\&6\STX\1029265L\b\153349\STX\DEL\1068743;\1083242\163318\1102924:\t^\NAKhV:7y(\SOH\ETX<2\ETB[\1053697\99133\44176d\1108201@\996297\1099308\4395\EOT1js^[\SOHo\RS\67258\170395\&1\92264x2n|w\1053901\1036418\STX\NAKp1\1023469\100697}\25965#]f%6\DEL\DC4\1085224k8\162571o \US#5\DC3\985807?\32912y\t\\O\1088392fvl~\38670<\SI\CAN@\172651\1009469\43146G+\181707\ETB\DC3X\26045\DC2~\133901\ETX8ls_d2q\47428WMQ\DC1h\1017423\"7}*\1012861!\67145\1088925\1065326Q@kSh\EM>\63657o\1108975\DC2M\1054348HJ}\78267|H]l\ETB\STX$\1025838\70324L\r\DLE[*,\157820\SUBH\1015914\EM\1089486Z3n#1'\1022221Yf\138198G1\1099483't\1101300\1066253\&6\f\1071987?\DEL'\46863-gee[U\1086245\EOTF<\ESC6-(\1014533\ETB\145013c\NUL~\30038\ENQ\1058580LA\EM\69873v\fO5\FS?v'\986945\SI\996949m\STXM\NUL\1071514\RSW\1012357k\EOT5SHy\DC3\1092017PNc\SIwg5o9&\1019870\24902F\989235\1016043_LX\1080208_%U\t`o\19774\1037069\EMnQ\1097720^\1111514\99532Ej3\180125\1000342\1002703+)\1011841=zi\FS7\1111063`g\1052697Ke\1097912R|\EOT\1053863db\DC4n\f)\149698^\SO\EOT\1014538k\1002788\1042519\ETB\f\EOT+\47858r\15887w\n\11047\&6\97803Z\1102238?7x3uz\r0\SOH\SOH\42241H\18268V\1111743\1094683/\ACKwDK\40693ut}y\NUL\999717\CANt>O\EOTg_\EM\170507_M/\"\150515\18269\1097364L)\a?%\190762\58441$\DLE\US0[\SO+\SI\1081515\1110888s\53741)/uS55\1090690%5\b\SO9\b5\USi\135724\29763b\SYN\DC2N\DELWH6\r\1081300\1095727\SYN\DC3Jz\DELf7\ESC\ACK~g&zwm\ENQI-\992482t?\1076560\v\12570\1096515\1103045\1081324\160841)R\SO_-OTc\EM\990303\DLE\SO\1108639\974S\fF&\RSex5D\1103747\NAKZt(_(1\DC3\17863\121509Rpb\1074540\120301\201\171164+f\ETX\1016694\fr7&`\34502\176929\DC1\1112561:7\SOH\1067937\70660!\EOT[\996976tk\987033\ENQ1l\1078071\1033678'PbE\ar,\1085976b\SOH\SYN l\1039948h\13584\144073&UU!9\f\1033649K\DC1Y\EOT\NUL\ENQgXt)]R\FSRD?&@Z)\ENQ\DC2\SUB\60793H\1002094\153081[\vc3G\a\SUB-.\1069042\EM\185496\1044861\NUL:\132265l46~C\SYN:Z\12588\ACKGB\DC4@R\185623\&7\194590Y\34138\f\98816.\148434\SOH}o\ESCU\DC3w\CAN\4777" ) } @@ -129,7 +129,7 @@ testObject_ApproveLegalHoldForUserRequest_team_12 = ApproveLegalHoldForUserRequest { alhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "s\71869C?n%!7\DEL.&\152686\1103353_:\1035430`/\tCp\1055113\EOT2W\v\5468\1011362rt\NUL[vGj\DEL}\1097432\ETBXT9n#\SO,`!&Q\50213\1014248\96610n\185387~q_\1001653Y\v\175424;N\1046497\USf*\SOH7\RSw\\\1032043W\41637\\\t\160897:y^\49692y0\127523m/h8\180708\1064330b\SYNbA4\DEL\1066502\1086907j\1039057w\vTf2/\US\STX\SID\DLET\66326V\144042L\46751\&2Pb\SOH\SOHYW\1088116U\a\"\1010089\157274\984771G\f\RS\NULVC\1011697\ETX3\DELna\27350xWY\DLEL\1032406\59231\r\CAN\ETBfv\1032807\EMMKno\US\986491]\b\1051634\"\158977ee\62236\b9\ESC\1093462v\186317i\30725RZ\DC1T-u\139600\136083\1034716l\FS\1086006j\72978\1111332\SIE\74393\1058814\9795Y(n\1029867'\999681FT|A\28021Ny\1012657P\1027292HGJ;\SUB1g\162214N=\45525/1\1050131`\1041892~h-ydx.\165667NEuo\ESC\STX7l\59670\f%\128360s>\ETX%\1042079\GSfi\SI\100435\DC1\999275T\SYN\50388K\1023635\GSs\ACKk\31089tc\46530\1075102\1044382B(9sK=\1077971\35717\DC4\NULN\NAK`\189818Uh\993752\SYN:\SO M\fSR\1059421DLc\EM\ETB\58481/\CANfI\v\1107188Qz\51080'FP7>\GS\1071265+\1023462]}\ESC\SO\160413\1034709{\985451{p\DLE\bDp\DC26W\1092265-y\SOUQDJ\24794$&9&#nc!yI\1037082\SIK3&%\\\ETX\1111145\ETB\157407y\1097252/\DC3\1026756\DC2=}[\1004847\1064675O\1041800R\1050755\171183\t\vS\RS'\t]\984930V\133079q}lEK\172236q\"\SOH)q\ENQ\127584\170231c\178805\1081808.c\1051295\135483\&4\1017507\DC2G<(\174283\&1R1\DLE\SUB\1055200J\ETX\EOTe0;\SOv\DLEL|%qfi\SUB\94387\1108475\20185\SOH\990241\FS\fwWs\154534\SOJa\998742\138747\b\SUB5E)d&\CAN|\ESC\30508\CANa~Q\57706\1099865G\41551v5(|X\SYNk\FS7\1108344u\t&\22152t\146418\1075406\1018909\36286/\1051186\tc\1004597R\53288:\142873\SYNY\988200+\1097316&v\ETB Eg-\164876kX\DC2:f\NAK\71915\EMpwe\1107411\US\EOTl+ \ACK*\1090385w+r.\99987\USk\ri@d\1052758RY'r}\1113587Gh;o=c\146487\1108886\146745fV\SYNfV\tv\v\991018&=^\49680\DC1\SYN+Fm$\RS\ESC\1025547~2\SIBZ\b0\SOHGO<\t\SOoy\EM\1056228uv\SUB\1031679SKC\ESC>_\1020105\150038\1040388\38653\1037153%vt\1033323D\DC4\EOTn\138098\175291>\176075dn\167920\NAK\149711\&5|$\1057141p*\1048607*\ETX@\990461\GSm~\DC3h\151849KQ\DC3\145674kgQ\995485\984633n\62899H\SYN@\SO\DC1y\NUL\134961\&1m\EOT\ETB\SYN3\98234\152423v\\\DC4\1064318x\1097042$\DC3\FS\172963\1033210\USmk\rin\1110096\58538\ENQw>]\148479K%l%\a\991985;0\137916an*Nhh\177382\11280\&8\CANW\a\1079417By\1109404u\10755\1039915%!}\67075\RS\144028\&4T\SUB" ) } @@ -139,7 +139,7 @@ testObject_ApproveLegalHoldForUserRequest_team_13 = ApproveLegalHoldForUserRequest { alhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe ")-188\16491jj\SYN?I_\1010852yR\28592\11556\96713S\993598\1052649\1063332\51924\ETB_-znO4\f\ESC\ENQ\EOT\45818\RSFU1oi=\986046\15800\GSk,*\r\100246\NUL\996586((\ENQI\1035572}\61141a8\US\SUBWS\v~*`)\STXjH'.7g!\98573R=\\\\F\164647(J\ETBp\vQq_\1086002`\f\154456\EOT\49925N+\STXUZ{\DEL;]9{R\FSHQD$tK\NAK2R\191022zJyl\23289\DLEW\33763\156897\3871\SO\1022494\ENQ\DC4\1018391\FSZDz~\52553\31824\&6S\ETX1tl\1031582\1070931\&4\185408\USx6\GS\171499\ACKr\US\194844[\DC1[\1095095\a6XW\ETB\f\1086608\120499[\1044047\136632\11161F=\20950\1037228;\n\2727\&3\160133\129632\US:\DC3\146832mMX\f\RS\EM\SYNz2\173177}\CAN\991220\&7\vP\NAK\5359\1012017\&9f\1083765|y\ACK.\t\31232Cx\ACK\1006836X4\990238\1087233\&3\b$\CAN\999896\994656\rv4\36040\44687\nuKn\SOHv\1019866\187442\95139\30515\1055680\&1/n\143793\1031358+\ETX\\C\DC1\994032\&8rj\DC4\1087506\1002662FZ/\1103906$\36975\142151\145443S\vd\163125^\v\DLE\a}3=\8297n\DC1[P\52378\124996#G5\1094605\ESC{JW\43243hn\171909K\ACK&A\EOTu\28734\26836X\1104098yRf;2\NUL]\\Fy\139416kO\"&+M\183176\996968\DLEy\r X\1028935\EOT\1063518\SO,\1104587\n)\153062\1064592\STX;\NAKy}0\1044595C\ENQ\ETX2*\EOT\GS:\68481Kl\159532\DC2\45275^\ENQoLk\6286\NAK\rX\ESCM$#\1063536E\bojQ%\8142\NUL\1055760zk@\9162\1053307\996848<\121446yHV\1016737\b\10925T" ) } @@ -152,7 +152,7 @@ testObject_ApproveLegalHoldForUserRequest_team_15 = ApproveLegalHoldForUserRequest { alhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "D\DC12X\DEL.\CANc\1061750\GSQAGG\20403_\ETB\ENQ\1027245D>&\1086018cg2\143327D\100398\43754z<\142063\DC1\51240\1107872\"\166527\ENQ\ETX&\1096740\US\157249.\RS\1113096\161373\&5d[.\3420>#\1005438\1105208\&47Ew\1012467z\rr\185541<\170419Qj\1024871D\SUBr7\SUB\61064ps1DN\ENQ\182374l" ) } @@ -162,7 +162,7 @@ testObject_ApproveLegalHoldForUserRequest_team_16 = ApproveLegalHoldForUserRequest { alhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\1063295;(yq\EM;}o\1000262k\1056656B\DEL\NULUJn\146086h`\n\1113861Hm,\1100817r\r4\DC2WybQUJ\78800n8\161682{{\991366tQ\US\136167\EMP'5\1104198q,\DLE9ar\1077046F{\DC4@\180825!+\1082501$\NUL=T!\95655T,\26788\DC1\r*\171625\n!\DELv\SYN\a4Y#\992128E0\RS!\ap\SUB5;^<\1030861+\3675NS\37464:24h\EOTN4\83423X\CAN\DLEb\1002056wE\990942Q\1052588f$\169795\1015326.{\NAK\1039343\DC1\8793\1017267pX\186748\\c~)i:A\FS>Tl\DC2\128033=T\135357=G&\STXi7\1020649gG\1065044##%|i?WI\v\60089tx\162390h\137258q\1068428G\154159\GS\1023385D\30701\FS\1053852/#\DC2R9\1028565\16327\ACK\1041216=l\100130|&\CANQ\DELN\987658|\53008w\EOT\DEL\997524\SOH#\38822\1053990cLG?\1000306g;\"\DC4\98846X\NULK\144617\SOH\US\70078_\ENQ=rW\CAN\GS\110765\15603\EOT6+'MQ\3981\991904Ib\USj\71213\EM5(\ACK\EM\SUByY\ACK\1005517\1070725\ACK\1038519\134419\&8@\b\DC3\DC4\1076781\DLE\171045)\NAK\190873(Xc\7689)L.\SOT\ETB\98826f\ETX!R\1065590\147758GS{\1039766f\156254y\US\985568\1096137\59547_\162072y\31716\aR[\10845hQRe\983197'F\a\6669\5249Z\DC4xh4\SINDE\136849\180580dMf\63819\GS2\DEL\135744[\1095525\ACK\DC2\t\SOH\\\1006344\46489$l\168883\1000172\b'.\EOT\1060986w\GS\95775\1010397\&0\19380\1023899\1050888\NAK\SOHV\1021053K\DC3\2703\1103187\172377\120978#u\FS}qX\1101118\DLEH\11217BdJ\184162\1061671;NSM(%\136921__#\1056683\ETX\1091775_{&\b\DEL,\98001~Ai\SOH\EOT\EOTJ\50949\1059883\1055876[gm\SOH\RS5H\SOH.\188774\68750$rs\SO\125023\DC1\43144\t\t\rm\1069061\rC\32852\&35Zr\39162\169454\12458%4*~>=2v\f\vW5m\DC1i<\NULa\ETB\DLE\GS\95163\48394$\SI\1071399\49175\ESCl\36581\1044105\1098515|HV" ) } @@ -175,7 +175,7 @@ testObject_ApproveLegalHoldForUserRequest_team_18 = ApproveLegalHoldForUserRequest { alhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\989228\1033670t\DC4\STX\n\1106605Hgh\64348\&7\18563zp&G-R]0\993550Z'\994827_u\1046313=\187720\t\135085KS<\992586\160219-\1072422\vJ\1013674\RS\51633Y@\DEL\1113296\n\ACK\94263k#\tcyVB\DC1E\182661\1109188\NULr\riX&\1068794\&2\1042907=\n\DC2,\1084001\ESCE\32745V\\\49583.Hr7\SI\1049433\ETB<\100299ac1;d\154060\v\DC4P6&Q\14349k.\FS1&\DC4\1044825<<\FS0\64609(]\39860\&0f_5*c\NAKr2.\1085689\1002307\1045113\DELW9\1046599,\1054235{j\1000833*Lyv\DC1f\DC4R\SUB\95665-%\150458\v\ENQ}h$R\1081317\1019572]\11114K.fX;\1048182\&2H\7117\65499\te\DLEw\DLE\DEL N\7289\1036588\170465\GS/\1070329\9943\b\"F\139159\DC1Hvr\999435\55171k\28188\1109813\41202-L\152276\1001843V\ETXA\SYN\1105535\&7.j\SIqF\1048843+3=\39037\47083c\100734,\STX>f=\183726\r\1073896(\60684\"\EM\1057542p!_\CAN\SUB;v0\48053Pv)Ay\53313DA\GS\DC3\r\47850cc\DC4\1043056\67365Mod\ESC)>8\EOT\142812ms\DC3\EM\44781H\SUBT /t\SO\a:\1034002U\SYNM\n$]}\ENQ8\NULJ\1074800\1000418N{5M@\GSx\tbeF8?\64312\12683w2UfV_~BRSY\8267w\153047\1049394~ALK\DLEP\1017470k\1098468\1059888\DC2)*\995793\23984\ETX+vW\f\179840\1047712p U\ENQlPa(\1085028\1064214.ts\DLE\1044932\174013\1030563\990439\NAK5e{\EOT;|\FS \169988\"/\138997\SI9\DC4\f\999753\EOT~\1063723\SYN!\STX\168779\ETB$|\23035fmSW)4\16662\179041W\US$\20397\1037087B\1006044&<\67114\29550\CAN\1077577\43474Hu\1010294RJ\\P+9[\SI\1066506c\"\44597\994554\v\1092097'0N\NAK1\DEL\1076469Eo\1071483\37597\SUB\1092908\n\SUB8k?X\1098235\DC4|5d%\29404\&0t&\ACKk\NAK\1086928\&1\1065483;;[\128321k6G}\RS\a\1105099b|Q\145507&L\182826\US\26622!(+\DC4*2M;\160716B\180517\ACKcI\SO+\1092491\156685\1023802N\65859/y\174932\DC3>\NULT\1055414\SOH\bA\1027446\169091)eJ\"X;Kv5\DC3\n:\1004892\57498$\SI>\1091550\ENQ\EMff\DC2ha9`\187563\21362#yq~9z$myW+K/\"\1012704p\viG\ETXs\SO\986505\147946\&49\1073075\984275|\1087633\1097703\r$\GS\158421Z\21481v\189241\f`i2N\1084745uQ?\1040178/!B7'L\DEL{\39768sA\1103818O'k\SUB\145785s\35509\SYN\v\986070\986176\1010030J7Y(Xv\59273l\26953" ) } @@ -185,7 +185,7 @@ testObject_ApproveLegalHoldForUserRequest_team_19 = ApproveLegalHoldForUserRequest { alhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\fz.\1091593\br\1058023E\26771\DC2g\1084813\1106403\ACK\SUB\"\62491\DC2[W:\NUL\ETX\68895\&4@o\72767#M\121119(\1044521X\NUL\1034035D\45750t<\DC19\DC4\v+\DC3:Gv\1036084\DC4+\DC3\1010876\92293\34486s\1078861\SYN\DC1~h`\1087793\174225\ENQ1\173353>\64117\ESC<\STX9\RS,d\187057\SIR\987440\ENQ\EM\ETB1u\SO\SO~\1057178>&>3\DLE:\1077412i\159172R\1109008\1018042$\1062919r,W}Q\DC4\148438\&0{\RS\66393_\NAKa\137722Y\1035750C\1086696\NULm\SOH\ETBM\RS)\CAND\3595\24646y\SYN3\83166\n\1077059\bV+\5330\GS\41524F\917819\f\STX\DC4N}iAb\62823g\tN\96650f\vQ\15772\&3\"\121340\&7,y\\\ESC\1059500Rt\993814\1001846 \23120B/\51415\1106581!\1001166\&4 3\ESC?!G\179582>7\1024331R Ry\STX\141978\&5D8\EOT\66506\nC1\FS\"\149391\RS/\1039676,|\SYN\CAN=g\143926rm\DC1 Dz4~-\1098224/\1029118:./\n?\SOH+\129168TP\169412r/=S]2\"@P\188310\\15K\1030887\26708\14910{@QE\\\1055612\b@\170454/@y\1092160\5491\&0KwA_\1018346t8\ACK\ENQ1\1064853E\SYN'\ESCRH0\131183\ENQ\SUB\1097748\DC3B\GS\SYN0TM/u\183360]Ucc>j\1056247)\1036155\4133^\133073L\SUBAY\1086528}\1075262\STX\EOT4OtuQIK]t@\62302\vpx>{:\1060349\DC1\987582\&63+\1005816\&49Y\ETX40\180226{(\1028097h]\STX3XPPq\78458osR?B\12134\29518\46380H3\GS\997875\17184m\SO\1083738~\v \EOT9\47563\1098802b\SOH\1080523!\1013341\ACK\149326\\U!\55237_g>@C\1099641\1088928\&5W\"\DLE{Z[\63786\166987\171088K\ENQ\1090165\STX\63425Z\1062632\DEL\1074874\183885t-\1081873B\NAK\ENQ\n\3934Hpv\9500.Z\1112482\&6%'Ku\37519\ENQ\1009953\178321}D3yW\1062506\&0\DC4\SI\1099066m\162810\1061361v\SUB\1014531\&6z}\GS\r\DC4S~\1012246\&1\CAN\DC4$o\a\74265H\ta{\154562h9\f\1097875\1001861\1071266\ACK$j\11656\&0;\n\21414\1085169pZ\DLE\139145\40739(\US\DC1\1067214p.\1012942\157531\SOt\v\NAK\131692\1050866J\1013358@jQ6aZ\92399\177467\96501=>\1011985\170350\1069919\8079\aL\138219Q\GSI\139369k\USET\NAK\167274\1089591\SOH\1044934\1078365\ESC\1505)8hcjIN{\1050178=gD\n\21575\CAN\917576Qm\EOTO\DEL\1109507\ACK\1100841\6200\188761r|\DC3;}\1053295NZ`#J0\1094912\DC3\SOG\EOTRT\EOT6s:\191070m\64564(\FS\54449\1104942+\58629l,\DC3t\983696Ui\fp4\128029RMNm\DC4\180560&\SO+p\111201\1106401\1002379\ACK\1111973`\US\183762\vchk\DEL\990099\1029557\&9z\1037886\1097078pN\DC2&&C\DLE\558+\994148)D$\ENQii@\EOTQ$^Z\168869]Y\1108144\DC3\1084858[w$7DZ\27299_\DLE\1020012)\SIJ<\DEL_Rw\172761yaI\1091155\b\SOM\n\US\1062748\b`\57587\161753Xlgww\1027067\1033445\23572 t$\SO\US\GS\173215JN\tX\1084264\&2`\1088844\1002261\1029244X\ENQ\1006766jij\49523\DELn/_$\1105934;\64817sN6:\\\1017608L\1094707\162982\68062\n\EOTI-\USVO\DC1C#}\bQi\NAKBUd\FS(\ETX\8707Dp^\ad^\SI\SYN\1031968A\\$$B^\GS\1022428\1028061Z\DLEL\986790(Q2\EOTEdIU\140043GH\121253\USA\167041\EOT\158239\1039933z7\DEL7r\165084\148879\46288j\1095023\SYNeS\b\NAK+\n,\"\5201-,'hi\1075613\ENQ:t#QFk;\27281bq2\65861\rmM\1054102:\145702\20190Ft\SOH\1013857L'@|rUk" ) } @@ -195,7 +195,7 @@ testObject_ApproveLegalHoldForUserRequest_team_20 = ApproveLegalHoldForUserRequest { alhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "b\GS: B\1106918E\159753\1069144H/G\1077924z_\SI&6\SI\72308\r\288\EM\DEL&Uc\DC2\ETB?BU0 |\147737\1088938p\157022\1093121F1S\18509p\1025514\&7kMx\f=\SO\1065952\1026347\6586\100522g$\ACK\48215\SOH\SI/9Y\1096988#AgT\SI~n\1090224`=.\36615\167069\156459>}\DC1j\RSx>\1021270\127798$KH\ENQ\"o3\v(\152173\995193\998214\78660\RS\1093677v\48969{\SOH$\120962\EM\36407,\1094884\1080155Pbd\135555x\147620\164453\RS\127873Mp\17673H(p\STX\176299*\f\SI\15877\128165\1019429q;p\1106859\a6j\1012470BQ\13631\155076E\CANEd\1047713C\1102763\NAK\36446\1113903\SYN7\5393\131500\ESC\1105151\"gr\b\DC2\1052065\129532\DC3\NUL\DC1?k\146956bLb\128331v\68434\CAN@JE\DLE\986023\94224\DC3]\1011138XB8y%\CAN\1048338\1070539iw\SUB\US\10900k%/g>\138269\1021696,\72144,\ENQ\SOH\1112465XXF\998356\v[v0T:\\\a\999575\157976\990811\US_p~:+\173509;\3079\DC2\n\1104804X \vN\rn\190454J9\EM\1016599\7702\EOT&\27805S\US&\1029070\&0\186426`V$|g\r\184993\ACK9\49854\185783L/U\1033088ByS)@\64074\ETX\SI$\ETXfT\14467\1093561p\151508\bM\ETB\EOT[Z9;\1038266=YG\121380(\DC1\SUB\NUL\DC1Oc\17590pc\SIOJ\NAK(\1007413.ge8\54165$\191071\1053126\GS\CAN1\1071179m\1102861Ol\DLE#\r[\t\DC35+\ENQ%\EM\ETBD\1013850\SO\EM1\a\1044686` #B9W\1040267.\al\134436\ENQS\1049173\EOT*\1038519\133853\131291'\a.\DEL\28755*g(\183196,\SO\1049281\1082825#\RSU\1049086\1066586,4tB\141824\b{e\155746{\EOT/\FS\188986oPT!\r\159409\168829\&8\RS*L^F]-\29637\DC3z\995300\&7\GS\r8,{p\983520H\95037\1096487\&7\172819J5<+w\DC3O\1022126\DC4\1762u\DEL\DEL\65012\172954\1004888^\FS9s\nS;\1108376u\1024015\DEL\DLE[Co\1025644pz\1074815\SItne@C~\ENQ\DC1\SO.\22690p]bD4d:\52798c\27855\&8y\1004963\a\SOH\bIt\44906kUL\ESCT\"\SUB\SUB\STX\47893\US\152434\STX\CANq\155213\\X\31077\t-\b\984355\&2\DC4X~\EM\GS\DELp}\67368wS\1068848\&5\SOH~\1060981\158356\23923v\99034AX\999427,PW={O\1084317\45376\1046621\164348s6\DLE\SIy\132637\DLEjz\SYN\SOH\"\t/\1059617JD\a\1018206\96623\190738H\19501Ku\ESCeG\1065007O\1019775d\1082625D8y9\131905\DC3,G\DLE.xq\1026660\&5\RS\1014660?\19044_`\1084258\3894\137647\36701\CAN\NUL\STX\189160\119831As~b(&Hi\ETB\CAN\186027\170956\1104651\1016426?PGhXTL\r\SOH\1061834\vA_" ) } diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/CompletePasswordReset_provider.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/CompletePasswordReset_provider.hs index fb620eefbd..36c405ce54 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/CompletePasswordReset_provider.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/CompletePasswordReset_provider.hs @@ -18,7 +18,7 @@ module Test.Wire.API.Golden.Generated.CompletePasswordReset_provider where import Data.Code (Key (Key, asciiKey), Value (Value, asciiValue)) -import Data.Misc (PlainTextPassword (PlainTextPassword)) +import Data.Misc (plainTextPassword6Unsafe) import Data.Range (unsafeRange) import Data.Text.Ascii (AsciiChars (validate)) import Imports (fromRight, undefined) @@ -30,7 +30,7 @@ testObject_CompletePasswordReset_provider_1 = { cpwrKey = Key {asciiKey = unsafeRange (fromRight undefined (validate "Cd9b4n7KaooqOhOciMIf"))}, cpwrCode = Value {asciiValue = unsafeRange (fromRight undefined (validate "W0CLFxLOL"))}, cpwrPassword = - PlainTextPassword + plainTextPassword6Unsafe "\1012683\1112273\39028\&5\168679\169133rs\93986\&4wo~\1002561l=\1032023\13042\SI1nt\35892\1050889N\46503>?\"\aT\69782\USgg\\f\SYN\165120#tS\NAK8\DC1C\36700q\r!2d\DC4\189369m\SUB\a\\V'W\\\110825,\r\143398?\ACKx\agVQy9\SI3'h]\78709n0ue\b\1032695?@\ETB1zJ6\NULI\a;DL\ENQ\37006c\92669\US\ETBz\1097017?0\NUL\184657\"A&&\36577E\157691\US7fG\1081322Vpx\DELI'\1102879\DLE\1008567g,\NULH\DC2@+\1085033\1064315\DC4\1091186\STXJ\1103240dPQ\STX|\EOT9^9_\1033902\SO]\a\1022683Of'd\SYN\"^\EOTw\1073515_\1113440\DLE}\95632\DC1s5\161851N1\1078798RkTZ&\150149X\1065364~''v{4MDK\153974\US\SOH|oB\143604'q,HU\1025306\SUB\NUL\1060487+%~v\DEL\97853V|5\127943|\999498\1059223HTFhF\FSdelLB\CAN\SUBbiC\1027783\n\110976u}g!\38540M\141506\1037727Pt$2(W%\149078\&0i-H\SUB@ii\1037533\NAK2\2636hg\50874\28429#{\23697\SO\NUL\146715\f\f\1039241A\GS:\EOT]\99785qf\SOH'\DELx\139534\SYN\f\DLE\nT\149322sK5O\EOT\SYN^&3\SOf!\150976\GS\SYN\f\1112187wy\1052535\1091937\1045148\SYN\ACKijjq\58477&\RS\"\DC2\1063939e\129001\ETX-\\\DC2E\ETX\40256\39310Z\DC3\22084iD7Xv\137008m\SUB>~\CANW\139109\33037YYZE\1022090J|\5247\CAN.\137437p\1011705\ETXS:Y<.YBcP\31609\1107733v4U\f\987772\1070124W!9Z\1035690;\1106506\DLE\132101\SOH(kH\SUB\"\vdX\136713\10837x\154948\&6/b$A\"jH\133538\48869\&9\DC3,\144088\1091851{\DC2\12495&>\1040461" } @@ -40,7 +40,7 @@ testObject_CompletePasswordReset_provider_2 = { cpwrKey = Key {asciiKey = unsafeRange (fromRight undefined (validate "8XosCtq4Dzhyo=UoMRg_"))}, cpwrCode = Value {asciiValue = unsafeRange (fromRight undefined (validate "EoNo4PH=cFSyQ-yuHhP"))}, cpwrPassword = - PlainTextPassword + plainTextPassword6Unsafe "\DC3~d{ \988098\1008471\&7\DLE\NULd\1065586\SOH?NT.\186651\1106270JJ\64065^rd\146603N[\43292\SOHt#dn\142707}u\SO\1022368<\1094323\18349\51616\GS\CANn\n05\983885\&4Z\vIJXz1ia\20698&\SYN'<\162555\v\19677B\ENQ\SI\1049058\DLE1dt\1038032)$\135798\&1b\97041Fvi\36729J\a_T(-`S\NAK\fU\20849dBbTgi\167678\rfp\171973ED=\STX\1086228\SUBXa<*#\1037916<\1106037\191075^%Xx\ESCOM\DEL\994881\1059244X _3\DC4K\GS\a(&6\59167\&8[\1045759\1111435M\681>f]o\ENQ`m\DEL\1112157\1102641\11945\f\161652)Q1\1018093q\1005011\&9\1102348UD]$\41477\f6j\190919\&3jAG\1007534!ys\NAKs?\17249Z\160153cfpz\fGC_\SIf%xb\99796\&1\ESCj\94762\&4K\rQ7\150803:\55009%:\r\"-Zq\DELU|\DLENa>\131324K\131830G\ACK3#\"V\NAK-w\ACK\1081085(\23629\1091792\\H\21182\ENQ\1049732\1036941~M;FHW$X\988437Wy|x5N\CANTrX\US,\n!\51726U==I}\ACK\1067103\1041045\1085401\EOT\983701{ }1\144729yu8_\DC2p\1053610l)S\128946fZ7\ETB>hnRX\458M{U~Hw;\69816\1035492v=J\8990:\1000731\1096086\70367o\ESCs=\NAK\1017016\SOH\NULb\1111472\152433H%f\1040890\EOT" } @@ -50,7 +50,7 @@ testObject_CompletePasswordReset_provider_3 = { cpwrKey = Key {asciiKey = unsafeRange (fromRight undefined (validate "=aYXtgLJZX77qMIx0Oah"))}, cpwrCode = Value {asciiValue = unsafeRange (fromRight undefined (validate "RMQ-RtgFDI-b"))}, cpwrPassword = - PlainTextPassword + plainTextPassword6Unsafe "\1073786\1022541\1030619|@\DLE\1050256\58722\5028\SOH\25945J\EMkH\986937=\11472\"SP\\\FSw\95016lR[.29\137466\&1W_\64827\96388M\RSU\a!\GS\43687NKv\993525\1097611X\50069;?\157751\&47.\CAN\1103688\137799\186574}\v8\STX3fj\DC1\SI\181630=-3ZmNn10\DC1\997119\1059249\161874CT\NUL:N\"\SYN\\@|q\128174\FSv_u\95666\1080533J-*\1034203;\1068818hC (_u\161608g\43952\33809\NAK\US',m}\a\30792\DC2Dt\171459\152195Him\395|\125271q\161223r\110828\&27A\NAK\EOT\FSgP\1090390\US\993009\62450\1042020O9\EOTEB]\DLE<\156612\127142\133358\1015398rJu\t\1027420\1050082F\bfxm/f\a\rC\152680t~D\ESCO]_i\US\39307\SOH\35670>\SYN\1086602\NAK\STXDz\DC3\1048748ZC\DC1x0bLFjXI\148199\EMZ\GSR2!\ENQ\DC3\\Mffm\986388\1043076\94041F\1096421u\7179*\DLEM.q\33878\a\1106357GdxHmu\DELSTrb`cn\NAK+(@KZ\ENQ]\1034430QEf?fw\ETX\177531.W\STX~k\ENQ\993340\1112261\US\tB\SO-\STX4b\185882o,\CAN}P\SOKD\v\1100259O*\b\1061589\RS\1106367\ACK\NAK=\1048333eh\DLE\EMY\12994\986285\185764\GS\DC1#)v>a\1050729L\DEL\16992&gh1\SO\24688\&18\DC1\1091353(\167196\1031220lc\ACK#\1096547Poe\178761~\ETX[%e\133630{\1020978\&31\99380\45215\SOHI1z\1093633s#y\1048198\FS\8988g\USPE5P\SO/\n\1089996 *Z\DC3\2954\33162p}sh;[Sr\STX\1015744\ESC\tO\152390\STX/_Q^a\157142\1101351\985165y" } @@ -70,7 +70,7 @@ testObject_CompletePasswordReset_provider_5 = { cpwrKey = Key {asciiKey = unsafeRange (fromRight undefined (validate "OU56F44t-0ybJj7eKUaS"))}, cpwrCode = Value {asciiValue = unsafeRange (fromRight undefined (validate "rr3lleg-Tu4eJ"))}, cpwrPassword = - PlainTextPassword + plainTextPassword6Unsafe "k\1044075Pnu'6Z\NAK\1017783\149108\ENQ\129297l\18438[\1054432TMgddIb\186517mt.TCQW\1025717O\1111819M\ETX\27672\ETX\ETB\1083603\1091383F\RS^\182596C\SOH<\rs\f#\STX?A\n\170555\68821\t 88|;\SUB\1015442&\n\1042330'\1003626\151074 <\63465\v\EOT\1043258w\1012648\DC3l\62396\FS2)\SYN\1003311o4G\161486\&1;0IVKt6t$Y\":\13086\156982\1055032\"\GS\6275$y\ESC\15469)#\1011445H\SUB \SYNLk|\DLE$\GSh;\19798G(?ft*V%|\9608\bC\b,\131877\SYN\7628eI?:T1\ENQ2\1042416B+\STX\\\GS>4\1042921\1015196\DEL\1050654\ENQ\RSdH\NAK\SI\vK\NUL\1020294\a\b:9\163015\&3\53363%^[X\r:\1044970c\n\1035333kk'RA\78616\1054694\24158\1051573c\RS!\167908\28730\ENQ\SI\1068557\r/\SUB\1106472\&1ott&\SOK" } @@ -80,7 +80,7 @@ testObject_CompletePasswordReset_provider_6 = { cpwrKey = Key {asciiKey = unsafeRange (fromRight undefined (validate "54Yh4fa_ClTVqjEEubnW"))}, cpwrCode = Value {asciiValue = unsafeRange (fromRight undefined (validate "RcplMOQiGa-JY"))}, cpwrPassword = - PlainTextPassword + plainTextPassword6Unsafe "\1068965Mz\1112587\\b\988910\33388\1081682\FSSi8:\"\r3\GSc\989625I=8L>uA'\SI&I\94104!W\995368\&7z;r\ENQnj_+3u/8\31470{\32573\170260\EM$vy\rB)\125105l\58284\1022117'iN8\SO}vd\1025869\132023uw\996610\&17\ETBF#\154217:s\1019264\EOT\CAN\12331\127284p$\53580\&2\14658\DLE\13233\SUB\59635Hl\25906\SOHw\1054216\&4[\171724\DC1\RS\SO!lS\EM\1073106\66443\\(\47504\61628N\1029483M\NUL\"\SOHd\1088943 \58859U?\31664d\138217(o\RS'\47111\v\1097785{A\ETBb=\1039402\1096760?o\n\164402*\12095P\SO84,Qf\1065714D\EMZ\SOHux\1096460<\v)\1109779\185595\25160\69876\&8t\136448Ya\GS\ENQ\9575\NUL`\US7\1022950p\1032880\&42\32304h\68036\EOT+W\a\1022685aH+XE\1016645p\SUB\8531\n\DLE\136210\1080841\1069380\119885\t\31849k\1020979\159730\RS\99244\1100479\14782G\nh\168920\SUB\DC4{\1107942\&5,\US\DC2L\DC1(\137496<|\bZ\172359\SIK\EM7\t2V|K\ETX,\SYN)F\50452\20991\100678\1098846\1109927\tJ\SYN)\133930" } @@ -90,7 +90,7 @@ testObject_CompletePasswordReset_provider_7 = { cpwrKey = Key {asciiKey = unsafeRange (fromRight undefined (validate "muTkNflRkN4ZV2Tsx=ZS"))}, cpwrCode = Value {asciiValue = unsafeRange (fromRight undefined (validate "X-ySKT"))}, cpwrPassword = - PlainTextPassword + plainTextPassword6Unsafe ")jtk/z\184222F!N~\ETX\990448\1055900{8\73979\153166!D\1043025%\135850\168364u7WynrV\ETB\148520p\1077327Lt\842e^}?\1093891l`.`Y\vZ\STX\1112581P}[~\30935=}L\1095875\a\v!\1028719\ETBH)>5\ETX{\NAKD\ETXUEh^ ~\EOTCC\ETX\SO\16392p\38296z3jt\NAK\984409\bB7 P\CANSu_\183789o\17912\DC2\178168I\v`,\1022887N8\\\DC1^\10311m\CAN\1030400\FSZ_\"$\ETBB/\NUL!\SI[\DC3\vy\f\ENQ\ESC\137923OC\SIt\12293:\EOTl\\\b\EOTrG@\US\45550J\95310\166637-\10023\&8tTT#MD\FS\DC4lJQ9s\64189\25142\DC1jlVF\96794P{\5228\25037\NAKKEC\1098620[kg2*C\991918\NUL[\35874&\74062\188051?\182094\&8\145055\rSYlf\95342q\30892\94613\NULM.\b\t\1102963\1018631;\DC3_\1029835|@\SYNd\1082087)\n$an\SI\RSp\n=\1013045D+\97624\f\1106118\988197\1113\GSb\181818\SI\1091492YQx]\1063062c\18044\993702\148181\1072483\1042478J:\ESC\RS\1052622\186566<>\EOT\DC1\FS,\1076029i4@\ENQu^\178972\1082722Dd\63135\1006290\EOT\66041>Tx\1091471#u\\`\STX\1093786,Kt-\1035926D\1024804\154425,I.\190722:\15722&3n\v!\40042Pm\41694$\n\SOH\183103\75035\1093394\3121>ihpLGl@L\DEL\ENQ\ETB\182031\SOH \21434\SI)D` wC\STX\v\ENQ`\54406}$\39750\DLE[\"\1087944'q\1043619tP\EOT%\ENQeG\r\1058468\1110447C\DC3g\1038268#\FSYrht\164459@\1085349tMo\ACKWM\SUB\v\40317o&}~45\160190\&4K\1104579\CANl!x\167229k\ESC\\h\ENQ/4,\177887Yp\995759d\98258N\1108317vw\ESCK\1098528\FS\ETBRSf0\DLE\148633\93011*Wukxd3>\ACK'gN\1044418\DC38;2FN\747 '\1005699Yt<\1105770\21737\1045228\DC3]\13220\ETX@\f\1101655\42506f9i.\1005751\&5\n\131677\&2%$\1047618N\169552Y~47\986154\SO\1007292\1001379\31676\&3\1056996le\1059155\&4\DLE1Q\FS\986744#5?\73770\1092436\1011458\171368\167096\&4l<\1069261H7]=\DC1a\62925od\1064417A\GS:l\SI4q^b\1057856D\173253\1059916$b _oH'\DC1Kv\\n<-\t\US\1083436\163231\ESC\1098850F\1329\STX-\ENQ,\CANG$\NUL\38340.\1107219;\125009\169728\167O\ENQH\1018301%\ACK\1025545\1011306j\RS\994143\1094533mEB\120644\1031761A\20411\180256YN\STXFRm\US\ETXQ\1072397V`+\95270m\SYN1\1013314\b\1024313\&1}O\1108229\1002097\49175\f\1007287j$t\47188\&4!8%#v\f=\t<\49120\61960\ACKM\1056844\SUB3\"\r\989243\SUBX%~n+:\NULM\134421X\DEL-v\72197\f\ETB\996041\EOT\DLE07\1009115\CANU},,}\141362\bHy\fLa\\\n\64444\983949;jo0\157407\1061450\1041761\EOTMlW\DLE7\45112\1113654\984581B\1087787Z \1067937/\1027501R5F]X\ENQF|`\162826E\128973\r\v\984688c\1100696\1074387T\1041206\SO*(\RS\ACKbNs\1056623ST\139333\170914K\1032627?\SOH\1095798\1006647\13962\"S[TY};\SOH*r55\aT\1006364\SYN\SOH\1111555\1082650\RSZ\a\1020940s\162901t\1055866}\1055756deI\153662\46739\rR\\]'\1084483\1056412\\y\135616\FS)@o\30437Ci\1081016\1042881|[Q}}\1025142\SOH^\1085438|S\EOTWa\nE\DEL,\1014498S\DLEq\DC3s\"h\36770)\1084960\RSB:" } @@ -100,7 +100,7 @@ testObject_CompletePasswordReset_provider_8 = { cpwrKey = Key {asciiKey = unsafeRange (fromRight undefined (validate "4h1kCFffI4sHePSIIfS1"))}, cpwrCode = Value {asciiValue = unsafeRange (fromRight undefined (validate "jgfbzV60"))}, cpwrPassword = - PlainTextPassword + plainTextPassword6Unsafe "[_.VrDh\1015708\1032560\&3\DC2=M\163597rhfOlZuP\65504\DC1\SUB\f\rx\FSJ5\f\DEL\181294si\166877{P\CAN\GSG3%'O\a\f\RSa\1092468x:\1053642\61514\1073484+\39638fMP\1054011B\\nu\\\SI:a4\15010qI\n{\1029779\NAK\1041484(r\44941EI\13466G\141832>\FS\1022348\EM@*y\US4{,\ETB\151574p\ACK\1107549R\1055583H,\DC1\v\US\1009911C!\SYN\1027699i}2\1006393\1013086pu\t?4\ETB\35803\44095\NUL\t7&\f\94064\993295\1068521\1077762\t&\ab\160257'\NULM:\29880oI\DC3\ENQtG\DC3`/0\RS\166279v\b_c}m\UST7;he\155120#\99948\1018238\1062963S4K\EMR;\ETB\US\ENQ\1021792\STX\1003450:\24440\DEL\EOT|p^ZN\30349&WtKz(S&M\SO`\SO\181996#\1011887C:^\ETB\147530f\EM\a3jp/\1058108|p\SYN/9?Wn\13780\RSH\ENQ*\168131\1075215\119182gh\2225\1089941T1\133460\77864\1037953=\986510\1004229&1Z[\1043805\1002639\&4U\DC4\998270K%\DEL2\USp&q\1055724o3QhHE:}\ENQhil\1096277fc\f\SYN1U\ACKTK\DC2\173882!4Ch>f\DEL\SYNV\49106QcXO3\t\SYN1\185658\147541ii5;?\ACK\1023746\994599W\63325\DC2\45506yDu\132949\140075\1007168\"\EOTVsg\1088989`\1042945:\38432'\STXE\992832\SYNJ\ETX\64654\DEL\RS\rV)6K\1001241u\n\1061707\ESCWq4k'xZ\CAN\1004671Pp`\78706\DC3s\vb'\1026286\DLE\51253\49630.v\1078713W2u*\1026823\f\rc;=l2.\135778\1067475\66363'AT\1038064\20692mc\ESC\DC3?Y\EM\1043502erF?lU\177756\SYN2\137736ZW\SYNe}\110678i\r8\1045526%\DLE\1060820Wu\ESCwr\SYNZ\984526\DC1\DC4*F\1025876j\4244\NAK\69844\SI&\24155t?\SYN:\996677\EOT\1096939\\d\ESC\rV\1048902\DLEY\SOH\DELHDi#'#\SO3\DLE\1033528\1066728hP'\SI>,#;B-\DEL\ETX\FS\b\1080220\\O\173118\155899\33548\161628r\DC3v\1036063\NAKwY>@P{&\126581muC\30489\DLE\RSW\DC3bzp#\SINO\ng.f\SOH8\1044888\USM3\STX9M#\31452A,S\144295\DLEiK\ACKi5\DC2\1106504\163392\&9\DEL3~\SUB;z\37537H\SOn\74309\1097966\22046h\SOHH\SO\1014941rSW!\1076838\1019303\ENQ,Texo\1103981\\U\60688\1107601ef~\NAKA\CAN\1095090\b1\FSiW\EM:i\1063110\100555\1028434\f@\45876^20\EMn!\1110881\ETB'\t`\"^" } @@ -110,7 +110,7 @@ testObject_CompletePasswordReset_provider_9 = { cpwrKey = Key {asciiKey = unsafeRange (fromRight undefined (validate "8QW8mjnVnIisvrtQDzWV"))}, cpwrCode = Value {asciiValue = unsafeRange (fromRight undefined (validate "qXaaBJ"))}, cpwrPassword = - PlainTextPassword + plainTextPassword6Unsafe "7\CAN\995057\1082858>#\149981T\1113543e\thUr\189434/\186737o%\DC4\SI\8198o6n8\20176c\1043600[C\1057789\&72@t;6t\169068\11814\120655\DC1\EOT\1079958\v\aS\SOH\EMey\ACK:\aii>\1079059u-<\1112894\1083324\SYN[#b$<\r\1056477\1033082\1105819\ETB!eWg\991833d\DLE\CAN\ETX @\SI\185824\&7\b(\40642\&3\NUL\1110157!X\FSe,t\ETX\1095428\&3\128629\1025661p\1000552\184281\184297l\25688V]\1068327v\152194MF\v*\1050101\1065061 \ESCT\SUB-\21105\&0>`{|bal\1060553\ESC\US\GS|\ACK\1028192\DEL\DELV\143705dq'\DEL6mCCjv&\1015677\DC4n9\1022140My[ K0p\\`6r\182750\1080218\DC1$|#\137636H\DC2?0{%`\STX\1005371k!\RSIg\SOH\SYNJ\FS{9b\1059876/4@\1060707ldKAH\ETX'8\180338\178999\1013270O\1075685Dko\23121\&04%/N9B\1003052aW*\1070751\1043722w8\SYN\RSr=\EMnX\1071326]\NUL\GS\1082718\139251\1079728\DC2EfW\t\SYN&G\196\&2\1008326b\1023329\1102771\1047159\&5[f\NAK\100090J/7\26364\t4\SOHS\CAN;7\185137R<;`L\1112382\1022626\&1?yCIiS\153111\GS\FS\EM\ESC\156314LH\140232\\:K\1002577MP}q\139293J\ETX\151699\1052232\1108510\NUL+X\1029314\181545D}-!EF\SOHE|\131183\&6\39841\1062330\21504\SOH<*x\179748\1015132k\DC1\DC3\98575\ETB\EOT|\SI~gr\DEL\2694YyEY)Z\155604&\DC4\997375\1004619\36183\151489\143359\29364\DC3P0R(|\1044843(%Y4\1044821?3\ENQ\v\ACKU\988376\30638Y\f0L\b\986153\STX\997297,\ru['" } @@ -119,7 +119,7 @@ testObject_CompletePasswordReset_provider_10 = CompletePasswordReset { cpwrKey = Key {asciiKey = unsafeRange (fromRight undefined (validate "Myzdj2g7NTl0ppCPXiN1"))}, cpwrCode = Value {asciiValue = unsafeRange (fromRight undefined (validate "BBwqW3"))}, - cpwrPassword = PlainTextPassword "\ESC.\63992\SYN\128619\1086386\&0EI\50894\1058818A\ny\65231\1092012~\CAN;p" + cpwrPassword = plainTextPassword6Unsafe "\ESC.\63992\SYN\128619\1086386\&0EI\50894\1058818A\ny\65231\1092012~\CAN;p" } testObject_CompletePasswordReset_provider_11 :: CompletePasswordReset @@ -128,7 +128,7 @@ testObject_CompletePasswordReset_provider_11 = { cpwrKey = Key {asciiKey = unsafeRange (fromRight undefined (validate "nQSYG43lVn8kYS-MPtOO"))}, cpwrCode = Value {asciiValue = unsafeRange (fromRight undefined (validate "5BuwQHalK"))}, cpwrPassword = - PlainTextPassword + plainTextPassword6Unsafe "\1045282k\1026750)t\NUL\15552jgI\ETBP\SO\188738\147525\1066604G\39626jb\f`\nTq+Ut\92361\27743UBpVU \992919f9\21139\1020059\&1hYp9Ja\147132EBN\DC3t@\1079146i;P\1042445\180008\&8a\1091375T\158952V\138448\SI\18953fx\11087d:\SO\\\1054972?b\NAKKtz\GS\1104407\39067\n\1074206\&3\SOH\1025715\r87\ENQ9@\5471Y\ESC\62699\11493O\1045551\ETB\10550\1037708$Fph\US9\ETBe\fC\20273%>\USP@\STXo\34112h*\1042645\1104430\987562E\43000\11020\32229Ft{A=\38646#k\SYN\185887Fi\99911>&oy\98658\f_\1099272YIL\65827\&2\184583\1063350v3\RS\DC4\27853T\141265S\1048343\NAKK\150089\&1Y\1059308\NULk\DLEy\1067797\162645\92680B\78890 of ," } @@ -138,7 +138,7 @@ testObject_CompletePasswordReset_provider_12 = { cpwrKey = Key {asciiKey = unsafeRange (fromRight undefined (validate "NeuDtdyLCvq11nGkkEal"))}, cpwrCode = Value {asciiValue = unsafeRange (fromRight undefined (validate "sj64oWB"))}, cpwrPassword = - PlainTextPassword + plainTextPassword6Unsafe "an-<\r\ESCa$\n\SOH\150355@, daTW\1040876\1086641\1008932O\a\173984\1089573\187195E=\1033471\v\142301l\ESC\ACK\ACK\67616g>h(;\983436\ENQN:d\6803R\SYN$<;\18099:@\fWrO\119365\\\SOHE\NULX\SIL&Qc\143803\ACKKx+K\FSF\US\1018415\n:\\\SOH~a\147255\78868\RS\DC3Jr0c\DEL5\1004711\&88\1048447(m\1018537e-cWRQ`N\1091454\127355+i\DEL?\DC1\172339(\1079229\1021542\1023479\1095290\NULzNO\"h\61187q\NAK%\148825|\8495A\171333>\1023153\NUL*\144781\b\1096599\&6\ty\1084884\1106372\&9\991761\&4\54666\993909\&69\188610\78768/\EM\3120\SYNX\160680\1093419e\1101140!(|\EOT\180765C\15108\GSj\73803\t@\rsQ{ZZ-\22170\ETB_\EM&6#\bsD\v\n\td\74406D\37637\&9\72882\1015558\&3\NAKN\1028309Fnk\ENQ&\EOT3Q&\28043Ys\97711H\181981\999099\46018\&5VB\1044294I+\1104448\61690\&7f\12643\133501]\r '\163623\SYNmbE\1015369\n2?HK@\DLE\DC3\DC3\1023424Z(\DC4\SO\DLEk{r6%*\1034286\EOTM=/\STX\1035914&4\1098394\b)TI}\998716[+2\EMV\SOH0h\v\18412@\bQZG\GS_\DEL\999345Cim\DC4)m\1021546\RSP\23785zv\50314\1005770mi\DC1\100847\1042938\USC-zT\SIQY^;f\NUL\ESC\30038N\93068\SOH\DLET\1038908I\DC1\187625n\DC3\CAN\\\r&<\SOH5\71118\1027153X\1092148mF#h/{\DLE!(\16202\&3\ESC\32283\185971[h}I\1071533:\183293R/\1004445\140257#\"\1028937\v\177329\61790_m\138219(\SOH%^\1105873[\1035020RF\CAN\1054790\24076\DC1\FS\NUL\72138\STX\ACKd\ETX>#qn\SYNw=}\1001530\177147&^\NUL$BP%5\3450\179283\DLE\ETB\SYN{y\34999\1114051\NAK:\17208na\133899\1014430c\1106626NDB\160028\15282u\43902Xpr*#\172705\a\SUB\188880\1026535F$\ENQ2\ETBB?Q'asS\ESC\96583\DLE\DLE\1012383e?\f\STXT\1096814Q{\DC3R\CAN\1065288$\1074134j\SI\135241\&3\DEL\1035586\1073529\43493\&9ecd2\ETX\139431@Pvv\123147\157284q\r\1091419\1052105\23426\185829\1098874,[a%\1087411\"RLOU\31476V\1060394K\NAK\EOT\180111S\"Wes\ACKH415\78735-S\SUB\DC4h:d\1036393NZ\t\1043380m\167051i& \1107753`dP4/\DC4Q\ETX\54045B%\186624\&0;Nb1\DC1\EOT9\SO.\1014579\187014q\ESC\1078099f\ETX\64604H\1060225\vY\RS\1045658\DLEC\179470\a\NAKWw\ENQ\1035817[3^3B\154130\"_\nPK\1076894{\ACK\ETXO\DLEr\SIvc=+af\SO\ACK\1101910\167540\STX@\GSQ\1011496s\ETB^c\CANwJY$\1107843s\DC1Gs\1049240\DC4\NAK\171080k\US\ETB|\1065322\EM\1035477tJ(\1075051\1687xc\b\1056830q.\34099\&7\NAKF\1023165\DC3C\a\172318S][\DC1:\ACK\26422qL\1039209\&2\EM\44805\ETX?NG|x\1065136>/Iz\1061649ms\US\SI\1005398\131153\159667\&3\NAK\1048772\997425nd\tv4\DC2\1080172\1101786\v1Iw\1050069\v}?1m^\STX5#V\147028\1063172w\EOT#\1030144\145884\f\DC2\131840\6065\FS8O\NULS\ESC\1033971X8N\142482\1041006\59926.\ETX\163181\ACK\DC2\RS2\GSr\EMV\nK\NUL\DC1\1019014\30036_W\61065\9477\SOH\1094473/\392\20690\159848\181387\EM\vGDR\188046\SOH2G0{\FS\1084240JX)\188982SE\176663B\1089777\US\132402&\SYNg\"\DEL\1902UCP\1054969\1106547\1106033YU\EOTi+,?\147075\1044086\1028895\1110977\1016778\1106548\DC21\186874\1095378L\1030254\997653\998721\SYNc'\ESCp[\STX\EOTN\ETB8^\1021121Bk\b\t[" } @@ -158,7 +158,7 @@ testObject_CompletePasswordReset_provider_14 = { cpwrKey = Key {asciiKey = unsafeRange (fromRight undefined (validate "4hqO6D9=V3BKXLXcLie2"))}, cpwrCode = Value {asciiValue = unsafeRange (fromRight undefined (validate "emsaYZVuPvQ1U"))}, cpwrPassword = - PlainTextPassword + plainTextPassword6Unsafe "\1028432Q~\7949F+9Dc7\1026106jl\SIC?xdB\ENQx\33993\62067\ETX\DEL\GSj/^#NS}fiO\119558At>\GSh0U\62526`\r\aV\US\1112085_v\33980w\ENQ\184054\&8\11831\1032958}e\ETXi\NULRC-- \37583Xd\ENQ\an/,?\SUB\SUB\1066224\42328gQ`\70388\41959\1012806Q\US\ETB\184603&LR\149821>\1012033tR\DELg" } @@ -168,7 +168,7 @@ testObject_CompletePasswordReset_provider_15 = { cpwrKey = Key {asciiKey = unsafeRange (fromRight undefined (validate "bbFHebGp6h_3F4QpSrud"))}, cpwrCode = Value {asciiValue = unsafeRange (fromRight undefined (validate "K_xVBcX5bLpjvL"))}, cpwrPassword = - PlainTextPassword + plainTextPassword6Unsafe "\ETB\26894'\fd3E\120233\1029573\1064918Y\r\1104541H~4aF\16111&&\US\1085044\1086081Kt\2880XoS\r\DLE\NUL\1044509\v\983386q\182823\1075779\148618N=\1062701\1004214R.\\\t6!E\164333\GS_$F\STX\DC4l#!\ETB\DC3+\1078110P_\1037691\GS\1003847~HhsY\1008817\CAN'\996168wy\ESCA\1096877\&2\170187\1060412\SOHO\SUB\ETBc\t\1090646ud\1037884\CAN~\173115\1096337\rB7\1049690o\DLE\1095190fL\996695\ETXi_=\a\SYNLE}<\1106966\aEl\49881\v.6H\CANw\995916ZH\176178\49327\19051o1?\61005\1065006!W\DLEY@!\1058199S`mq\15087\161424\167582\a8\127764~rL\41008\171779\DC3[\989714K\SO\ETB\152791$jRxH\SYNm\1076533\DLE\169669'\EM/xd\52526\95412\&8\ESC\38505\&6bYZ%\139602\20809\35764~\72852SG\1075777`0pL.\185639\&4f`7\DC3\1113337*\v\60813/T\180136C\1111167Z\SUBZ\44799\DC1@\\\1060472\&2\EOT\11121K\1039363||\DC1h]3&\DLEY]\GSk \EOT\SOHU\161853.\DLEk_\133547O\1041592h\1083420{\64532\aw\ETX3K\41041l\1069560\a=\EOT\152591\"\fyH\78163\SOH/L09\149680\DLEvw:\ETBO\1066598%#(Js\169845'9s_&9\32941m\149591$\1021728N\984156WE\DC4=y$=3\1083024\21817<\t\th\1087258\NAKx/\999799V)\STX\183098\1073874BI)\\gk2I]#l\NAKPn$\172450\ETX&\DC1;\GSY\EMO\180851\ETB\1095722d\ESC6\151131}$\175277\NAKajf\1093922v\184717 \ACKa!\v\166519\EOTS\1012345'\153953j\1098235\EOT\FSE\1061729\1052832#5Zg=\172012\4883\66029ZU\36791\22747&C\STXZh,\1088719\7021\1087041\ENQxC\987916\39597=l,\fu\998370\ETX\60675 }&\183212\989435\165094\1040277\135097\"\v\SId\US\EMJ\59927'6WK\13266\ACK>\70807\995567y\r\"\98652,\DC4\SUBd\NULne+\64011,&!9Y\15584T\127281<\1077668d\31074e.#w|?\1034255G\1027753S1\24647\\\1090505\bz3\DC4,\988313<\\\1073727\DC3\1032879\997224%-\64532\EOTC\ESC!2\156292\145116DT \f\SIXja\\R\1014521\SYN`\CANJ\n\45882\1023562\SO\13921ab\NAK d0\SYNX{:\51467\CAN?\187194\&1txQ]\1005159?\176303[)\78300\&1O[Xl#\DLE\38014\50691~g\1043081{\132217_g\ESC!\SOH't\1101558I\1003044\1063761?\137915`Nd\182690`*1rD25c\169907owK\20714(\1055173\&6i&j(U6p\1104351zK\73918mE\11375\vjr:\43447`\1094897w\SOH\SYN^3\DC3<**`j\f " } @@ -198,7 +198,7 @@ testObject_CompletePasswordReset_provider_18 = { cpwrKey = Key {asciiKey = unsafeRange (fromRight undefined (validate "m3ruXwhym9ERHyTAJo1y"))}, cpwrCode = Value {asciiValue = unsafeRange (fromRight undefined (validate "88xU9QOF1FPXdL6e4"))}, cpwrPassword = - PlainTextPassword + plainTextPassword6Unsafe "cW\ac,#\12631\n}\188543\EOT\NUL`v\STX\48639\SYNO^\1061062\1045730\1096180\tUs\EM\SOH[b[uz\DC1\1106362\DC3-\995083\1054859+]\GS90\DC3X\GS\v>8\154400\78507\t!\SYN\24475\&2 \RS\fW?\SO\EM\17276>\\S\ACK@Qt~8`T\1073440\97220a,#\1054560]X\1019169-\1112078]\1005234\SYN}\1112649T\999008\1062688\rH\171818d@9/pab%\52983**pK4q$\984140+e\NAK\USZ\DC3\29392\&3\176130\1073376(\ENQy\\(5$'|\2800!_\154876_\1073420\&2\SYN\996743\187317I\DC4Y\"B\1049376=\1103936\&6\66599v\153333\NUL\137084\119859\147584'\16885GJe\FS\SIPk\NAK\ENQ#\29575\23580\SIa>m.\161669to\SO\1049661_\v\165212\FS\ETB\ESC\trn\1029796\1078206\61317\rM\149956\&2S8\a35\\\ETBo\191128\DC4\58762~?\178576\STX/\nNZSjbWH\r\1098678X\993718\4120/\1046632y}#\63730\ACK.Voi9\50993\f*Y\STX\1001056\43180\DC1\rS\1021396\144641qSj\17576X\149262\1081745\1076445M\1063531\22347\CAN\45875\40887B\NAK)U\"~V\1036888\1007909/S\61542y\SOH<\b\ENQ^\SOY\1013585;\SYN-RKy\ESCO\1033537[;\b\1094937EDjW\997383\182740bKx\128165lZ\DC3J(\1032322\&1mK\DEL\69897z\131148\144121i/RxiQ\1085090}E\23345pA\1065790q\v.\v\ng\20319Gb\46475Kt#\USP@8s0\vg[c\169328\DC3\US}{/\1002448D8\170376\159999\987435\67200\1053165M\1079934\1073683\DC10i\159626\1111106\ESC\"\1019962\SYNg\1025072M\1022474\1059584IsD\b\1086244\70682Z\1015255g\DC2;\SOH\1009422cQ0f\STX]0p<\1065421.j\DC3\r[W^rsM\fU\65479=h\1059093L94\993336oNs\1016719Z;8\30468lw\t;S\GS`V\\f\993287\1001923\49875\1018016\1032042X\ESC\NAK\132703sb\SI\1110714C\ETXi_7\138308\NAK\35645}\12913\100683go\FSVj\vtr\SYN\181280\166083;\137762\161816\EM6\1068253\1058678/\n\71064\fp\1036795O\SUB\45835>S1=\DC4>m=Li]y\1014422\r\US\5961+D\230\54691UWo@\1104594n/\EOT\FSDR\1084131\&4\CAN`-}/\v=<\DC1\1011393\DLE\SYN\1000229oB8\1073774\fT\185994?\DLE5lJ\917988\1051232\993358\&1\\\SYNGx\160450\993275HF\988493\1096467N&\DC3A\1078985\ETB\1085595\71193@\a\SON\ETB7\RS8kT\13512\SOH\128792}!`].7C\ACK\EMa\991996\SOH\ESCR\\Iw/y\1052927\162141;*!\SUB;)\1034215\DC3\GSjQ)\98905\1083130 \aQJf\143466\3112\1088669q\183516D\47434Z\r\1051585\1066298\1011799\DEL\31175\1077158\19157\f}\1074960\CAN{\1026108\ACK\165269\989993\1021383\4839\993646_9\FSYzI=JL0]\45720/W\NAKD\ENQ\143508WJ\CAN\DLE8\EOT2JsPn\1025590\20415\bhB\DC1\1537Xj\ESC\GS%:p\64920@gL\ETB\1087542\145056\1111605Q" } @@ -208,7 +208,7 @@ testObject_CompletePasswordReset_provider_19 = { cpwrKey = Key {asciiKey = unsafeRange (fromRight undefined (validate "nBmwchpz6q_fDPCPZYQe"))}, cpwrCode = Value {asciiValue = unsafeRange (fromRight undefined (validate "AHGkmRBXJr="))}, cpwrPassword = - PlainTextPassword + plainTextPassword6Unsafe "o\58515ZHN\83385\a%S\1039235I\1072272\&0\DC4!\1105051\&0\DC4\FSc|\SYNo\r\141433m\DC4\1017077&\ETBX\GS\101008sXzlOS\1086195nZ\"\DC29\30572S\n\1028791\1110650\SUB1>\155164*K\63527\917991\34537\"\152219\174017GW\165251\917540PPO\1099839\983424\&9c\1001124U{fLy\SOH\FS\STX|\35808\&52\SOHP\n \126255/\42693\188778s^_\1012451MW7M\EOTs\DC2M\RS9\ENQg\1084863\61924@%o\DC2\DC16m&B\95458\190903wL\1066100o\r\1082662y\ETX\tk;>\1108088\1053265A\US9\4469\STX%\44556\SUBghb\1046982\DEL\b%0\1011473\16374'kz\61155\126123\NAK\1040333\&3:<\1028617\12709\1085958X[g77\59354_\ETX\1018780\1031200\&7\STX\GS\163106\60867\n\f\129490\993680iG\181984\58073\168758\44094`i\49314r\65104\GSu\1030407b\1002850\1053366O)\1042687YQ\190781\DEL=fZa)T0j\1070016K/\1104693%v\100085qsY\1017025R\33451\997088\&3a\45926\r\DEL\ESC\1031881\NAK\35199\183615'a\11657B\111310\STX=\RS>n\1055557)d\US\FS.F\1111038M\133759\1021129\&3x\ETX\51747\1102182\11790Z\35206\&3\173723P\RSV3GeGN\v3\28136a^k`\29343\22637\rK\SYN\NAKe(\GSQ%b\1080735u\DLE\ACK\NUL\99272\1095099y{\61538\&9IbP\SI% \CAN^\1064103+f\144228\59518r\18266G\DC2?y\DC3\1073829\RS8\34346}r\45176|y,\b\1026988\145851/\DC1R\1017813\&0W\988979N\NUL\35979q\bc\1091121\NUL\1087940\GS,d\CAN{J8[\1031817\CAN\r\138592\EM|\nz\28160G-{\SI(1\47823\GSl1\18854iL\77903j^\DLE4\ETX\159954\1105693\83316\FSoj\ESC2z\STX\1021083t\17703;K\STX\DEL4Yhr\STX\987287fO6h\158330t\1076871\&3Tpef\SI\ETB\1109588jC\150352eh\10328nf" } @@ -218,6 +218,6 @@ testObject_CompletePasswordReset_provider_20 = { cpwrKey = Key {asciiKey = unsafeRange (fromRight undefined (validate "n7oUiCMAvjokyCwCwIZx"))}, cpwrCode = Value {asciiValue = unsafeRange (fromRight undefined (validate "padtz-lbyFICM8PEzCj"))}, cpwrPassword = - PlainTextPassword + plainTextPassword6Unsafe "n.\30576`Ab}1x?dyrWEI\SO\b|\r n\\\174375\163710yzk\CAN\1032873\13076q\1104973h\1078766\1065080\1073163\1024180\SYNzt\1041454oY\SIv\1109814\999708!\DC4b\r\ETB\139978\1037986\&97|\68831Is\78227\48210\984397\1108736R\1076978y*\ESC \1080452\6350;\1002645*F\ETB|l0\\-\49792/d,76W\SOH\1091954a\52507\ESCkY6\EOTe*6\1068076\1010489\STX\1109890%B\"\DC1X\145857Z\1107907\988601\SOH\r\983601\ETB\1027493's\\g\SI_=\187494\52638\139634oOcZ\EM ZWd\EM\STXe\25610Zz\1055806\1023881Me\25012\DC2N\1061919o\154179BmCD\ESC\146744\165530Wsw<\ETXs\SO\992482\11825,\NAK\97960\ACK\1112588je1m\1080113\&5\CAN@\SI)(\39581y%~d\1022649\&0z1||L{1\EOT\1083342Ja\74536\EOThHi\NAK\1033200\SOH\CAN\ACK\175120\183861\DC34&q\GS>\SOH\184139\SOH\ESCQs\41951a\59763\1069217bv[u\bX\1078841\1048633^\1015710;[\STX\DC3\1001312jYw\1003565\1077047\te3\148232+\7427\\\SUBq\1108026r$(zD)\\7p\"\168984\STX\59311?\8657<\NUL\1035836cP\194909[>\USm2Y\1010432\1106430\21518P\DC1.\1074512\35480\ACK\EM\SOH1npVW\SUB2+\ESC\1059649\33997\GSk\v \993759A)*uk\1030453L0\1078688\1044139\DC2\1029875\DLEn\EM$\1054292?\NUL\vji4Y\DC1\EM\1027716=/S\1024040`P.\ENQ\SI\GS\1090161\50097mww\61962\59664e\994460\1030466\f\83226f\"\CAN{X)\v\4796\SYN\STX\119946\DEL\992301+\39597jv^\169149\SUB%C\"]v5?\185720/M\991044\1010224\1027231\984290+\SUB+\186874VG\SOH2\1003544VM\SI\f2'\1009297\1059762?Lx\986666<,Q\1009359t?\1067784\18910\GS\CAN-\1090445C\2603\1004458\10478XZ\STXo\1019324\by\985769,\46054'\a\21265\DLE\SIH\1003281$J\US\1051584\ETB\r6\ACK\FS_1\45810\1013879\998189\167043\DC2>\1082944<0\47209G,T\1055523\12871\1057078:>4\1005909\1060368\GS\FSED\DC2:\rzSMQw)P\50826s\1051230^-~\95981,v\DEL,\SOH1=/GNsO\129350\&6\GS\16013_4\62900\1097318!\SOH'M\139907+$\29092\154621<~E\96994, U8\ETX\986557#\1092210[\1042274.H\DLE\1098681\ACK\1062248\"\133455?I\1005507e\167230\t\28751\1016604\159825\GS'\160639\&9k\EOTZPj\1084498\1039215O\131535L@\171949\r%2>M<\n\120995\1031232\&9/\985482C\SOHav\142062\ENQ-|\ESC)\b$\"\DC26\16379\CANCT|Ut\131524\149842\96725X\64829\v\28384\DEL'yR\1022028\1056329r\25908o\165079\1077144.\185928\&8\NUL;\NAK5\ENQ\ETB8Y\DC4g\1101865Q\1085552\150701\&7!HO;\br\1026135C\24186\37827\SO\ACK\165967E\r\DC2\DC4l3\1090105\127078\DC4\SYN\bUF\15427\DC3.wO\EOThm\164680L]y&\1024985\&0\308;Nwyw\61385#\r8Om@x\1007233\b\ACKo823U\146708C\SIMN6t\DC28\1047608\SOF\ETXRna\a~\r6\fE\US#\tn\1006471wr'B\rnlolj\1017148\144338\1087477tT\119355\1044444\SYN|4G}\SYNEn\1000211\&0D\DLE\SOjn}`0\994578+\1019070\184767" } diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/CompletePasswordReset_user.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/CompletePasswordReset_user.hs index 3a45f407e8..a52b2589cb 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/CompletePasswordReset_user.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/CompletePasswordReset_user.hs @@ -17,7 +17,7 @@ module Test.Wire.API.Golden.Generated.CompletePasswordReset_user where -import Data.Misc (PlainTextPassword (PlainTextPassword)) +import Data.Misc (plainTextPassword8Unsafe) import Data.Text.Ascii (AsciiChars (validate)) import Imports (fromRight, undefined) import Wire.API.User (Email (Email, emailDomain, emailLocal), Phone (Phone, fromPhone)) @@ -46,7 +46,7 @@ testObject_CompletePasswordReset_user_1 = ) }, cpwrPassword = - PlainTextPassword + plainTextPassword8Unsafe "Tu\1075381\&8\DC3\"9\fB]\178630\DLE<\1082059.C<6W sgS8_\1082572\n\nD$A\DLE\39966I\ETX\996087\36300\GS>\74838S\74072EI_\1003352 a[\148144\995126\119062\ETB0\64445\t\1024616~\51451$`\DC1\132612y.Q)\58664\SYNa\1005229\SI^\169417\984185+\bE\142814P\SI\SUB\SOg}>\129335>\NAK&HS\179057\988796M\DLE\"~?\1025845gd\21005\DC2\\)\1061144F\USI\1107931V\127768\ACK\190997[T5'wP\SI\DC4!y+d\US\181414\1008428\1003322\ah\DC22r\SYN~\1036760I\SYNYO\EOT\1096694sT\140136\&6q\DLEXjp(\7195\SOc\FS\DEL\a\1073646fc>8?Sy\FS\SI\47755\nN\v&O\20470\1050569\ACK/\ETB\DEL8\190265\1097196\168739\98170k\31543\95657b\ETB]\\\CAN\DC1;3 j3F+\1086683.0\64117(p\1069437H\ETBj\12185d\SO\143221W] Hz@Eb\1065092zW}\US\52827!\SYN;\1011087T\DC3![*BS\1069863w]\50081\&3\DEL\ACK\1051925\177178G.A\EM}%\DC4\vk\172371\DC3Tf\DC1\ETX\1008924\EOT&\996766o.\65255E?jK|6z\1080190BC\DC1\4815\189727!>\SOH\987832\t;Y\64130\SOH#\121348\SYN`w%\STX\CAN\140041zT\1113224\&2\1112857\CAN>\220\94946\161064&7 8U\RS4\31842\1086714\1048811--\"\ETB" } @@ -65,7 +65,7 @@ testObject_CompletePasswordReset_user_2 = ) }, cpwrPassword = - PlainTextPassword + plainTextPassword8Unsafe "a3d\44313\29830ps\a\136373TjCIX\1071131~\127114[BRsm\38256X\3615!<]{\SYN\131207;jYj8\b\1032200\fNT\167561\ACKHGp\136999\&7\DEL\RS\1004158\83348>7\SIJq<\v @\152552;\RS\STX\92420\SYN&}\1062674-\3615\139147\191067Ef\1082924\EM*\2415/[x<*;0\134125>l7\9860k\EOT\\\nhl\99518q\1059994\1025015\&1\1072462\1056328\b\1036337 %R-\t\1019852Sn\1041872\SI1\985289\NUL\DC2\41090\US\37274[6X\98569nRMHw#tB3Gf\r4S%\ETB\rC=*rQ\v~}\DELH\1038274\&8\NULMyd9 p\DC3\SO{@\ACK\ACK\"rs{Uo9\DC1\t\DLE\ENQ)0\74243m\1039889\RS\1091756\&9o\51528\1108221\r\439d\ESC\137380M\ACK\b\1106682:\GS\SOB0\58417\1105367ss5G\183138nmPIoPE\SO2\DC3\ENQH\ETX\18579p\1049009v\vc\1002107\1011754o^?\153218#H\39389@\38468@\100660V\119100+\FSk\1109219)/\181730\&8NO\4502\44741\&95\180373WT\992017\5700i=e\STX\184895\RS!>\166049`\1100744gtG 3\ACK\1056868\a=x8\DC1x@R\ENQ$|\DC4;ZL\SO\EOTt!\SYN\94514\&6^'\14966r+\ACK}\147730\STX\1099738i\ESC\1100952\&4k1};C$4\v\ETX\ETXH\1087817Z#=\15767\n1%\1069094 \1102739\17301$fz\DLEcA\27111Vy2#\1009541\rF9L\1057880\&2Eh\FS\40121_\DELjm\33979\&8\29452\r+/.{K\DC4\\\71364[\1078116\1084406\41552+yU&F\ETBj\SYNN;r~=\t\158066\988192\145386\&1w8ro\1079235\988454\STXd\1090982mN\1078789\39626D\DC1Ne,b\52130\SIB^\v\NAK|E-$n]\150608\1001296d\f3\SUB\NAK\EOTw\1017423\13701\5962'3\94618\RSO\EM\177775U\142455\&9\1083144:tS,?\STX*\a\EM/\rN\43879u\1041719\&4:t\986292~wGh\127829\995525%mVc\CAN#rd\a\69239_T2m\NAKrDfO,\ETX@ \r+Ab\1112886e\ETXH&#\23182\20467[\18409l1\57990\1019597\SUBe\153649'\16083\NAK4\176\v\13290*E\ETX\CAN\DC4\SIJ\987987jo\95783\&3lo--,\136332@\vHTw\EM\27585\994816XY4\95101u}qFJ\1047741\SUB\1004402<\\\1030039,*~\t\bed\1013495\a\n\1087537u\30465?c\22040\\\188453\CAN!\1099013\GS2s\1070087\1019176Y;\194995\ETB\1069790=]Qs('p)\1026694o\95259\120749\1006590\1087681\1075995\1087348\1055836Q\DC1\286\985351\4312\SOHW\1011595\EOT\SO\RS(|3\28370\25459|\vtfO\1092020D\ETB\SI\SOtV\nFJ\EM%\171766b\UScc\EOT\14709\1091572\152547]\r\38490\ENQ\23972q;\SUB 9\DLEL/\1016110\ETBT\7738\EOT\DC4\21916\153808m\1087717\1090815hwib\152516\92752\141123\SI\1107794I\170689}k\SYN:]4\1112947^h\1041650\1044941U\1016696Q@<\t1jU\EMH6iA\1025178YG\DC4\1009215]7b\CAN\152006\&5\58155\&5\1042194\66650\SOHs-\71063\SO\173468;\NAK\DC4hb\152591(\1090066kKv7\US,\f`\64204\DC2\139140t\175577af\"\1005915w[{\993617V\1001027\&0 ]\DC2\161723DbWCZ0\NUL\98681iY\61269vW\EM\181126i\b\147218J\ETX\RSnuC\190207\&6\1081950A5!\DC4F|G\1074732}\1017103\ENQ'l\98651PP\51035\1048386\DLE9\147758\ENQ[\1000903\160498\EM\51632\SIOO\158007\44043\1081671=\139448\&52gc)-)da\STX\1111103\DC3\1112911\1059428]\ETX\1081735\1092992FBo\"\136534h\DELTU\ACK\ESC_z \1015887d\ETB\52927d\ESC\SUB^@JcAF\fy\CAN\1051386\1015818\SI)\GS\41728W3Z\\J\1077392(\1091646t\1099630\f\FS\139631\1103829s\1045024{<9\SUBOBEj<\STX6\185426[\157863\b\1020557\SUB%:\1056059?\1923\DELx\171156x\ESC\65232\&0iv+\988571eF=\1100724\190781\1013223EP\RS4\1014784\&0\FS$?\NUL\72256\1101734c\1026017\CAN\rcb\96496\158485l\166631\1073857J\1072152'\36926w\SOH\163827,>\1004871\b`\179789\179824\1002436p)\132407(ol\145584t\16650\"\NAKo\160744\27930qsiAV\RSHaK/\DC4s\ENQ}\990099\ESCG\1102699\FSd(\"\157273m\NUL+\SUB\1044674b\a+[Z)_@=r\189255}~\49380\&8bE.\EOT3\133006N\b8\FSGn\133491\DC41\165251\FS4\DC4\f;\f\1067211J\t\1064422\1083725O%\6166D\NAKD%6S\ETB,#>\44276c\139025q\1009286\50801=TfJ]\1049633U\1090987\1090406\tB*xn~\140204myl\188730\DC3I.8G-\1110126B]g\b\USS;W\1041127\43602/\177836\STX+\DC3f\1006938J\SYN\a \NUL\1109096\DC2\b\1051438\1023012!\f\38441e\1046765\&6[U\140861\a\1100802;|O\110738\988796#B\118993t>D'wo\t#\42524o\DEL\188551sq\SIY\GS@-<:sY9P|\42677V\ESC]KQ\SO\rJ\SO\nD\1049001\DLE]PX\83306\1064468\178174\38183\STX\70365\DC1\SIO$/\1113950\SI\997632\RSK\50210AzE\175382\111345JZ\143113\ESC#*\STX\SUBUR]\ACK)\1038781\73070\183578\DC1oW\ESC`\1079062\144075\b1u88\DC4\71456\f\182516\1009566\1073420u\173741C\ETBLj\31808Nn>wD\STXvwN\1075045p\13712\1068437\DC1l;N~\1090032\1005469?\1013829Jy?W\184183\61781\996704T%\1048704SwB\131985g }O_yZ}G\997717Khr\1046472#\1087950au.T8\100298s\STX\1010463,\ENQEF\127517QF \42382U\28642\ACK\1076227\1074730*\ACK\t\1006836-BH3\26795\b\ACK\998490\DELjs6_\98507\&1|\DC3\1047406\1009924\DLED8R\CANG\1078545\DC1\1050566\983288v<\1104164\1093995\SUB\US\SOH_\178145\1055476}\1017836q~\GS?\b\53655P\1048664SV\983485Jd O\68042C\1111952)\54328\121513&\1070370\&9\1044268a\2131\43288\DC1.\19786O\49345\996208\1035987\2817\DC1v\SI\190140\100584\SUB\1037477%\1818z\ESC\t\1017722\119588B[\144627Ei\1094065\1060809~<" } @@ -124,7 +124,7 @@ testObject_CompletePasswordReset_user_5 = ) }, cpwrPassword = - PlainTextPassword + plainTextPassword8Unsafe "\156237\8492\EOT:c\1078897$\RS{6o|ap\RS" } @@ -144,7 +144,7 @@ testObject_CompletePasswordReset_user_6 = ) }, cpwrPassword = - PlainTextPassword + plainTextPassword8Unsafe "lm\SIx\64700mY\US\157\1097646\anG\\hm2\990707\94759@\US\1011493\1073683\129291\74338\70738k\1042928\&5\DC4\997060h\60626\DEL\"\NUL4\DC4\vk\3147yC\ENQA\1023742\ETB70\38101\51770>{W\FS\177211r? =\ENQA_&7q\f\ESC\DLEf\68299\&0\SYN_WP\DLE\1021503\EOT\EOT\1065458-4\51411Z\147742\DC1h\ENQX\1079338\&1hG1\175018uD\1012774-v04m\1059849l!+6\1040942\61614\DLE\RS`\50867~Y\"\1102072[hAR\95402\&0R\1006512\999868*o\SI\EOT:-J\1109450\&8\GS8PF^$\992707)H\169642ix\\\rE\989760(]!l_\1004903\26399\DLE\EMp\vEWR)#\13333\&1j~'\1055195\162244\171208\1095755\183826\SUB\172982H\ETX\SOHg \ETX\99545\ACKP\SUB\"\36838\985803u\1053240mZ\61836eROa\te\SO\vrm\1073174\DC1$2q)N\SOH'\SYN\DC4\1016164\1018115Q,P\58405i\"\64433]\GS-&%\fgKib\185920\ENQ\f\1031003\184381\150165\&5JV\148891.\149578\&1\154582\&5\CAN\987132\b\ESCfz\DEL\SUB\RS]1}\ACK\ETBUXlpT`\152367\&4%\EOTH\"\988810F\"\45177MAHa54\DC4QM\1109997\SO[\1113252/9aN\162376\1012532;oI\96047QR\53331+\1004614\1056061!y~\1091556\n\51298I\1077939|m\67254\EOT\DEL%0yMLir7E;\USq\ACKxm\DC4\1105802^/Ui\RS\190838\&32\991950!t\tZ\1058106\b~}o]\vO\14353\1061463'l$\RS}Uy1\f\FS\159057\NUL\FS\"W\SYNK7z\DC4\US#\1060362\DLE\ETX:\182927O=Wx\127159zR\FSP\1025104\1084512nNm\141492\54516kw\1096168+\28681n\CAN\NULW\1006153/L\1022307D\194734\94549m\SYNjOU5\EOT\DC3\GSe{P-\1016146\&95r\13539x_\1089200\SOojT6Y\161102\v\1006119V/Csbbwf+V\\mu6\1027479D\SI\1027110\v\RS\1007185M\159915o\1050522\26252\NUL\ETBys\29086CN\DC2|gNc\DLE=#c\38985\132906\&9m26\992990Gb[$^\DC1J\ETX\1110084SR\f\RS\DC2;\ETX?\b\43794\59646[\1048757\aO\25052\60145\1007943\1028907K\tp\ESC\1025903J\1024238,L{\DC4iA\6474\vE\SYN6U=\1107989\1095661\ESCC-\DEL\62245\1110781\1112386*oh\1065925J\ESC\992849\DC3\USf,#=\188596o\US\ACK\1020404\SOE\USy\5531\129349sJe\b\ENQ'o\"\1026175\f|\1091357.\1059323:\1106347\ACK\134543\1057161m#4\b \tgl\83000\n\US=\t\FS?/\1034660\CAN;\1085085XOW.\DC47\1087092+^\f\GS\11244\1051834\DC1A\DC1Q\1081204`6X\ESCJ~\985505M\NUL\1092477\US.\USU\173976D+_\1100752F\1060217)\72327D\CAN\SYN\DC3h\1039040q7\FSV\40427\NAKA\n\26138jiDW+d\154046\b\1016645B\98880s\134021y_" } @@ -181,7 +181,7 @@ testObject_CompletePasswordReset_user_8 = ) }, cpwrPassword = - PlainTextPassword + plainTextPassword8Unsafe "z\179971%]MV\141181\986464phO\3659X\143435v\b\CAN\1068207\\\DEL\51152Ps\1075457\1010611\GSY\t\983543lQ\np4h\FSi\1021645\146896\DC2\1061677Wv\176539\ESCF\SYN\SO\1086781BL\DEL*\STX)M\143511\1025207R?\167872\1096957\&9h,\CANZd\1100074M)\998933\&6g\RS\n\GS\51036l\1096374nhX\1009693\1031085\128645),\ftEZ\1076505X7\DC2X\1073715\62654%\997854#\177266\DC4f?\1055981\DLEa\US\DC3\1003011\42823\STXp\1053817\&7D\f!Z&]\SUBb8>\\Y4\155833\143579\&1-MG\DEL\n\178111\GSw\137384NZ\US|=\RS\155356J\166255s\984393\1020876\98188q\a*\183120\SYNemSmVk\189895mU\39988\r\DC3uz\NUL)\DLEA\152453$\1094006?&\1016471|\SOH\CAN|x\988322\NUL42\t\1089812lm\NULz\1022172_\ESC\bk\999120/\DEL\54527!l\1028113w\DC3#'p\GSK\EM#,3t\1068160K\135067\bI?\7149\SI\f\FSQ..\FS@\SOH\ACKB3pyR\f\1047730\1112024\ESC>\bx\178913\r\1064564o\DC3J\ACK\1015652\1010510\RS15~V\999893\ESCw\DLE\ENQy]C\992636\1060824m?\SUB5$\ESCD\461\1094742\1085880\&8\93047_V~N0\142058\46275\DC1\US\1049132\SOH\78616\DEL\65422\NAKIEX%\v\1074332~\991362\156757p6\NAK\184175Z\1012970\&6\NAK\US\1071418*\1047229x\FS\aHQ\EM\DLE\9533\1049878\1079830>dXY\ETX4l\987867.*,Jt)Q[7\US\rP\SIgy\154044GB\993355\ftX\DC3\DC1OB\1008239\tV\NULKicto\27617\1013290\b\a\FSPqgE\143570\1101916\SUBk\CAN:q2\\\1060253\NULe\ENQw\1045638Rqk+\100693)\FS\988176\&9`\6293Xj\SOH\t\186270\984047\ACK\"\n$3\1008823\141341(\NULt\vb/%'\185387\NAK\DC49@Hb=\ENQ\119536n}o\SYN\1032201'\1025326\19310\to\t(\1070036\DC3\v\983672\185675\ENQ)\ETB\1091867\&9\53850\EOT\1092243\GSEv)$\9575\b^\1002235\1032326v6/\8549\1058328%C.\RSP\a6\DC3pI\11955gpH BAc\1084789Jb;MFI\SUB\41110H\SIVI\153792S\158205I\\\tZq0&\GS?/DE'\1009961\SO21Kq\ENQ\35084\1024466\\ml\DC4F^%I\SOH\174592,\984239\168715x\1035028\43951\\SVb+6\EOTv\b\SOHzA_\1046012\DLE\998932*\1085737+\7522\1049016@>oP\1041902\ACK\DC3\1090159DK\142427z-eh\SO\181642*\SO]Cb\994205\1012718EQ\123624\34155dGy\f\1032174Z\1019635M\74969M\1038424W\DC3\FS\1014867\7823!r\SOH\FS\169164\26472V\1053481?K\82977\"/N=|Jdr0\\\77990\171276$U1\121430rE\1073118\SYNYG\1034547\35447\DC4\SIs\1076916\57864NR9lmd\157485\SOHg\119340\DELFK\a%aq96\1504_\20911\STX\33475\194700g\b.K(\1117\NAK-\51093/dc},\986196x?\18309V\t\8497UC" } @@ -199,7 +199,7 @@ testObject_CompletePasswordReset_user_9 = ) }, cpwrPassword = - PlainTextPassword + plainTextPassword8Unsafe "t%\1106482'\1085275G\NULG\DC1\1065237V\92721H\54755\1048145[3\150108\ENQ\142934Y\47443\nm\v\1001941d5\1090795O:\DC1\1099456\USb`\129486\nW\EM8\NUL\STX\146413W!\DELmX\132012z-\1074871\65424 \1017374M\axT(\v-\"3Xx\a\f\990702b7*P\1021085.\n\ACKfz\EOTN\121274\EM\24372Z\83127[7\1042390luXV\ETBRh\EMU5}y\996537&\1053537UO\155409L\SOz\1004758\DC3G\22883d\37015\&5(iM\SUB\NUL\"\1022433QKY]_\v\DEL-\1006612\145121\&1a\SOH}5]'L%s7\1097491\&5u\v9|F\ENQ\179220j\US\1034740L\174323\DC4o\48579\1034153\1099174lqDs\1052315 ]\DLE\ETXf\SOHbO.\RS\GSj\1002207E\"\r\1035749\162742Ig;~\US\38681\ETBWQ\DEL\1038511\100506?\1099376\\e:@\4348\986455Cq\DLE\9822>\CANzT]\FS\DC1\36942\ENQ)\ETX\1028763\&7\172341\SOH\ETB\EOT\135000r6;D%FM\":\1053022\SO-S\NAKc`]\50260\CANO\v$\1105473\ENQ\\i\1035531\tk\133720nME\GS\CANmi\1025051~K[K\1008508\&4\DC2jl\DC32\163406w.\EMO\1036572x\1022876\27050\SOH\1072386\34498:JY\b\27018\ETXDPH5\1071848Hh\STXj!y\994661\1064566\FS\SUB0\NAKg\t\1065452q\v(c\bR=\"\1008081\DC4\DC2\NAK\SUBi\998703M\1064362\177599(\24166l\179007\DC2\136782*\32877Y\1041066\453W\t\NUL_pk#=r\1032661{HN\1111623\1021137W3 <%*0\140910?\CAN|Q\151232oE*\SIx,\ETX\15845\74304L{NC\1082549t\181800I\DLE{\NAKM\DC1<\1010573efpSGC\988853Cl\SOH-+\t\f\1090761\DC1'uWmR\1088797\133403\&9\1045264>\142848\&7\DC2s{\1000097my|\1113699\a\NUL+\1035609t'N\DC4\SI\31012\&7\1084643\1027431o\1082014\1096666Yx\133375\&5{;\146931\&0XM#\1014368Q\30231\1081694\tx\1068519M\163412^-[a|\991629\32871\EM\\=\tH\1065484\&2\DLEW\DEL\1084570#h'K\1000324\vpX\EM\SO\t\98778_~\176195\1025397G\USwX\53603\1082556\50458@/E\SI\1006180\DLEQ\157258p#pR\1007809\DC2\1043179$\n`l94.\171953gun\EOTRG\ESCF\989700Hbf\13205e^\1007389\&9R.\b\ETX;\NULN$4\fVy\160381,h\36122\168831\991129\"^\33713J\NUL#\184356\8099\78814z{|\ACK)g\1085443[DK\SOH:(jNn\r:D\1112747dM\1054404G,\1088216\148672s9\SOoy\1107184\990596\&5Y&\164376\v\153046\SYN\"Ob50\137070Q_W+'\"!\74569\t5\155405\1061053\16324>\SUB\148951\&8\96940y:U\1051061\DLE\SOH\190737\170011\"\US,q\SOHa\1113047\SUB_(j \136574B\1107369\62624\DELB!\1090388RL^\142739\b\97613\51579\af\CAN\1019397\niK\133374\EOT_\15082\52701\SYN\992724\a\50769\f\FS;\US|V\996997\DC3\DC4\1008815Km\RS\ENQ\134593\1054688\ESCnz\DC2wy\141383\1079323\156366\&5Q\FS\71249J\SOH\1058926/9Ew\NULSbe\a\35263\149348\ETB\rd1\1006994\20380R\7068\SI\128999\SOHv \\\182039v`i\61613F\183123\&93\ACKdSl^I2.W\64005\1012650V%*Nv\1094255\1047508]\28666xi\1011412\917789H\27528\USJ>M\1058131\186155_&\190081\990889\1012542\46367.unqC\EOTy\1047316\DC1i#}\1061110\147400`^\1043640Q\ESC.\RS)f\990553\146957\NUL^\NULi!\US\49231~\\\1039256t\1096218\9343\DC3\n\STX;E\v\ENQz3\1022880\&2\STXv\ETB5k\1023115\178850\ESCs\NUL\v\43185\&9Y\92218\SYNQ\1087318\ESC\\_7\1106053\1092199J\EM7\1014160v\ETX\45857\SOHG\1048548{\CANPE\1073228\150502\DC4\13687:N/\1099530\1083947\1057108\1032054V\DC3}\174207m>{\147256\&4\NULI\ENQy^$\n=)S; R`\139984@xtE\SI3" } @@ -217,7 +217,7 @@ testObject_CompletePasswordReset_user_10 = ) }, cpwrPassword = - PlainTextPassword + plainTextPassword8Unsafe "Pc=\70672\149108~ix\1049331\&4Bv=j\1032226Rj\1098224\ESC\RShm\STX\164276F\163967\161659\ENQ\169704\ETX\DLE/$,\vm\1044889\&0zS\35123\v\DC1\29224\STX\n\ETB\EOT\30015N`??\22180\FS\nh\1096330/m\RSe\SOhP!\f+Hv\NUL.k\"\9280\ETBQ\1106065\132462%\1073477\1025415D\SI8X_pI\DC3\1034791${c\166694\182232\&7\999085qZ\GS-\ENQvze@\183653\SOK\FS3\1039436\ni\ACKi\146750/!\DC3\1005298\bTV}\ESC\CANRlY8\SI\135485xo\53151\\\189401\f\1043586\135041H\NUL\53392qZ\a?G4\150393\1070479\147240m\988567WX\999319L3'\151394S\167031\25890[SlhR\153001'6\1024896g\1064214;t\NUL\v\ENQ\1016655'Wx\NUL|\141526\156733\DC1g\4560\1031693\v<\1079492\1047033n\DLE?Nn\ACKe\SO\37093F;\n\12097wG\24904\SI9\1051218\SYNpCq8\EOTO4| !\US\DC3\38278\EM\NAK,`'{\DEL4\182683x\1023061\NUL&~w4YtF]e\100748#\EM]\179800\DELK8X\STX\ACK\14007\141199J\FS\1082822\150384?\1066331\1093677\DEL%\184568N\t\STX\DC1\162868\1009718\170538\"\1074945\DLE\48612\ACK=\14180\1024850)\989131\SO\b\ENQ,\147667xI$\95985\139748\SOms\162643K\13701R\CAN\37575\&9\CAN\64928\"\rW2\1044418ql$\154464uw\1667x9u\166803\4595\ACK\50454\134067yT:7*\164509\1009873v7z>Y|\\\SI\\ _k\1091148\993199:\n\94939Y\SUB\ACKs4Y&Y]+5g\191416\1107829!\EOT\983816\f\1043066z\26151M\SI\1031208!;`8-\170696\1104902&nh&\46018\997768{`>o\992346dt-\NAK\994271\&6\1111475C\183919\ENQl\992654\185547WKb6\ACKfz\188460\DC2\US*t\t\"Uz{\1061948\ESC\4918\EOTSp\ESC+5~\EOTz\1057942\ACKu/!\r\24940\4677c\1060220\5898I\a\DELU3M1)l\998068P\ESC\17318:<{\ETXuVsp3X\1052216\SO;:\\`i8SL's\983995d\98727UnI" } @@ -237,7 +237,7 @@ testObject_CompletePasswordReset_user_11 = ) }, cpwrPassword = - PlainTextPassword + plainTextPassword8Unsafe "B!\1044316}up\NAK\25454p\1069230\1055497 \39886\1023178]\1113089\1061672\DC2M\131499.\FS\167721\1068712LEKN\1037828@0\SI_j!\v\986396<\ENQ\bpJ2\ETB\993035v}\DC2a\190249\GS\DLE\49896\&8W\1103712\1044961\DC2\1106639E%\1112338\STX>\DC1]f\SUBs\133341\168122\1013776\SOH\1061693t.>\14333\&4>\DC49\b]f@k\1034383\EM:\1009024D\1109992\62676\STX\SI\1009774\SUB\a\999523\STX\1038777ua\r;I\129579\1063770\159019)>sa1-\991900R}\1094986^\4306\1095705\f\DC4t\SUBJ\1077394W^\32631a wajy\59881\1077144%Scn\162336\&9\992548\137492b\ESCG}y\1109761xg\NUL$\1088333w)\n9" } @@ -257,7 +257,7 @@ testObject_CompletePasswordReset_user_12 = ) }, cpwrPassword = - PlainTextPassword + plainTextPassword8Unsafe "\DC18\33713\DLE\DC1\43126~KSy0\1098569\US\152372lR\1105208+!\CAN\CAN[kv\SYN<\CAN\1050129kW\1001115\163706+\21051i[\30317e2[u#\CAN " } @@ -277,7 +277,7 @@ testObject_CompletePasswordReset_user_13 = ) }, cpwrPassword = - PlainTextPassword + plainTextPassword8Unsafe "NQi\1045721\&6\DC4(\DEL\14931-I\190282\DLE\DC1sA'\24027\t\USE6\\\DC2W\988244\DC2On\1101116m\92280g\999706k\SO\f'5\DC4%5\NUL}_K8\1022950\&1\14979B\171603\&5\1110874=/)RYG\\\ESC\1007353xd\983066\NUL1R\1036419C!B|1t\96635r\1025569\26267\EOTt\187049Xz\FSI\176746f\1055892\&7N2^ d:\24875\ESClo\189539OPgZ\a\DC2\1041937.x\SYN/\1007898y(\182083\EMBo3\SUB\v\1090249\t\1009316\1033672K\1073036{\157343Q(\74073M\99865\1061851;`rO\f\160403=q%Y\1059816\99237\SUB\141333 UM\f\167405\SOHD7,\vGnW\FS3>\v+\EOTzuQ{JtF\FS|\59814\51903+\1104795{\74101\&3\996441XfBgA\ESC\RS[\DC3#\1106531\1050615i\1088818\DC3*\n<3!\159426\SO(\US\DC1BVHm\175909\1111420\bg\1015312\DC4H!X\141513q\FS\USW\ar_#\183360\994422\SUB\US\\\1046753\95486\1039031\&7\DC4\15528\1100926H\1054195\DC4\180039\n7oEg\US\STX\ETX\"\63699|J\2046\1102234n\1031809&d\992846\165220CK\DEL\CAN?\96973\149920\DLE\DLE\ACK\NULH\EMw\f?\EM\tv^\ETXX\ESC\RS\95863\GS\SO\USB7lFo\STX\1003936\17648 \SYN\1100822L\42069+J\38082Ov\1019920\&6#9@\RS\b'dAKT'tzk\1026514\n\DC23\1091053YI\31165\"\1076177xY\DC3\1058287\41939\SUB}9nyZv\1110098\a\DC26r\1058863\100994:\DC2'\1042394\156114 \148989\&9&\NAKo-Y,[\997856_\1063117\ETBl\1088585\GS\20748\f \EMv}\168751.\1006090\ETBhgE\DC4c\NUL]H\f1\bW\1054531\SOH\1043667QnV\1015191\RS5\RS\137333\&7I\1087977a\NULSF,W\35909\&7~rO\1026587]\1086656?c\96846WlpOk\nok\\\1109331DC7\ETX\f@\NAKJ8\1045622\ETBe(A\1048081\154989.\b\1085843oJ\1092588c\ETX0p\1089200\127373&Hp_w\ACK\t\NAK&T9`\1035767s\33105\DLE_\158024v\DEL\ACKh\ACKF\DC3]\a\NAKH;lMLOqz\999079\1110425t@\996364aK]*\ETB1\EOT\53313\&7b\SUB\172040\40553E\22644pHt\1097939\132992\62032A\40631\986330\SI\EM\DC2X\983548P%o?\1102418\STX\RSx)r\1011553t\169304A\"?8\20267\SUB\186353^\NAK\170078\ETX10kV\1027480\78329\&55\120818\29728\GSC&S\1091150g1\ENQku2ytc\STX\SYN&;\rU\1070548'\996879+OX9_\EOT5fp\NAK\26556\183502!9s's\DC4\asP\t|\v\DLET\986832V\1102330,\987178\&6\140342P\RS\SYN\US\ESC\1042147\ESC\SOH+8<;/+\ACKBq\140372\1050808Y\1009276\48761n\4903\&8\SIF\a\US(}'\"/\28995Y?\163563}n\45558=4\SOC-\1043773\SOH\\/eI'\1050268u.5\170099Y\ETBY('\20830\DLEz7b\990753\63064\&0\172778\1093593sNkD\1114055\SO\1026850|-\nA\"Em&Sj^\178515}\ENQ\128693\SOEq\1087449\149931ES65.E\a\1053988\EOT{\SYN(\2032\EM\ENQ3\"qZ}2\ETBi'(\990398Dur c-\\c\12880M9mY\ETXL*eAuQ\26703\&5en\985099u\183413\GS7\99705w\1100252+\f\SOYd\\\137213\94577}\EM$W\49026\1009188>7" } @@ -297,7 +297,7 @@ testObject_CompletePasswordReset_user_14 = ) }, cpwrPassword = - PlainTextPassword + plainTextPassword8Unsafe "A\1081942\&9YM4fO\1090110s\1094466\ENQ\DC2\b1\179725\NUL#\1087859\183740\1019632f\1041374\6025,z$h#\f\ETX\2283\US5\996429\35782.\171631 \1068265\tiW\SUB\187596V\SUB5\ESC\SUB\SOH\1080402\DC3\GSf-J\138208}0\DC2(&R*}\157122\&6\EM\DC2$\DC2e\ETB\172943@ \986769M5N\29421\1073651O\13445\SI:]\18243\ETX\1031884\46115*\1016709qG\64812<&\1112925\32634\ACK\1066179\ESC\DEL{\r\1007409l\992191\\\a\EM\t.\166498\1048072\ACK{4g\ENQ\ETB\ENQ\120078\CAN)a{=J#XSJ\t\25101/\fu\1013342v2YZY-k$ #4\155005^\\\1106614\DC3>b/5-Z}\142114\r>\1032648\ETX\b\FSRS9\vE\DEL&\CAN\SIz#m5z\ACK\DC2pe\162939\&0\170848\54557Q\144858f,sH\NAK\DC3@\990148\998664&l\1028462\NAK\ENQ.f$s\f\a\NUL\36236F\"\GS\DC1\ENQH\r\143281j\986076?\r\118847\95137lf\1111505\1083975s'\1022814\1092252\187157t-\ash\US\1048347w\1010070\ESCj\136733L\1070015~H\ETB-\128398\EMvml\1013389Eug\SO\vDTR\ENQTO\SYN\1013663|\ENQ\1101450\SOH\\\EOT0Q*\165251Ly4\1005992It\24685w" } @@ -317,7 +317,7 @@ testObject_CompletePasswordReset_user_15 = ) }, cpwrPassword = - PlainTextPassword + plainTextPassword8Unsafe "\SO\16795@?\1034319\DC3\tMt\GS\FSD!Y\159204\1084245\&9\DC3\78687\1098287\1086842 _P\1008697/3\177267bZ7\38004.u\SOj\13467[\74634\DC4\147112%7Y\993096\994803)\64704h\159809\fYt\166228\1036104#%y\ETXO\NUL\50715Qo%E]\183018\&6\52933)\CAN\SYN,\1059969\1017948\1084728\1030365\1107776>\30767\ACKdSfz\FSX\1011690TU\8707jM\161765S\127326\DC4qE\FSJ\SYN\NUL\61212\1055205\SUBFg9\984629x\1038351\1111966\SI\SOH9\DC4\1099666\1049910\21409\1039222aSIN1s\t\128815\FS y\b\STX+\\G\180523\10901\&9\55148 M#|=Bhd\SI\RSt{a\STXAz.\179095\14700\"\1112100\r\65533I[\aT\ETB`\12556~3\1087071\SIn\994851}c;\1050618\&9\SI\985187/n\1048500\&5-\ACKZ=\1030734\&35\146351]B\b\FS7\ENQ\DLEGl'y\NAKb|4\989606=\DLEg*y\1056993\983575\&3Ej\RS\1112431\1088239:\167240\1104231p\ENQV\SYN\92656\SUB\aDBA\CAN\1056225\166252\182312i_x\138250#\vh5\1085011d\1105586\190225`\190602\DC1h\EMK\nW\a\41610\60481\10408Y\SO#3`\US\CANl\1060958\SUB[pr\f\USX)\CAN\SUB\1102505\CAN3NrV3\r}\r\164454\n\DC2h\\\1049410\1034744=\49232%*^|vp\21661ix\DC1\ESCi\168078\&78\a\78281 g\1022135`\t?\1019502'W=\1084341\RSj0\SYNk`Y\ACK\SOH\SI4!\187300\177532\&90\1017023\188333\18019FD!\1075920&\158315)F\ETB&\144361\a\58759\51196\aK].^9~\DC2J6\1013077\&7\154880}\n]W\177439\&0e\ENQL\157053a\"\17874\41525;\EM8\41213]J,m>\b\v^\1020018j60G\997360\&1fMGY;[<2q\1017963\1103307\1016618O\ACK\DC4. +Q\146747L\"\NULQQ\1081550pfK\156025.i\1110373\ACK\SYN\144039g4Z\ESC\1093918yh\ETB \1105457\44791\DC2,\v}\SI\NAKoSV\184902lUZ?Fi^H\99751\RS\1107891oR\\\SI'b\DC3a\STX\SOT\US\184371Xm\EOT\1021048{Y\SIQDO1OZ@\1096649\1098472)\1059966V\\F\DLE\1005888\1064602{0D\bj\DC2l-\ETX\1046901\1028919\1090408\31154O\EMa\1102121J\7867^\CAN?:\1041195\996983\1032028gz\146360\DC4\DEL\30263\STX\127304\1053834]N`\1113890\138696\SYNt2nqJ!p\vI\92251H\"5\DC1y7l\1059822EZ|kA\t\15546l\FS7t\n\SOH?t\1112409\EM.\191318zJ\989580\1044505WA{\1054069\1091947m\r\v4:\15073{\1077651\ENQ\44428\31089;i\38608\60141\148116\1084236\EM~\1030663\95306\n,y7D\EOT\9133\DLE\1050807\61817Zo\1113458MUD\147204\1073606\DC1|'Q\45980\n2\1000549\139927Nk\1023849@\SODia-0\tD\DC1\1035971\1057269\a*L\1105895\FS\EM\1030689\n\ENQ9\1041588\RSF\GSK\134105\NUL\rA_\1107772{\\\"\83191\989807T@\94793\ESCV\64140\1013588l\DC2Ea\127285\1086507\ETBz7\1006547P`\17986\SI\83490S`u\1044811\n\1086255t\\wf\12624m\EM \1008362\1069874R\1065254\">[N\n\"\487IQ\RS2s\ETXJ6\FSU%\37676HnDm\STX@\EOTT \27323imm\SOk\NULJ?*\STX\DC2+hhIj$2\83196\169764\1106792\r.\1106472\144305\1099117+o\SUBg\1090938o\\\a,\60138\STX\\\162017\SUB\1018764$[\1070254Q\CAN\1034607#\DLE\SI\999162\1090043\149519\\\35872B\SYNs_\42449\&9\vZ\100569avv:\1069361\EM\66820rv\986074\1041564&\98108,'/)\1069739'6\1112666\&5)\CAN\44956\&1XG\1077492>&\996088\&0\t,N\SOH4H\1091985\SUBR\tNT^;\SIs;\27408 \986736#\165922|uKo\1038168\136622\SYN.\1057757af\1035602vu%\1036358adVJ\1035793r\159529\14971#\CAN\1033919z\1061723\GS_d\NAK\ETXQl+&fe&2\1009634hbj\1099212[b\SOHkXF\1027185QZ\a*(\fOqN3f\ESC" } @@ -336,7 +336,7 @@ testObject_CompletePasswordReset_user_16 = ) }, cpwrPassword = - PlainTextPassword + plainTextPassword8Unsafe "U\1013927\38932zjM\ACK\989369\SYN\988971\44411\26179\1099606[\EM\6469\DC3L\1007944C-Q\987858[%D3\SIjP\159117\ACK\1104449\STX.P/\EOTj\99688U\"#X\63255\144060a\FSp\1056477\CANZ\\F}Vy\3567\7303\1017941)#\EOTNJ+\SI\36214\EOT\ENQK\171246fge\65191\SIu^s\rsa\SOHG\EOT\1022250>4\1053106\ETBD\SI\1037295\1082556#\DEL!\1065845\170137qw\27053Je\DC1n1Vu\1042145\&4~$y\146120:Y\SOH\a_K\a\ESC\DC4G3\1069872}a\1051446\ENQ+\150221\t#v\CANP\DC1\ETB\1027017m\8155Ni\995066\ETX\CANC0\1025017\ENQ\1078805\SOo^\156275@'\1001329\1051836\DC3/\984914\&4\1112505\1033864\161819r\1033625u\1086130\1004026nUW\147017\14745f\984915\147167\&6L\1056254J\EOTp:\DLE1\62628\24434\1036737\&8\987062\1025412\ESCf\1058077SB\NUL7\5692|\b@`\164062CF\tOP\n\ESC!5\SUBQZ3\ETB&}\1074005\FSl\1060790\28300\1051838\1057041\92189\170949h\EM.!<\1105788\121176\8485#,^\FS\97089\r\US\1021202E<\SO\DC1\STXe\1047025\157530\ESC\1108461\127768\DEL\1033097\&7Z\RSg\988877^}o\1092235\SOH\b\ETXxi(#\SI\1048177]\983467\DC4\1012517`\173149i(\DEL4@ C\SOH?sT[\59535[o\30407C3\DC4*Vd\1009623\1080174\t\153905\987527)\11605T\1104034\&0\SOH\1032423\1098555\DC3{\29330\45613\buk\997238\59248\1074873\NAK\36212/Q\1034971\\s\n\ENQ\SUB\ETX(\1038812q\n\1065055\\M*,-\69410k\1061499\EOT$\n\1043848\DC3\"\rPf,e\1074584\NAKM^]\DLENtE)\EOT0\1039707\&9}>\SUB\1031821>hr#\fnYe \DC33\US4\142595\"Df\19173O\1069162/+F/Ps\RS-\SOHGxR\163425\DC1\USJS\SOHP\1037710\24155\1070416a0^>\99334q2\14262\1022764\148161.\DC1C\173470?L\157517dPU\1071711\138047UV\ETX\1095932S\1083194\153099\156263g@'/\180511Mp\NULU\1042080\&6\153840\129602Q\DC4q\1010576\&8\ACK1O)~\GS\10101^\1035814\"'\f\FS\SUB\ACKM4\1061452,QR\41977\50386\1101120\\,\"\68775\1010234\GS3\1041457q\188067X\15821\EM$RS[W\SO\53767%Z\1102182\985051P\NUL\27978\aR_T\985127D\rf\987775\SUB<\bB\1009783\1111321\137944\43362\1104166\CAN\1109603[\ENQG\1093769\SYN\DC4CH\34381\t\v0d\1087322/V\1084112\17394r\DLE\147854\r$\150273L\187452\DLET\1012169\991037\47347\&5bw-}/\168300kHB\25586n\147531#z\1046737\1010676T\1096702\SYN_\"\190994Q\182486@4r\1093724y\169135Z#\15462\EM\1089051\158494\1030245k\n\172022(\ESCL&N\32338\1058995H\1102900\EOT0'\153127\EOT@-\157405\SOHt&=e&\GS1\1043880L\r\1039332\10350\DLE\150593%\t\1067637\&4\986137#Po%R\160955" } @@ -355,7 +355,7 @@ testObject_CompletePasswordReset_user_17 = ) }, cpwrPassword = - PlainTextPassword + plainTextPassword8Unsafe "\NAK\ETX\NAK\RSVB-^K[QI!h\154998\71117\1084382Z\35906,\r\73897>\FSMI\121373\DEL\1041332\13065\&44\SIiw\NAK7(\vu\191279oy\DC2\NAK,GLefv}q2Zn@jp\SUBRucnj\50153\&1\180385BC`\EM\1024436\DC1dA\aw\36359\\)yrkSp\1052631\19089\1083696d\ACKb\1104238V'\b{\ESC\v5\186684\SOHLp\1061917\STXb\98718\&69\ao\v\1038378\19929\&7\14137\DC1\186147\ETB9qT\NUL\NAK\USN{=\63014w\987855P\1069375>\1082182\1058366U\DC1L\78567\SIqV\ENQ<\EOT\GS?\1071802X\1081478rtX$F!R\DLE\STX\142620\9784H\65618*\DC2\24357Z!Sq%3\34833\"r\1107644W\DELCc\8262P]\168433i\1047019e\SYN\1048476L\49468\1075404\&27V\30820\94221L\1066619P\44283m\992353\167937S\1062257s!\GS\1012749Sa\164423(\1109764\US\1057958\999861\ACK1\v\ACK\985311\1098842\1029412\r5N53\SYNk\60720\187189=<1\SYNH\94843M\5786\35376E\148966QSy*fwg]Q]Y\"s\1023439\NAK\1112462!\145645\NUL\ETBn\1103776\US\STX!g\46993[\32717\3350\1109732_\1095512\ENQ\FS\v$\nth\177004\FS\"y\SOH\1070850\ESC:6ZfF.Fs\CAN\178064jnSm\tO9\ETBP@;\rO\RSM\DLE\1029277\NAK\1011137\120416\49923\1038900&5\97187\1060750\181666\ESC\1052105\DC4k0\160161\DC4OcMAZy\1065334\&5\FS}t8rUzI\34999\SIIL\n|q|\\W\DC4c\1102652=\141891S(\121160K\1021413O\41185\"7\13500Axp\aF\CAN$H6E\DC2{\178358\DC3\145547\NAK\25538\1029182'\2102\DC2j\140870W?&\DEL\DC4nF^s\988659\1069309\141794\SUB\SOsV\12922\NAK7\187903{\RS\41369\ESC\41141W\74457\SO\EOTg\NULn\SOH}\RS\ETXT\1101298\a\f\157153ne\111107NP" } @@ -370,7 +370,7 @@ testObject_CompletePasswordReset_user_18 = { fromPasswordResetCode = fromRight undefined (validate "gBlCfS7vL5ZlXMN2EWV5eSisvsqKezrNgWoI05VsTNJTtsB") }, cpwrPassword = - PlainTextPassword + plainTextPassword8Unsafe "\US6f\1111789/l\\L\ETX\57836\ESC\SO\96423\995453HW?-v\nqn\163962n\ACK@\ESCS5z\ETXi\EM\156843P\1043153\SOHEn\1108441}\166673\95024z\1011478Fx\SYNt`\vt\1036893p\SYN@\1047041'\ESC\4911cIWs}0Bgj\1090394\DC1\162989\181264\SI\1097419\ETX\1110190mK]\160292&Md\1061797\CAN\1008874\r\173984y\CAN\1035416\39608|^\144442\157286\1084325}V\ACK\989501[w\SOH[C" } @@ -389,7 +389,7 @@ testObject_CompletePasswordReset_user_19 = ) }, cpwrPassword = - PlainTextPassword + plainTextPassword8Unsafe "Nf%\26857`C$1\ETB\1016123\1013601#:\1093135\1993\DC4_D> q\EM<7\1107146{\1061674\97572~Z\STX\a #\aW\53248'&\SYN\CANs\DC4=\RSa\1085316\989974gA\1079584\ETX!C\a\990713(4PN?@\1081857^#\n\STXbrdyr\1106974\993303\v4\1097791!#\1052766\SOHdK\NUL\72402^Si\SOH\146114'nw]M\ETB\1012025i#*W\f\DC4\1097711\1091349f\1034270\EOT\364\SO\ENQ.\"\SYN+\DEL\1087866\144785\ACKCT\DELe\146451\r9\184888ifa\1113864\17863k^\DEL-^|I4f\169485\985795E\DC3\987554t0h\STX0\1015621\a\ETBp\63117(}\NAKtB\1060605+\SO\135099\1044767\&9\1000776\&5S\57916\37617qQV\147525?\54219Zrt\DC21\EM\b\GS\ACK?\DC3\156312\1092629\69667Q\31050y\179088y\139970S]I}\NAKi\991482\NULh\SYNR\28464[(\SOHHxq\1036471\&7\SUB\1094145\1022472V/.(]\991630R\fa\USJ\998815\ACK7~\DEL7TT\SYNRo\DC2\30121\RSrm\1027017\151151ry\GS\GS\51765e\DC4/`N\NAK\DEL$k\nx\DC3U\ESC\166495Q=\1019375\r\ENQ\1031360\1109306\188394Z\53745gp\42504\t\bI:'k\983534|Nu\1075753\&6s$#%p\17281cB\1003026M9S\173530\4950\100374\tv)\69736M\66455Xwug\96822\&7e\"Nj\DC4\1023797\f)\995080\STX\DC1>Xi%\1019327\168371b!\SYNXq\RS6\SYN)\DC1\SOH\SUBq\132317,8\RS[\74835R[l\1103025\CANE\46590-Ccw /\r\133918=\996631W\DEL\1106772\1096813\98986\t\1038602m\1087137\1111197g\172456.\CAN?$%Y5$\b\DEL\10731W\1032611\30462H|\173272\US\SIUq\1071566\ACK\1076198\987790\1045529\SI\STXSs%8Yo\FS\27056Uw%\143377R/'\NAK\1017747\31651~A@\NAK]M\t\1043105k\rvR%N< %hWk7\SO\t\1022082\1007073\&2HDZ5\SYND\v\"3\1083081!L\1028467u\1044115,\STX%\1105946$iN(\f\187862\147976\988324dAA\b\EM<\t%F\41597S\989085\\\1098403\110714ax]\35131\998139\54320\988848@*\1036145?\1040527S$\DC4\146906\96397\b:a<0\SYN\SOH\rd/z-\DC2\128549\fG\152978O?H\140755\\\4798i^\127894\12415\1041013\36494;\52340\DC4\rI\163748\1098064\1074828\a(DwA\ryq@\1029109\1077261\DC1\DC3\DLE\67107sn4r#\120414E\1113581;\SUB\NULDq856\1014066\171865\ETX\1065435%TP\1086344*;S\SI{>\a#\177106\EOTZc\SYNF7\1009077a\20098Y?\STXp\135345!p\26071.\DLE\ENQ\t\\\1036223a\1040873+\163305\n65\ESCNb8\1020528sc\rQ\DC1\NAK\1107289B%=vP\DC19\190607\&8$WC!\190590\b\1075330i.V[\47896W\186257I\1091816\92618D{\1104672OBm~xx.aVlU\CAN\"Ou\USoA\r|OD\SUB^pB\166696Q,H\ENQ\63043\DC4\1003911\42643J\DC3\NUL'bVi\SUBb&WS\1106725\rqm(\187473\&4;$N\1001886\STXA\62480U\DC3t\CAN\SIJ\\~_\DC1+hq;\180132C\DC1\US&\1075833s\ETB\GS\145589\ENQ5v\169060\&1\59025\&2s+8\DC4\996138\167208\at\50528j\STX\143681\189398U\1009286" } @@ -408,6 +408,6 @@ testObject_CompletePasswordReset_user_20 = ) }, cpwrPassword = - PlainTextPassword + plainTextPassword8Unsafe "%:F]T\ENQ\vt`c/$x\f\r\110962<\167682\DC2%3gF1!&DM[Alw\8366M :\tg\SI +3\\\35195r\186137A'\1042721\aU8\1016516\78238c\NUL\DC1[&w\158228\1097827\&6\ETBU\SO\1035337_\\\58202\DC4?\1112500\1082086q3\1107715Z\1025233\&20\ENQ\158606fkc\STX,FT\37265F0m_8o~!\SOH\1095300`A\169824!\180135\21290Kh4;f;.\1061263*q\1103264\1071770\1079751\176712\993711_Ay]\1038017I\RS\183781b4\160789Vt\63109\a;!\f\1078906\1021229Z^,r\SUBC\DC4\73459\&1\161616\1017807 aS=ak\SYN1\SUB\1019433\1071476\bH\25304]\176465]%\1098929\23086;\1000067\&01\EOT\DEL\1070921X\148186\71226\RS\1045965\1095376\1107687.+e\DC1\1092989MeAY\1031197YvK\DLEi\r\1067660\RS@f[>>\\m;T_\DC3u\157897\1007461\30666T\1060491\FS+\1019481\1111804\1017079\&9ta6k\1014740\&7&\1019716RD81g(L\52575\1105513^k\US\1087923C%\71252\13479T-(\EOT\r:V\182162\99976\&1\173091\167405\rJ\1058506T7NM6D\1093179=+\DLEU\144210\SYNv\NAKf\SYNz\64962\917993[64=b\EM\DC2\110592\1037416>C\EOT\135323S*\39272p\61918/\DC2\CANm#\DEL\1019756\n_~*P\46796D~\185934\1061284\RS\EM\SYNh\1058927<\57662^\1076167V:yL\DC1\983936\54304\95217Me\DC1\173637\28857}\23233SEyGN~eC\41589\1008496\DC1{)\48719^NC\1041531\1010402\152999\ESC}\1050714\104431550h*\DC1\SI{\SI\SUB]\1071376>|\ESC_$[\181480\EOTbN3j\RS\171025\ETB\145187gP\1065781\173942\1009175\99521\1027637\1025619\59790.z\185081\DC2\t\157620fB\\\CAN\DC3|\DC4G\39064\&6\ACK\b\DLEh\DEL\124937\NAK\74416Gw,+5\v+k\99425O\1004788F&T\100343\ETX>GTX#\120481\1109419?BS\29595lQ\169334\35479\1049632=\RS\SO\996776\55138^\r0n\NAK\146675\128353\1038461\STXEq\177483\993226]h\DC3B\SUB!\990150\29868N\987137\1026589\&2.\179310\6355\&2u\DC1$\1057066H\1077906ua)\1043473v\RSy\EOT7KC\ENQ\1024303\&8kuY\991388\1075309:F\169126E2\ACKZY\186798\1061629J\149940s\US\1101984\&7G6\ESC\vf\DC1\100667\1062487\99642\ETB\bE\1074206\47318\52993wl'\92404\168932\993434gl\989551!U);v\ETXI\aK\USa\NUL\987059F\DLE\1096453Z\DC2%\994420u\1107348{Ah\ACK\1113953\b\SO\DLEz\DC3\ENQ]\SUB\164854Ft`\SUB&\1037715bw\330VSO\ACKz\EOT@\\`\147544\4111r%\"%;\1056968\&7\rs?\1098051g\1098269P\SOHt\NUL$\186214\1071744\1022584\1013974c\ENQo,[\1055940\SOH\172608rc.\147909\SYNg\NUL<\NUL1hO\ETXhhN\1007263P\DC1\SI\SYNY\98489mBIk\SUB\n\1080085\135468\DC4U\fu(La\DELkUt\3227\128054t\b'\1111124{=8\DC4\9850jzt\ESC\134303\STXQ\DC1 7\RS\175534;\1108063\NAKe\1014967\36268\SOH2.F\33192:\1035967\1453\152125\DC3\ENQ8EC|\173778xy\RS/~P\140732[\1087310\RS\1018753+0j7z\184026\USfr\f\CANSm\SO\1067099\1014662U\DLE\NAK\164724^+*\\k\v\US'<2\1022414\ESC\14889\1065\158191\188417)\GS~\46128Nr\fu\145379\168360\GSl\ACK\DELcUjm X\EOTM\\3j\na-Y" } diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/DeleteProvider_provider.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/DeleteProvider_provider.hs index 195112d542..57dc0249e0 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/DeleteProvider_provider.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/DeleteProvider_provider.hs @@ -17,14 +17,14 @@ module Test.Wire.API.Golden.Generated.DeleteProvider_provider where -import Data.Misc (PlainTextPassword (PlainTextPassword)) +import Data.Misc (plainTextPassword6Unsafe) import Wire.API.Provider (DeleteProvider (..)) testObject_DeleteProvider_provider_1 :: DeleteProvider testObject_DeleteProvider_provider_1 = DeleteProvider { deleteProviderPassword = - PlainTextPassword + plainTextPassword6Unsafe "\DC1@\DC1>'\1102947\995024\1043045'\NULUr,\"\1053599\154586\\\983184\1005060'\CAN*\r\119092Jx\158738(\1034236\1111068\GS`\RS\1023229$#\181515\186716\t=\16360z9\\,v\SYN9\1081993\v\1003036>\a\10046\&42Y\NAKS\176274|\54053x=a9k\SYNepY\44071N\1020919\30201\DEL/\EOT\1024289\&1\158768`P\140:Zgr\EM\17418?\141760>[\DEL\4472\27674j\141802N\183910\SUBl\170710{\171194\156957v\50468\\yDx\27333x\1070509\&8O\10189\DC3\ESC\SIwn]\1002158:\NUL'\NUL5\\(\bi\83316F\DEL\1107124X\SI\SUBi\990574I[Z\1028861\CAN1\1105411P@S\SYNI\179180%\NUL1#8}\\;}])\USh_N9\1079200d\803m\SO_\1072463B\SUB\NULX\tc\NULR\ACKw|3_xn\\\1020350\11339m\1017300Q&}\DLEk&+\46848M\191189\1077146h\DC4,\DC19|FB}\97649\f\1002295\996162.\DEL\ETXAL\52088/\ETBX\b;<\GSX\19235\SI\boB\185334\FS[!\166871P\1029617?R|\EOT\1090605\EM\NUL}\144677\GSk\v9\ETBGL\23477-\25258B\CAN\189512\&0\ETB\131990\1014508\a}ns\\~[\167960\&4D\DEL4V\SI*\ENQk8gBN\SI \18554i\990272\149977\1008889o\1091527[(I\165811:\SIc@]V>>\SOH*\DC39h?\92463U\FS\SI\rR\989411o\54904']s\1021357\1095599\1037065\28114\b\160993y\996966\165520n)\ENQ\998845~\1070129\1071107bA\fc<\5532\33882#pUG\GS/\CAN\155833\52960" } @@ -32,7 +32,7 @@ testObject_DeleteProvider_provider_2 :: DeleteProvider testObject_DeleteProvider_provider_2 = DeleteProvider { deleteProviderPassword = - PlainTextPassword + plainTextPassword6Unsafe "\984525\1043960OXS9n+u\v3\SUB9\189261)\NUL\GSd.\DELxf.3H1mc\CAN\SI@i8>7ob\1047764\ry\SO\147692J`U{\93847j@)\179667\"{\1053310,\188059s\1045477\n@\f#\15153\ETBE4?7\31129)\vh\1005526\b'\132397~-{CwZ*\1037033\190558\ETBeJ%\DC4\1023444\vr\160978\34762|A[!U\SUB\128583\nv\5189\CAN}r_\57489\NAK\ETXk.\RS\137427>\1054128,\33000[w\SYN\v\176083SHH\DC3i\119359\ACKa\v*\SOHo)P3nB\ETBVI\EOT@3;5pl\SYN\DC1\NULl\1113634g\CAN\DC3G\1057089\161998\&4\nn\97904\1043436\vk\EM\SYNV-\\ 4Lu+\173185C:fd'\32161]\SOHDW\NUL^9\NUL\ENQM4r\ETX\27301u\1041008O\162963`2+4l\1101064\1077396\1082420Z31&OE\EM\1087511\&3\1034203R\EOT\1000625h\FS\1045021\1105250kV\190547|PP\1037249+hYd\SO=\t_\STX\50344\1035551,\r|\7757)\1000692 \EOT)Q9S\1048123rH^\ETX\26197m\ETX\120622\SYN`\DC4\159476L(\1032667\DC3nGeQ\1340\ACK\1035560\67715ha\SI\RS\DC3Ph8#R\7873S#Q\EOT#/\1037058Crw\152923OK\SYN\SIpT{6f\1037408j\\Yb\168691\&6/v\STX\SOH\1001257\CAN7P\1090994\1003374\65195\96273\1002856O\ETXZ&\998950A\SYNZ\1079101@We\SOHyeD\1047023]\a@\1052273F\ESC\SO\995299\NUL?#K\DC4\"\5054&_[:TEq?w\181853\DEL\153239\DC1\"\SYNrd\145008y\DC3c\1042973\1035111{\CAN\ETB;>?\996728\SO\"\RS\167515\63621\SUB2\DC3n-\1093919z\EOTGD-S6b\24090\1006310:0[#Bb\DC2\1089765\NAKzF\ae\rC\SUBx<\987227K\ETX\\\SO=\1087219,943m \183245\190748-\n=\DC1\NAK\142118%[YX\64073?\SO~2R~h\b\bX\93045e-7?\1398D.OLru}L\131183\t8\8708\25653)\994590\1065269Z~l\DC2\49866\162561\nZ>[mZ\ESC\CAN7,\SOH\NUL*\169477\&7|\1095835[l\171154\SOH\SYN\SIF|f\EOT\"\45145W\988489wtC\131467\42228],K\EM=d\162347\\\998674e7u=\1087050;\SO&\DELlIT)K2\30282\29522U0W8\143297\&5v\159176\&7Pb\49357\1086563{ \r]\1035947\SOt\USv\1111251\GSMk%\STX\r\DEL;zw\1045585k\a6T\DC1I\rk\4589lQ\v\1087379U^U(f%\27173W\990453\FSF63V\ENQQ\1039545\&9\ETX\1027888K\CAN;?N\5575#p=\1103580m\1021118\135827\NAKq.p\1026570k;.\SOHgF\1065081\1100911g6\STXR\1085500\168383a\a\SYNZ\NUL\1043812A\97854\&7[w\1060519\34962l\DELhR\EOT\36730\72839\DC1(l\aav3q{*P\1083959\60318\ETXRbJO<\173398\1066886\&6\14855\aLs\DC3\NAK7]\STX\32065\n\45961\DEL\65810\&5\1077711<\993297\SO-c\47037l\SOH+\r*`0O\ETX\1065442_3\DC4$I[\SO^\1093967h'^Ir:\DC1\997590=W\US\NULW\987020-fR\NAKk}hxB\NUL" } @@ -40,7 +40,7 @@ testObject_DeleteProvider_provider_3 :: DeleteProvider testObject_DeleteProvider_provider_3 = DeleteProvider { deleteProviderPassword = - PlainTextPassword + plainTextPassword6Unsafe "+\DC4M\1079446\bN\1092976\7721WC\1046938W\ACK2\NUL5\1054739\v\179949{Q\1070052ZnyA-\GS# ;;\1058412" } @@ -48,7 +48,7 @@ testObject_DeleteProvider_provider_4 :: DeleteProvider testObject_DeleteProvider_provider_4 = DeleteProvider { deleteProviderPassword = - PlainTextPassword + plainTextPassword6Unsafe "{k\1032127j89m\1028940!.,\1011366\989738Tg \GSG\1062577i\179248X\DC3\17357~\35308\1080081KB\ETB3Z\120005\SO\27779ytV&UZ\USjaO/\1014772\ACK~\ENQ{\186280\v\170867\129409G\1056004\1000425)\158906.\n>\1081241:@\152049\ETXa\ESCMW|%\USh\1029384x_{\n.>\DLE6R@d\tCW\140369\SOH_\DC4\78572!'g\SYN\1044903!RS\f\NAK\6544\STX(\36031:F|=\DC3\GStg\172165&\SYN,r_\1025883O\DELT\27292Y6HquP`\ENQQ\SOH\SOD)=\ESCs\29741\SI?\1073240BO%[\160823!P\26163\1014284Rp\RS!Xe$x\1110728\144032S\988049\990894\18495\GST\1105691\49284\174626\917911}o\57602Zl" } @@ -64,7 +64,7 @@ testObject_DeleteProvider_provider_6 :: DeleteProvider testObject_DeleteProvider_provider_6 = DeleteProvider { deleteProviderPassword = - PlainTextPassword + plainTextPassword6Unsafe ",\fT\n\11037\150521\1112935\&6\24772\STX2\142196Rjw\rX!Us\1077738f\ENQ%MA\1089631\140112O\41617fs\23343u^\31774\5501k9\\g4&CyT\1081061.\1102377\119305\1039223\FS\NAKVQX={\f9\\\1075870$w(w=\DC2\ETB\988694\t\135307\rX0\74402]\1098792G\1070616Z\ENQ[M1S\143908\996439\v\167213`nwh^\12408^\141240,\55182VM\NULW\158742J\1034824\1042748\\\a=6\23807uF\8041j\160911\ACK\DC3\1061055c=\SO\v\49989C[']<\ETX\GSFm\1095852\\J$jd\ACKw\28402}\CAN!^-\RS^\v`\145626\&9E{\139362>\STX1n\139165V\1015510fN\RS\49877x\382m73\1052448\NAK4\171724wtm\94543f-V\52768\RS\61464\1024899(Ey\187608~\DLE\126606\EOT\988290\1094131<(\1102453\15963M\987791\&1L*\178238\986389P\1110638ew\SUB*;\1020562\156439\1047006b\1096691\&2\ETX,3_\ETBg\184769w\ETX-\7809FwX\1008433)&\144525^+ =n}S(\SI\1018943\tM{Q\GS3z\1105280s\133855\GS\EOTFs3\70669)\6660\amom:R\8110\DC3\35666O;c\DC4qM\168664f#;E\US:(\33568(\STX\1010570FN\ETX)\1053483\nIOuG\ACK\1016055xoA\FS`m\SOHs]:\1025351^5\EM\ETB\DEL\159157z\1021102~\b\48196\135249\STX\1092689oj\ETB$\133413$MK\1069196n\n\8575G\RS\1024958E%\1082021iLRpbg\DLE6\120928\20506=\GS.\151888?qW\1102074&0\f)\SO^V_-Utb\52528\31472]RT\145602|\SUB%l\NAK\ETX\1067340P\CAN\EM >\EM\1106918\179190M#3\SIQ\NUL\1018740s\60067i- \187088\DC1HT\95859T\1104463/\DC3\STX\US\1001037M\r\157436\16960\993091\1085868\1022183V\ETB4\SIB\1085605(=BQw$\SUB\1067953\139343Z\CAN0\DC1\127556B7<(mb\DC3h\96162\55002\DC2z\b\145908QaQV\175250\151327\n\985159&\n\SO\1018855Q\STXj\v3\RS=_, \SI\EMQu\EOT\SIY\DC1\RS{\EM\1065344\SYNW\144838\149272X\1081686j\99102yg\ENQw\14545Y4g%\DLE\1023519z\1057216>_\1027401\SI\1077238W\SI\1092706\SUB/E/\b#*\DC4(\")I\176856\1001466\&4>3\37037O\DC2\GS\94771x\111120\1033261\DC3\SOH\1052866T\50307A8N+`:f\t\EM\985835<\1064255EL\160636\&3\14480\127849\r`\ACKy\RS\f\151666\165718QdS'Rw\179457>S\160451\1078575\n\\L\150549}\DEL\134027\1065618qj=\1059318\173025Qz\22020\188172\SUB9yf\EMc\188027le\128231\DLE=G,k\t\ETX\EOT\1133\DC4^'\r\CANS\39323;f\a\1037800w\vjV>\EM\DC3\1091368W$rc\1089926\172604\SO\ENQ\1074216NE\NUL\SYN\6921\169203Bz,[/2W\988557\ACK\989352\178741~s=\1072691\fA" } @@ -72,7 +72,7 @@ testObject_DeleteProvider_provider_7 :: DeleteProvider testObject_DeleteProvider_provider_7 = DeleteProvider { deleteProviderPassword = - PlainTextPassword + plainTextPassword6Unsafe " \624M\23418J\FS\1054202Lej\ACK\22365\41654\46673\95301^5\DC2\984400TJ\984916\&9R\DC4\1050813,,$E[\SO0\ETXsb\t'\t0>U\1089416\SIr33f\137027\143327fQ\ENQQ\b\ESC@51u*!\ETB\1049788\DELw\46535>]L\36134\1039185\163894L\EM2i\\m\1091968\&4c%\SYN\9242\DC4i>\120056R\164597\r:z\DC3,`49s\44254\138481\1103072m\DC20I2\993122\137231\1077346[ge\DC3HS\DLEG\175034:#j\DC2X(*KSOs>\NAKrB\1058112!'\RS\DLE*.j\CAN\GSJpS\a/,\a\37202)\n\1027944s\1091946\RSe\1098612\1086816\SON\43847r\GS\1105154\1019251\EM|!\96749n\DC4k\1023808\1001051\1069320+\998856%\118987S\DC2U[\61135O\ENQ\1110955\NAK\1001308\170546\SO\EOTL\1111010d\ESCP\ACKYF\DC1\SOH\65867\28419\177258\SUB\1062425\985666\t\151420xI\1020274\175005\DC31\DELF\176479I\169549;\993159\NUL4:\1010330!\n}k\1080461AaoT\150654X(_\n\GS\ESCf\49821\98930>)\DC26\DC28\157333B\1060776\60653:\1023707}=3\NUL\995126\97810T-%fT#`,J,\NAK[\NULe]D\CAN\nw\DC2\120860\RSpW%\147220X\986014o{Jek\ACK9\182559?H\DC4\996934\t>^Sa\GSK\140398\SOQHX\141574+W3L\CAN|\1068172\\\ESCl\174698g\987261 P\n#3\f\1013264X\v\SI\tm-\RS\185875:'\1053231\990328%j\120308\SI_\1000210\&1/tiHCDZ\SOHZ\139905\1113168\&3m4Qq\FS\SOHkuS6Tx2b\EM\SO!\1023256XT\1101340/3V\1071327\&5\1086459.`C\CANwOA@3\1069157Wqx\aK\DC4\FS\1035400Zu`\129142\1079715m?f\1073033\GS\EOT}X\6820\&2:<.C\158741E\66192\&2.\1073847;2g\SUBd\SOX\181466P]\1103031V\99500\1597\ENQ\ESC\31838\RS\21975\CAN,g\SYNu\SOH=^\71324\155428u@\b\STX\aD\n\110745F\ETB@&\1016259Z\t\143067\EM_\DC1~m\155177\1055559y84c\"[;DU\141586N^~\1017673s\DC3I\95006\STXm\US@_\1083426H\93812Z\EMZ(\183277\ACK\1089255\133426J_NE-\154549\170008\FS~a\1058991\181123\EM.\176985=\176509\SYN\t\1019377p\100335Y\1027317}\1070781\DEL~\60615\DC4d\EMw\ETXnYWB\1103855\1007484R\EM\1081389'ZXzP\1001276\ACKB\ENQy9(}z\18379'1x\STXp?MM\ENQ\1033401\1095702u?\137539\45622;\65599\26469l:)w\145127\GS\ETX\1061851z\1080393_v{\151849\164975\8631X\EMI\24721Y\NUL\149184\STX\175455\a4\157755\&0e.r\999201%b6\190409%\SOH\SUB \1109498\984390\182849K^CERXA~#v\33651{\STX\1048885\26836\SOGbz\DC3\1056261K\1057379\&1)\186016\r\51132\&2\189152\NAK\996489Vr\aG\1026756ax\FSN\1004712\FSc\ACK\US3K6=\54958\tY_\1067945|\1084323\FS\SUB\1055497\DLE9<\1065761\DC2#\1013541\EM{Z'd\50849\33048\DC24\70681\178096-\1028926\NULC1d\SO\1047114\FSN\fB\157482\111105/\DLE\57509\&8h\f\1034246e\997247[\110814\176299\DC1l#\100151\996255\34863\173136\DEL\1090949\US\DC3\STX\DLE\1051161\r\SOHGR:\20017'2\US\13757\21938\ESC\1015967%\1111544\1013576VXq~a-2\ETX2f\DC2>.E\1082947tY@Y?uEX\1042210J-F`\62692LL2N\\O\ENQIE" } @@ -88,14 +88,14 @@ testObject_DeleteProvider_provider_9 :: DeleteProvider testObject_DeleteProvider_provider_9 = DeleteProvider { deleteProviderPassword = - PlainTextPassword "\DC1\1064292\SUB!8V\166314\SO\ACK\ETB\"5\64599j\189708e\DC47dNxU'\16357J" + plainTextPassword6Unsafe "\DC1\1064292\SUB!8V\166314\SO\ACK\ETB\"5\64599j\189708e\DC47dNxU'\16357J" } testObject_DeleteProvider_provider_10 :: DeleteProvider testObject_DeleteProvider_provider_10 = DeleteProvider { deleteProviderPassword = - PlainTextPassword + plainTextPassword6Unsafe "~\1040972jRa\SOH\n\SI0K\990428\133439\DC49\FS,|\SI6\1022952P6G5\ETX6\t\1075581:k\96553Q\SOH\35898\DC1j\172175}5fvp4\FSc7\1098045/J\68898\&46\1018453\CANZ\1065057\1090165\NAK\1072358\DLE\EOT\65446\26156/XU\1079115R$=\fa6f\b\1098528\&8?E\1062300OU\US\SUB\FSC\1113609\f\"\5545M\174668e\ENQ\DC1M\989929A~qq\STX\EOTY\ESCG\1052864>\ETX\50239\995133T\rB\bn\ESC\ACKL\146046\166566\171485Z\ve,\160056\1094193UZ\t\1002621\ENQd/\DC1\5473\63971[.%01w3\EMtZt\1004605\1099738v\t\187486)LM\RSf\ACK\140763\1014776\&9\SI@\1035887\DLE\FS\ESC0oQe?[\1035913\1111740\&3$_\189385\GSZ\NAK\38187i<\DC1\1072354\&3\1076164YF-\SI\9828\2130\a\"nP\1085224e?1\16223nIWsC\37896\ETX\1063189\1000812\RSax'\185030\ETX\147022O9\STXE\990294\EM\DC4t&;X\r\EOT%E9\bM\ETX\188586pfI\46096lm\179262\"B\1039905\SUB\38603<[ u\\\1058099\986546\988823\178253j|\DC1\f\EOTz\"\986225fs\1088266y\216%.\138588KU\b\NUL\1038063\&3S\163149\1081752W`\DC3U\185513\28773\61686A\1031101\\eM\\)OB]xep4z\142267ml<:0\1097056\ETBr\FS=f\f*3\SYN'M_\DLE\STX\SOH\161576}\985376R*\191128\55073M\NUL\SI\41927\ESC`\183647#q\vc@$Z\1071926\SYNQ*,[\187915\131597\1002663\1111830\t\151977o\GS{\ESC4\US[2gGs\DLEY)\152942E\1084490|\32920\&7\171342\1057558j\CAN\1095607~=\FSI\1075491\&03=\r\1004914\1010211\60429|\DC3\CAN\STX(}^\1065299w\FSJ\53542H\1098143\157584\SYN\49771\983932\DEL\1105851\"5~o\54484\121456r\154102:ov\189215F\1105398;6HkT\48486\99906ei\1102176\1035874;A\US^\DLE\166094\EM\DLEt5\39199\1000398*\987236\36310U\20442}\1071144\ETX\64424Os?\1040194.s1\1104551\n\EM\1083665\54846wuY_Q\SO1\\Y#\21726S\23739'\GS=\ENQ\v\ESC\ACK\164113\ENQtA/R;\158105\131797\CAN\172442\1064571\DEL]sq\SO<\b^\EM\DC3\ENQ\1003433{\SYN\26868\&3\47069ik^\GS\DC4P\SOH\1082140\EM\67980+:\1060625\1036803R:9\1100130\USi\r|]\178537\1089051\1019056=\1002762]Hd!,3\US\nt\160073R\2576i(og\1111195u\15456\SO\1040617V-\1071988\US\1022948\SOHL!\182613?IsQ\RS:l?I;*#\1046129_K[\1041748XA1a\1024880\986414X\18008aYf\1045309\1053346\1038859\"\142308\995622\b\NAK74\1008952\\eCp\30226\NUL\SO\143524}v$\1050141\18217\34711S;T\1005256" } @@ -111,7 +111,7 @@ testObject_DeleteProvider_provider_12 :: DeleteProvider testObject_DeleteProvider_provider_12 = DeleteProvider { deleteProviderPassword = - PlainTextPassword + plainTextPassword6Unsafe "\1007892\166298.1\35067q<\DELR\179909Iu\SYNWP\US\1042902.\120669V\7259\EM\1061036\EM\7817\EOTHh\190352jY;=SRhp\16588.dkc\169263>|X\NAK\1080314\&5O}\CAN\1043753c|~HBX|k(\1053055j\1108065J\f(W\1107955\ETB\166679CO@\GSX\1112489\1091049\\BCg!\rX\GS\987727\DEL\70816\t./\SYN\21028tkovvp\1107555s\GS\143204\1005238{\985690V9;" } @@ -119,7 +119,7 @@ testObject_DeleteProvider_provider_13 :: DeleteProvider testObject_DeleteProvider_provider_13 = DeleteProvider { deleteProviderPassword = - PlainTextPassword + plainTextPassword6Unsafe "\1082093s>O}(4\189851=h\127772\&7\1094821\STX\39698GR\1077438?\1008267,\176101M,/Z\EOTs~\100263zj\NAK\94052/_'-=3\f\\~8W\CAN\983872/\USm\129494\&1\FS\180953\&5\47911Z\n3?\1001530\n\1037936'6\1073619\144126m\SO\STX8\NAK/\1084245Z=\SI\1061631\1054559B\v3yU*\DC2\1025567@T\ENQ\FS\1018948\STXi\DC1w\157694c\1083418\NUL\119939:\167173\96905\US\139551\1099836\&3*\SI\FSb+Z\DC1K\1095387R\21991n.\6600=D@NX\NAK4_&\DC2\n,XM\678\USUw\EOTR\62649Mi)-\37650J=\ETX-\f$k\a%4\n\135411\1013583U\1061907>\15674z\52528nV>\SYNa*\139458\22648/!\145080K\1088356s\f\996120u\NAK\1101690\f\RS7\ETB\995731\46942oA\12088\62892IK(\a?\128017i7\1047542\DLE\1015992\DC4e\136695\1097623yyJ\1050424>]\178185\a\37397c\64716\SOz\DEL\1037853z_\vK52}B\EOTQ`'k\16203\1085598\1045965+\1113033\1108508v\1069069\CAN\1049355qsJ\ACK>y\NAK\33210\&6b\DC2b|HtPl\24067W^%\CAN\126568\v1\1076330Ts(Km\NAK(c\n30\CANe%\DC20\ACKAv\1105666\SYN\NAKn\US\51276\&4y[\997438qo+f\47599\990691\1063656\1040123_zn\NUL%Q^FZ\ENQ%\64647\7165\NUL\1040652\178170\GSs\1074533Q\DLE?-&= \ENQ\993080g\998803Y?\GSzb+\DLEN\DC4\STXDa3\1010949\r\STXwQ\1018740\FS]T\1079943\GS@P\FS\1085\1113380\&2\25177\v\DC4\92483`\NAK\DLE;\16890\175811?\DC1\1066402\SI\1086327E\DELm\152922.\172491W\999440\58559\189616*\1090337G~O\ETB\DC2Y\1025522J\f\163887\NULx\172426@\DC1A(\168166GC%\SI{t\DC4F\DEL\20907\DELWE\1073704\ETB_%m=\184124\f\47174A3\fI,\46251y\\\\Y-\v\92982A\1027677oY\ENQd\r/\r\NULF\991639&HHqr=Y\SUBX_\128625H6(qXk\b\1025750NR\RS\18562?,\1047164\1107498p`\r\1074813\US\1106686\16834\&4d/)_`\DC3\SUB\128275'QL\1024034k.\23619\1019113^w7\165946Qt\1083998\1108588i\157558}H(n\EOT\\\1057760\r\1048983\1065683\990896\b3b\v/e\1009653W\ajg\DC2\1033673Mu+g\ESC!\19357h\DC1Y;\FS@;Jd\DC4]P1Y\vY\EM[\\\1044982#\SIZN\DEL!k\DC1a7\1108339\&1\11532D\US\25783f`\DC2\1050644\1103528'v\"\1069716m\1108792\1106047j\1044188\1087005\DC3\57801\DC11\bt+k8\GS\DC4'hh\1056281j\SUBk\1037401\DC3\RS\DC2,\1005521o\51371t\ETX\1051670\1109590\b?\126115\189762e\32865?)h\n>=\EMw\EOT>LeG\b\vZ0\GS_[\SOH9\DLEc\1021058\&5\101013^\DC2\ETB4\ETX\1038029s\SOH\188620K\FS\GS\92438\rL\ETBU\ETB@\74427\ETX@\"e\39077LUj\1069428\10801\SUB?\DC2w\SOHZZ\FS9\16133X2\ETB\rq&\1014748\1019808pO+?\1102310\&6T" } @@ -127,7 +127,7 @@ testObject_DeleteProvider_provider_14 :: DeleteProvider testObject_DeleteProvider_provider_14 = DeleteProvider { deleteProviderPassword = - PlainTextPassword + plainTextPassword6Unsafe "b\58964t\43509CR[\30082\ENQ7\100247\1034461\1087199[\1076643R;\SYN+\EOT\128754j/\1080285y\1093960\1074869\1111511\1104150\FS\27079\&2\EM\RS%\1082459\SI\36007\b\1047331l\SI\DC4a\vA\1042157z\FS\ETB)\1064065\SI!d\1079928\FS\48559\992612\&0X#\135600r\1064811P]X=AC\8677\EM)\a\31588\151769\1084526\5443\180158\134162T_\GSe,+dh\RS\13107_UD;Y\1015192q#K\1061942\97367\1040156\\Y:\33421h!_]\1048452\a$/\ENQ\SO\DC25\94744Pw2;G\170952\ETX\NULy!pb\45112\1103992\994562\1108199\ETX\DLE6\178134\STXL^\9774\DC1Y\ETB\21002\177094\1075624\987871s\118876\1073267~\6034c=.G\1052723\73447;>[\1059144\&0\151875\26991|\1110095\1006840Cz\990775,U6\1104366\4586\1061238e\166472&>79\SYNY!<\19984@92\175732\57726Z\DC1$!\nkI\SYN6GN\1081932'2S\NUL\1019754\nL\183592i\CANiE;\DEL\1016455~\987760u5\999910Q\CAN;x\1087031\154161\8330\991716BUW\182886v\138451%\vL\72727NI&OMAda\ENQ1J \fn\161210d_ne\NULln>vBY2\DEL\DLE\36843\177112m\1104121\STX\DC417,~\3514\SO\1071353Ze\58200\SOW\1099316 h\1091883/\SOH\EM1\a4\GS\1086744=\1077430N\147549\&5\EM\997507\63121\119025\ENQ\1066003z\100958M\DLEzjMOt\1073319:\1017862*\16993\96231\SOHX\STXm\CANu\NUL\110651.\FS^\1080562M/\169642\1007425\&3UJ\NUL]\1090144`4\1054369<0$\411\ETX\DC16\818A\1110569\SO.\180475\1089094\\\1047981\16200#\t3y\RS\f\11238\v-FO1'dC3 \988541!\1109624\NAK}p\137074 \t\ETX\1089664\ETB\DEL>\1033232]\SO]\41807\ENQz A\48291\1080313\998629{\SI\43105[\tM\38214h%{7\DLEA\990402>\169399nC&\1010809\f?\1089890\1045417SW'\134489Vi5\1063266T)\EOT\136130\DC2\rA-2\1077967\171766E^\155561.\ENQGU\SIeK*j\ETB\1066886>|\1016171\161797N\995265\r/^?\DEL\176863SH[\48944F\1008201G;W\ETX.\\\54392\133906\1099838xPTzJOxK#+\SOHQS\ESC_\ENQ\987918\ETX/J\ACK\NAK\US\CAN\FS;\FSh\t\1075773\GS\1003463U\140135\STX\\{5\126649\1044119\163168\DLE'\1058566\SYNr{\126548Yn\1102570_\128667U\31090M2\a\1058972\RS\GS8\ESC<\1004867WJ\1083668\DLEj\NUL\175358@XnT\ENQagg&\SIj\STX:8\FSsD" } @@ -135,7 +135,7 @@ testObject_DeleteProvider_provider_15 :: DeleteProvider testObject_DeleteProvider_provider_15 = DeleteProvider { deleteProviderPassword = - PlainTextPassword + plainTextPassword6Unsafe "\RS\SOH\163246|\1081576&\1075890.N\GSU.3Oq\151879a\FS\DELH\SYNs\1076516\1106106\RS\1112048nH-LN\42248\&1\v\181047\1113105j\1001303C\1004626\&5g}\1065015cw\986165\1070943\61101:EvZ\174578(\13913\STXF\rs=\SI\44701k\140660\&9\1076022\1013260\tK\1041887\164216\1034762\1047302$o\r6\168682\&9f\169746\167284+P\1090200\1035482\172491zW\1102752i\992639&M\917933y(\ETB\ETB\1073015\1101769Z\nd\144149\59717!p\NUL>\1054017\&7\1009114h\170904\1100109E(\161156\ENQc\\\1077220\50624\&7vZ\2371rwu_\1053440]C\140470\1092234.\"5\US*\137224\&6\SUB\1046146\SUB8q\NAK\173329\63177A\167607=^a\1085502{\\*\83220\1003880\ENQ\10500l~W\1063837\DELT\DELr\1004181\DC1\DC2x?\49524wO}\r\EOT\144109*\1068005 \f\120687v\nBs\181134ja!\"oi\1037158)\ETX\74886gOW7-\DLE-\nP1A\63650O\1033878F\DC2\f.\ENQ]\DC2\134995vL\CANR\1063068\ESC>UMz+\1091884r\nDsb\163979\NAK\a \RS\96765\36103Zf@\ETB\SIm\SO5\nkr\FSc\996842\US\ACK\175700\1009400\&4r\74926:\1107913|7\SUBE\DC1\985699`C\150786^\57829\191390\1076416\FSeX=C\19389S\SYN_\39050\38611 \1092584\DEL\172018\NAK\23389\bi23U\8829.\DC4\US\EOT\GSc]_y\1074588H\47815\&3\ESCL\1092515\&3j\NUL\NAK\1101578,\12565:\r3\167203R,n\100484\ETB\DC3\DLEEPw\1075213\r\1027682P\1015026H0\46948\1009050tE\ENQ\aL`\ETX\39936\NULb\1092605\991410D(\63685\1025013\DC1\"\NULc\118802G\48098\a$\1031302\1093860I\27655>#\148108o\57701t2\1029904\&1\22701Mp\6046[W5\184395h\37434\vO\12585\985865)\NUL\143558W\t,@\EOT\994743\1097630G\27848\141382\EM8mc\8959\SYN\SYN/[\173572j\EM\20360\1111592\27686\ENQ3\1035412b\1037050\993709EFnU}9\1021599\1102061\ACK \ESC\1045396`\22327]\NAKx\74630W\1079807\t\6512\&5yR\1028421Nc0]:^\588:\ESC8uz>o\SIP,[\ESC{\1048188\170536w=]2\SO\1009630\EM\"\t\ESC\EOT1\DEL\41025\153517\&8\n\136032N]\"c*c1uH\ENQ0qjKC\1034529@&\168350\SUBb\1101720*d8\FS{\180679^\DC2\DC2\1035986\1112759\1025915\&7\155709\1103075\64400\40440\1039882\ACK\69406z)\DC2M#C\NUL])\ACK_D?\ACK>+}7^^*>\vOj\44865G/k\NAKZy\n\35781\DC4q\1018031P\151611:G\1106921v\1080679\n\1043025\21106d8\190025\DC3e7d\66589\991382\CANOY$KN@+\DC4y=tt\SYN\187543\v\996590OH(\26817\DC4\SUBB\1075843\&4Q\143566t\SYN-\\\b\19213w.\GS~\1057961rB\SI\1104286um\1026778\186266\&6\ETB^\ESC\US\RS\1060059\SUB\175508y\SOH\1031791n\FSty\166317\ESC|\n[\EM4\158389t\RS\t6K\GS\1062433]\NAKE%\DC3q^R0\94889Ya\1092835o\1103087&\1012475s#j,\1088050DYum\17114\53975\18506\170767\988594\1106611\138262!\98848\DELT_!1X\986364\1040882\GS\20624mA\1012611\15315U\SI\RS.v\rQ\SUB\78422eh\SOks:Ke\r\136392" } @@ -151,7 +151,7 @@ testObject_DeleteProvider_provider_17 :: DeleteProvider testObject_DeleteProvider_provider_17 = DeleteProvider { deleteProviderPassword = - PlainTextPassword + plainTextPassword6Unsafe "Y\1111686{C\SUB$)A5}\1037699r\64320i\EM\DC2l$\1080256B'/N#^(\165678\34179}\174928\1030133\f~hK5)*%a)o\1027470B\b\fm$F\98914v\147309(\158225\1097652\&6\1018954=>pzgR\68163D4\11771&\DC3?\99182\165889\ETB\ETBH`Lly#^Iz\"mp5'\1030424i\187500N\10233\1091522\12122\&3\\\1038681[XZb\1111103\1008586x\DEL\98369\DLEU!Q5\NUL\DC4Mi|M\NAKq\SYN\146639k\\R\NULx\DLE\47985R;V#=|=\ETB&\US\141694p1\ETX\DLE\1036965'\GS\DC4,b1\1001651\NULb(Q\SUBhu\EMD<\180686\159770\&9\SOHXm)\1055998\159683\1099970:9\99357hLfB#Fw`e\1013324\EOTFb\v\ETB\33707Y\SO-\917817\1075524R \1089165\SOHg*!2\ETB\1091876o33.I@8\190235l\1064222\SYN\1035414\37496\bhp-\US{\1076177E%r\"&\14430\DC29\r\NAKAb6\DC3\1102220\134593\38926\&6-U\ENQ\31570\21170^=\DLE\37772\141550\&5\ESCL\ETB-\1045961\1053532wo\1099915'vt\72729\SOH9\142176\78110\DC3\SYN\ESCI\142104\1029509l\EOT\163661\&7\DC2t\SO\1079897~$;\61461g\GS\51504\GS3\162463s\188827\28282rZL\989190L\1040493^h[h\1059704\18006\ETX\ACK\51711T\41593C\160950\1089468}-+U,B\43472\18750\DC2^\1109666\53210\119346\74988\1018132\GS\10434\1041335\25217\NUL\1055413\\q.2\ETBF\1071025\52062O\36781\60964\1017409k\1069787H\1087543;\167027\NAKr\994597b8!-\175535\19285\121482\59568\46793z\998810\1068682D\EMk\SUB#*\111355gzC\FSg-{\RS\96607W9\168210\151686\SUB4\DC2\DLEE\EOTr\156887\1110622r\a|eY3\167679x4\70151\&4\v\985172\DC4]\ENQ\DC4\9412\999921\9133(\61253O@`@\40016\DLE[Em\1020245/96f\186696Y}lftO\1052392_SiS\147632\23259\60158\USSL\ACK\v\EOT-{R\1033058k6 \70113\&3\ETX^\SOH\10941u%V\1111562\9529\71728g\47762D\16143J2?Z|\1049927\DC2Z\ETB\141688\DC3A\120792(@\183856\995526\ENQ\996855+\DC1D\DEL\5033\&8a6>\1036623\DC2P[\1024005\&3a5P\ENQ/\156230\151368\12046\&7B\ESCk\47755S\304%\1008766\1061238a\166179\NUL\133901Ya\58953hy\1080315g@\1073839\1034222BvE\DLE#Pe\1060144;\SI\1036063\1096979[U&\26840f;Bc\1065091R\1091793(l\ETX\1026868VT]%0\DC1\CANe\ETX\1011732\a\DC4\ACK4Y\t~\"a\1068011\"\1052055\"{a7\1084777(;J`\DELla(K0\SIeE\28309\18904~\171294\EOT\SI\999388\t=\1068077\1079397\1068594d3}8\21072\EOTM+\70402\1040161\52377\1061479# \1066933\1011184. \35779\\\SYN(V]}\EM\1046997z\1015251c\1059552>\148901\1025421z6\999624\1002310\SUB\39894\SYNY\12850rK9S\14047\996190ps\NUL^e\ACKS\1031053g\26960+\1011451\1024730^\a\1040759i\97575w\1075975\1061444\&4a\RS\FS\92968\1099246\135015\EOTE\35685ON$`\DLEG\118943l\1094375\137265$\5932\ACK\1095091\SO\1037386|\1074130^\1057249r\47614\FSw\ETX\1030398tH3m\187374\NUL\ETXop\182636" } @@ -159,7 +159,7 @@ testObject_DeleteProvider_provider_18 :: DeleteProvider testObject_DeleteProvider_provider_18 = DeleteProvider { deleteProviderPassword = - PlainTextPassword + plainTextPassword6Unsafe ";inV{\ACK:k<\159032'f\194800OO\171908Jg~\16950y \1063289\&4f\185251L8\1067002\NAKE)\n$[\153877\164020W)\1090118Vq\1076661\1056381\ETBv\FS3\"\44071L\ETXY3|\17433D.\175929\141623\35056OW\169598 c\993540\&9#c`]\DC2oZl\168383EYp04qh^u\\\119175\1102533\1049137PB:\DC2x\FS\1100667\ETBR&e\b\1029890\SYNVq\b\132190\t^q2\SYN\1106187C\186092\1091537|3H\1005501\129514Q\rQ\65896\\'%F5yon\180549g!*JR\DC3|_XT:\STX/\SI\1089648\1073482ts\1112740\156579\10425\ETB\996553\RS+\986289R}4\19735K.y\1021391\161879!6Qe\142179\1050604\188602\EOTv\172728H\DLE\toXZv\70156\&2\141492$\163293l\140322\1064732#\DC2\1045997\25474\NAK\vJ\1006867&B-^5A\1042940\ETXjz6R|{]f\1074871~..U }#s\132245\DEL\NUL\132562s|\94652\"\DEL\133115\DLE\CANLhq\1033726\ESC\v\1002946\1061222\&8\EOTEC\SI\144619cP\10760\DC2x\997216h\155878\&9\FSA}1\RSsx'\fK\7536DDH\8857\"QBw\NUL8\STX{~\ENQ\ETBB\RS\1053865:i\NUL]\FSuOG\28206\63332@|=#\EM'W9d*_@\ETXgo\995815\t\EOTh\SID?T9\181985\1020008m\143442>c\1001882\NULi\149569y\CAN\180906EPq\78481=I>\US\FS\SO\1094604\986242p\1033389\33784/\n\a~exe\996608&E|A\137812\ESC\169170GV\1065628:\1012118\983414\1082272m\33380\GS\ESC\1074967\b\154149*p~*=)g\ENQ\"\66625\r+\1038096\&1Mg*A\1060855h\v\45287>K }HCM\985362\SYN3tr\1040974) Fs+1R\148331\&4\183209\US\ENQ\1107835wp\7962\v\\\148159r\1067131u\185237\"'\163734$\FS\ESC5\1005766=#'\ACKa\1030348\1025970\1111229\&6hb\ENQ\bh\fz-\SOH\110742U\11663O\11334\NAK\1066984+>\1106918\1100386\133478\DEL$\DC4\1044763I\987097s\SI\14989s\DC4^q\GS42Y\ETXFI\FS6L\ETX\1053178&\989232\1036131\52123\1049385\&5\1090940|\ACK}0T\83124\1001336iP+[\EOTZ\\U\a\127841AZ\23434Ik\707#0`RB\DC2{\153322\"1\149405s\DC3V!<\164942\157369\1039498\121459\1032366\175383\50990iyh_\GSP=e}\190818a6\NAK=\ENQ\18951\a\1086109C\194976\152534\DC2N[\176451\1004378o\1110379{\NAK\1010009H%\186064e\DC4\ACK\67223\1031998P|\1072232O\SI" } @@ -167,7 +167,7 @@ testObject_DeleteProvider_provider_19 :: DeleteProvider testObject_DeleteProvider_provider_19 = DeleteProvider { deleteProviderPassword = - PlainTextPassword + plainTextPassword6Unsafe "Kl\ENQBCU\1040121X^\119359\bK&\SYN>ij\a\20640\n+R\1053680%^'\38115jae@?\1043893\121467uy:2@'y Q\SOF\t\57777Q\995951\1077777km4\USx2\1095595X\1098369\67400\SOH44\1031165z%\1082145B\ESC\1046966\145847\175890\SOH\128321\1006495-3LD\STX)f\140657?\US\n\152489\&3\ESC\190466tI\DELH\FS%!\26875\ETB0\34759L\ETB\178829o\188053\aEs\994404|0\6918\&8(b!xZz\14160C\4765\ACK\NAKS/\STX\ETXm\993644E\24839E\1010197\NAK\1094339\&6\bU\1084257\NUL\1089839\EMoM\a\151540H\SYN\EOT\t\59644p]wK\160326\131200\170825oCzL\NUL\37401\6800`\1008455\SOH`\155066F+B\191214\DC4s\GS\1107951\1011415\145930\1011719*Va\1016814\&3\92352U\1041768$\986653\170164i\150222\40514s\6185K\152534\1068598\&6a\1112830A\CANyO\4385\SIf\1066008\1027423;\GS\ACK\DC1H\ETB9$\1077719\40661-5\1081849h\GS\1006428\SO\ETX{\\BCh:Vh\ESCv\r.\142946\1012400fo\186827\1092563\188990no\ETX8\v\"/j\1003711\1102444bHq)s\CAN~q\54496\ACK2\1085077\984760\GS|\n\990808R\165347Me#/_\46160\FS3\ETX\22209&L\b'pT\"UR\996906+=>^\SO5?r\187101}Q:\NUL3/$6\43895\1078788" } @@ -175,6 +175,6 @@ testObject_DeleteProvider_provider_20 :: DeleteProvider testObject_DeleteProvider_provider_20 = DeleteProvider { deleteProviderPassword = - PlainTextPassword + plainTextPassword6Unsafe ",P\NUL/\1062674c\1025500\ETX\DC2\1092782\7515\10241\1051631\f xY}\51863\166763Ys^>\\&\1101626\DLE@%\1001692l\1048977\r\SYN\EOT\1098659uJ\1090892\1001335\176845zR\tm\991213\EOT\1104050S:\NULq~\78186QQj\134242FFS\ESCT\1041187C\DC45/4\41800L\1021674]nn\ESC:&zx\23773DY*se)Gjyt\SI\DEL\63167t\b\158189\1011791\74151\186784\988726q\1054928\128241\1084393\1083799\RS\157739\140294/>y\nB*WG\US\ACK?E\1077605\SOH\ACK`F\NULwv\USBFZEi\1057971\1071701\"(\ENQ\SYNw\10435qD\ACK{\1045581\NAK\1000974?@\1054465\ESCd\160366\14434\EOT\SO[[2Q\71914q\ETX8\186443\17218\DC4[\SO<9F\SI/:\135535\vp\ACK*X\38920# x=(\148764\94655\1031460\19272$iWu\159354\174694\SIDXF\"\1102911:r\NULI\1022649\EOT+\1033453\DC2x\STX\NUL\1023640{r\NUL]\STX# \60640\1092332S\20202w\71172;\DELFo\a\SO\67356hzv\1000950iJZZ\47361I?|~Zz\n$N7\182716s\a\NAKHG\ETX_zp\SOH]\ESC\7155H#?uu\v\181957J\a\EM3\58109#9\r\DEL\NAK\127271/v-\1096160s\7800-\SUB=\1045407\997527\191133\993768\998501Z`\19245\68758DY\140737\178607\98172#\1097525\&9\DLEahKQ!wk{c\157906\&4\132479?\tg\DLE\36252\DC4\984848\DC4N\US\1107596e%e\31671Wj*/Xd\1107774X+\NUL\NULi\SO\nyl\1106773\983090Tt\54445\179129zO\DEL9@\DC3\STX\SOH?*Z\ETX\1123>`\ETB\1084869GW\SOH\vZv\SO\991538.a_\FSP\b\18597\143860\34448AMq\b|JSh.6\1072749\&2\29972\39815o\184644\52955/T;>g%\RS?_[h'\66707~\1075477\1104377`E6o\NUL\1035887\44786\RS\GS'\1056127|\18783\152960d@\1050621UG\985068c\DC1\139084\154956.TQ\1048778\b\CAN\DC2zE\1024549V\129551\1100529\74753\ETX;\ESC\1057712Yl\1018818u\166935\36297\1109093\141493ZO#\r\t\NAK\179138\USh\n\984366O\140762\142254\168039a.K\DC2\142191Gy\154465<\158304+D\1018755a\180376q\ESC\t\1052272\1001618\121095R\a\15159$%R LRT:jH\62211\DC3d\1022968\bR\152452*t| 3\ENQk\18884\1046962\&4%{\ENQs\SO8x\RSkw\996530V+O\ENQmT)Y/$Q\59511&?\1005279\&4EZ\983385~\1085280$U7H\1002122}e\1070772i\51290\1019872D!C\NAK\n!\FSE;V\1107110bj\NAK\1061098_\DC1\SOk\1058268),H0PE\152123r;+*l\985176\&1w`\DC2\64347\SYN$vR\1096368*ij52\151566\1046919D;e\1092443E\51993\DC2B9\1087497k\36419z(KTc=f)\43746\1106998\SUBc\DLE\989733itu\170967{k\1037793\14705\180217 G%\NUL(\1035152e\1030842|*mU<7w7\993260O\DEL\NAKvd\14637\69389\1094203&\1110919F\NAKg\1040202\992282\DC2cI\SI_\n\SI\993477H\137225\95199\1017269?\1075780\1085076G\1022476\r\SYN\168876\DC2\b\\m^\1053642\&5uh?N*\DC2\1084667|/\173546\17888U\ETXI\174769\f\1028908\EOT0\188238#vQC\fv\SUBS9\1044569\&4\1088016\1074352c\NAK\993004\&4-6M\137104s2\EOT6\SOH\DLE!\tShpJT9\1043618\1078275\163874~G%\147280K\FS\US\nH\EMc_\CAN\1087805ty\48054\"(\996357Jal\48065w,Q\26254MM\135033\1048832\78030\ENQ\135666\&8!\35782\t\34344\7132;-<\39833\2650S[\1066982\1022614\1024833vF.\SYN\1040678wu\SOcNpEY\1105650q\DC1;\a<\ETB\b\1094990\1048314D:\1018980^B=\b\177470T\1083410|a\1056080LK\DLE\DC32)h\vi\98283\DC4e\EOT_\ff\175617_W\nM8_B6\1017235^\52537lN\1102453Q \95310\1003587\13893^>\DEL\1006142`\998340\1107872\54544T\13283:\\\ty\FS\188230\NAK9\DC4W40\DC38?\CANhOX9\SYN\162545}\DC3\"\176375\1090658\95340`-5!\NUL&\988094R.\f\8339\DC3-g\a\ETB\n\37512\119835\DC1\ESC\1009823}\1046441Ke\179036\&6Ma\SI>\1081261\r,b^`:\1069901Un\990762\148377\13503}h4\25577\1108679H-\n0P{\153394\f\1114021\f7\fe\64438\60791\161330B/V0\1058234)\51833\DC4,v\160202U\DC4i\194755_>\SO@8?D\1029214=\n\182111\DLEo\1110681\DLE\1054551\146791\&2\DC3);K\NULo\ESC\DC2,\CANXX]9\162319\&6K'b\989499/\165610\989186\33910\&7F06\1084196b(L\GS\1050134\&3{[\1108824.N\v\1109096\nGb>X" } @@ -48,7 +48,7 @@ testObject_DeleteService_provider_4 :: DeleteService testObject_DeleteService_provider_4 = DeleteService { deleteServicePassword = - PlainTextPassword + plainTextPassword6Unsafe "L2\"V\6753r3\21384F\1004100\ETB?\US\1022278\23420@\1034750\&4\nzp\"\ETX\CAN\1020846\SIz\1052126J\12165\1025906)\STXa4\27812\1000964j\6260\157933\1070050\&1G7\96926\USO\1110814\SOH\50833\26545\&2@,.io\1075695\&0\27267\&9?;5M;n\1098431\EOTS\STXLNQ\\\SYN\"Z/z\SUB[Dw\180898\341\111115\&7\1009591X\32939\1096608I\a^\tT|\DC1k\DC2x5\10464\170244\ESC[s'3\63609\&5HC\1950N\148830F\1029548\1059110u\DC3\ESCh0\NAKVY@\DC2\DC4\985223\DC3\62363\&3\DC1'vA\SYN.*2llS\97030J6\1098113P(\1069570\1055988\52837wg\b{\156428s+J\129477\1067080\998577_\133791\DC4{\ETX\ENQRx7v\1033468iS\v\GSYv|\1006295mY\49707\DC4A\ENQ`XJ\DC1TM\1039522\CANi\STX_\3143!\DC4>UW\"_I\f\FS\998242,w\136697\1024216<\992580if\ETX\SOH\SOH#\62779X_\DC1\CANg?\1023357\&2\1010176\DC4\139562\&2\EOT\145727I\1047364P\ACK\164378\24874e\ENQ\96920\174821*\63977'3\DC1\DC1v\12684\&3\n,>m\SOHw?zqp\STXQoGf\t[g\1001958\ESC\1062317r$\EOT{D\1029860\&0`\188502\140808\1102082\EOTU\986732\140410a\CANo\173950k@5s\1113153o\1055827\49856+S\STXn@I.%\148298\n\1101092\DC4 \1056737p'\128936YcN\SO\60490l@\1036155\&9?\42343A\NULat\1109075\EM9\1113569\f#d\STXPP ^6&?\11000_\61012\ENQ*M#\36501R\25749p\SI\154166K<}t:P\984299\ENQ\n=\1035698\"v=@H_\SI(\DC1PV[\57739{0\140392\a\"K\vXD\144064\66295LRB{+\9504:l\1102414r`\1113738\&4Zh:\EM\128474?\1058326*\1090019Y|]\174030\EM\1036213\SOH\183356#\1000903\1035926\&5Vz\DEL\SUB\SYN'ZX!1\43994\&1yc\DC3p 0\1068860\1085354[o,2\1091212IVq\ENQ\983215\SYN+,\f\59521Tqx2|jc\f\b*.C\STX9c+\7769\21087\EMV\160699\1110293\24375p@\144691\1096790\136541]u\RS7M\ETB\SOHu6\155675r!X\67180\EOT\nx\ENQt\ACK\1072004\ACKEG\SO\USb\DC3\10246\&7.g!c[y\SI\1077060Q\993055%\ETX(\135156AX\STXlHP@Yf\DC4\NAKzjs\SI\v\SO\1085889\NAK\9804L(/]k\1051473b}\1017916?\179327\1075800}W\USG~\128139N_2\25917q?*^MQNL\\\ESC\FS \163401I,\1110667\DC2i\32846\1105436[\ETX\177036cOam\1009736\bD\50866\110872\r\RS \tr\985779\69978&t\1050041P#1@N\1040570_\1042320\1102184\1085170\39341\v\RS%\t^\993553\SUB{\GS5!NP\nC\35824\SUB\1067488\1050717s41\181692F\134397VOb\DELHV\aj('5hkr\1095355L\151305N\1094082\&5\172446\NUL\DC3\1007647\161512U1\983204nS3\DC4jf\1030456\43037i\17090\1080707&A\1004875\r\DLED*GJ^eH\30653\1101701(_~v\b\1085954\US\DC2\n\182353A O\DC4#H\a\83302mx\74596Hq\t\n<1n\\>U~gcqCErC@#\131787HyEr\nHF\EOTN\165554qR\DC26u\1052388bn\DC2%G\v\b1\96521\&6g\EMp\170871i\32661\&0*hL8\SI\n\180656Mpms" } @@ -56,7 +56,7 @@ testObject_DeleteService_provider_5 :: DeleteService testObject_DeleteService_provider_5 = DeleteService { deleteServicePassword = - PlainTextPassword + plainTextPassword6Unsafe "\t\172857\42813C\CAN\GS\161736\a' h\ESCN$\bE\DC4*F/[\r\6924y\ESC\GS\1057486\vF!r]\"\10020r$\1083003-\1108025\ACK\168132p\SIc\1013251\EM8\DC4\NULq=\NAK`U/\RS{e)\DC4\DC3\74597\186929U\ETB\1088221J\ESC\fh\991495K5`s(<:\EM\f\ENQSE\1104616\&8A\1083480Kmh\EM\\\1036835B\NAK\1099687K\26605\988753lBp'J_%\128829\48697\1054594G\DC2n;pt\SI\SUB+i1\EOT_\142578\1004730aR{\1049051\CAN\vV\31101jmy8\STX'\140278\&5i@+u\1086394\1004758\996051\EOT-\1070519\163184A],J(L\ETX\165645r5\39997F\20971gN\r\GS\1096859\ETB~\voX\NAK\DLE\1057183N$\"2 %xsk\DC3\997612\141019+cmN\SYNr\983491\NAK\ETB\SYN\DLE\ETBCt\1036568\1080751\ENQo:\123596LL/\ETX\bJGqv4\\\25512MAt`>G wFML6d=\146826\EOT}|\GS1\DLE\EOTi\DC4\n\r\138718n\72975i[Z5d\STXx]\GS><\60749\32184\78324bx[\"p,xeLS\1038078\190378\DLEd\vxote\168399,\187447\f\18411>B\DC2\1036527\CAN\44361k\SIG([w,\1102557#P3.7!3Wr\v\rM2;g>%\50923Q\1045600`uy=*\1081173\ETXyo\SYN]\190840wJd]/d\151668*`\1084306D\DELZ\1027879}H=5\NAKG%K" } @@ -64,7 +64,7 @@ testObject_DeleteService_provider_6 :: DeleteService testObject_DeleteService_provider_6 = DeleteService { deleteServicePassword = - PlainTextPassword + plainTextPassword6Unsafe "\128376EhL|Lu\23379\1080483H_~K\8066*\tX\1035800\STX\DC1]z);.\SIG\ACKBV\1102139\1082533 \120015:0\157693\162521]\ESCu(\v\1014410\1023814\1012657w\14968~U\"\EMf\166514q]l-)BSsx\18821H0\145814g\v] \1045056\n\NULA\997958\132031$\20514&e\DC4\DC2c*s\149852\1045753\32216\&9+\1072506-\22880\186724q<\EOT@\1046997\95854\GS\SOHw\10768C\t\DC4R\1015074|\1013866\988177z#]9.\1090164*\736\a6\985230g\DC3*\SOH\SOHr\ETX`\992329t5-R\46692o\34976mW\DELC\NAKs}.%W\SYN'GA\NAK\DC3]-Jj=,V\186648s\1075877\137116g\38327D\1025836\10179H7\68640\DC4\"\150298ST.\35152\994016\CAN\135344\a\DC3\f\1067223@\CANJ0\v\t\SOK\61106`?\DEL\95854\50324P|\GS\CANrE\992378la\1082907GWLq\ETXmc\1081729sM@R0h\ETXB\1036016\SOH]'\158367\DC1*N\1080973T\998376\92956\DC2d\1038903\RS\188257\1060904\993321\1044087\&5Z\STX$e\1103845O)0/\44061~\DC3\DC3Hd\998585Yt6Wo94\1036192\147855A\US%&\68303\FS\1052800\1081644_|w)\1109140\RS\162501\NUL$?AHR\19420\f\bPl\DC3\54563\DC4\33018*\186381\SOH\NULf\DEL\DC3v73W\1107996\83019\1045844N.\ESC=\180831Ptu\SUB#(f;Wm\7415\\0d\1095311\EOT1a'O[\71125z8|;S\STXP\62324I%\60473z/`\182696PF<=x!@\SOH\EOT\DC4\bY\v_/\a@Y fLE/aC\60167_w\1018128\SUB\CANf\135056`I\SOH\1055086A\tt\132582\27088GO\FSl+\DC3\1011310r\59704\GS\6624%mbT\55138\ENQG &j\1057843\td{\161063Z\1055111D[1\96530=n\169420b\20176\998614{\RS\1005601\"\39342\1027237?DV\61675\v(P\1097380\SO\181027\27659\20728pR/\1078103j\96346y\53377\164734\r3\ETX\39384j\50759k\DC3\1040693/\1104301\SI\NAKx\44875^\177769\1080050\&2Sk&evu\101060\187206^N)@H\GS\1079756\bA\DC3W\1013638\EM\1041225T7D\95543^\\6\NUL\SUBw\1057725b\1016188*\1043975\1014298a\RS\1012013p\SOHd\NAK[&\1103345T\98112\"Q\60620\33674\FS$\US~\SI(\1073485Ko\RS \1012751\EMF]V\170752CF\fP\SOHQ\NUL!\DLEH\1087861\SYN#\EOTq^\993604\151199\1038802.\988853E?B9\ESC\f\ACKKF\162140\164353\SOH\146902" } @@ -72,7 +72,7 @@ testObject_DeleteService_provider_7 :: DeleteService testObject_DeleteService_provider_7 = DeleteService { deleteServicePassword = - PlainTextPassword + plainTextPassword6Unsafe "\136983\ESCQE\1031569\DC1m\DC2\SOH@I#\fnJ\ETX\38914^\1113665\1018472\1082131N\1022796\182352\DC1TPq.!8\169378FqJzQf-.\10416\40128\&9s\64369GC\43913t\1030201\SOH\16112\1098240\&6\133919)\SOc\17130\985217\SOHUF\16066\ESCoZB\DC276Tw>UJ\n\FSn:0\144123\ACKY'*e-\1098900\59543Bj\150654SX\DC2\v\FSK\SOlXY-\1113154P\SYN\1032888x\1097596qf\48246w}\rc\165617im4)\1031691\&0T`/\1046585'\1041485\b`\ESCP\1090958#\vN\\\DLEZ$D6U3f'\36612\985822+\1047049\DC2\SYN\n\94587\atIc\1023477cr\NAK\32429l\r\182656\1026919\DC1'\SO9T.\ESC~\74564\165585\11714M\18761\13838\52007`\96738)L\158049a\24389\1011390\DC1{\50619\22441wu\DEL\GS:\DC3xhp-\SYN\161044d\NUL\DC3@4\1101155\"@0\1079248\GSr\\\1111460\DC3\27560K={Ae&uQy;u\1059410z~\1100715x2Y,{\1074143\GSoF?o\1082020\71318\DELSr\993616G\42927R\FS0?\1069779^)\SUB\1090952\71703\r\GS\ESC\998256\ENQ\a\vy\1021237\FS\\Pl?aM\SYN_3\t[\987821\&9IKG2kc^\99089g\"i\1085089\159329\t\181764a\1053641y&\SOE7\DC46>>\187064\25144~\t\DELy" } @@ -88,7 +88,7 @@ testObject_DeleteService_provider_9 :: DeleteService testObject_DeleteService_provider_9 = DeleteService { deleteServicePassword = - PlainTextPassword + plainTextPassword6Unsafe "[\r=\1010928\ESC\NAKPq\1091744_\29858\f\152928(\243\1089953\n\1086813[\SO(\DC26\171315\SOHR6\1070191\1059359T,\138258,\"\175571FJ\1063162\&3A\EM8\1003695=C-\1048718\&7\25307\NAK\144818\&2y]\182318\DC4C^1&H\1051688\&5DG\1100017hB\986897\1079098\CANE_.\1041564\"\ENQ\1049879\v(\EM\EM\1046376LROmrW\CANN+\SYN\1023612.WoZ\US\SOC$b\23372\DLE\129288A/x(j\1108387tO\174309K4SP\15428\NAK\ESC\1064811[I\NULC\127304\1081191[U\EM<&\45631\28921\27182B\RS_l&\3177&$/$F\148093\DLEUq+`\153269*i" } @@ -96,7 +96,7 @@ testObject_DeleteService_provider_10 :: DeleteService testObject_DeleteService_provider_10 = DeleteService { deleteServicePassword = - PlainTextPassword + plainTextPassword6Unsafe "\SI\162050\1010608[\1049535n\b\ENQiN\NAK\US\NUL\SYN,\157238)Ac)]d\1008845\1006824Y\1022374\1082018FhaWXY\NUL\EOTbCK\49929\95708(7\1106169\1010949F\EOTxP9\FS\GS\1082851\NAK\1011817P\46197QJw\STX?\176946+Q\NUL\190459!x!~\155567\1048077\NULi,Q'\5401h\1000486jp\1029658\ACK:Oq&v\DC4\1012006\&8\CAN\DLE+\ACK}\1038820\"5L7\1053147\1109810\74179\&7m\v\RS\161930\1075844\RSS\30116P\132230k\717\1040324\v\rBP\994309W*M63-J\157232\163200\&6RH\1026767'\FS'r\DELsf\DC48\DLET\")K\DLE\DC2X<}\1040651^\163594S:\1007149\158242UZ\1069548\EOTi\33002\158326_\27737%\177997Kq\1003460%~\v\34758\58147T\GSps\1103527\&1\1011413\30309to\DC2t$\1017630I\170455|1D\170894\160641+\1103518+v7\STX~\5395\1004330Ju\EM\25814cx\"#E9w\166343ONY\1071128O1\121062p%t^\ETXNT\78812\v\f~\RS\SOH\SI\n'{\r\CAN\NAK\SOt\STX\68818@P\GS\1007512\USy9^U\ETB)mj:\1062680B^\1008595x+\RS5U\139534\98639\GS\180227\25236H\1060521$)wbin\188907\987276\ETX:5\1039916\ACK\ACK\986245\1048529hl'\tb?`%vC\"D1[\110860+\166283)PV\1044669Gh\"\19964\12043R>\1006662\DEL\180850\ENQA?\SOH\132636H\1108426$2\96437\1013325\DC3?mLdM\188499/%\DC2\FS[I8v\ACK\1061666U\67987$GrCg\SO\167134\1077919n4?r \ESC\1050882Tq\ETXu\1023983\ACK_|L\20449a\1094815vAaO\139479r=4\39568:p\1060300\a\1092447?\1110782q9\ETXE\SO\bK-\48132\SYN\48687\146156\&2\SOSj#\ACK\1083773l)%G\1078272K\156921R\8373\CAN\993378v\DC1\ACK\128360\1039196\EMo`6\b\31597v|WoQ3<\STX\97186o\986190\CAN\1088664\NAK`\FS6\SOHF\1063772[zXs\1044292$\171854\GSoH-|\ETB=B\1112836\52633\NAKox\DEL\73760q\ENQ\DLE\144879\176649\1093334\CAN\1049023\&4K\71172[Y\144809\SO\1076788f\"7\DC2Xj]s\SOHj\1088788\&7i\1111376b\10639\NUL=\RS\23060\997561\191212A0\DC1N\STX\ETX\SO\128338[\39744OU\SOH^x\1032678J@F~`n;\57681i\RS\SUB)jI\EOT^\73047Y\aM\13358\22601\100792\51350\"o\1077272R\\\DEL\178673\49995'&\EOTUtq\140882.I\156694t\41032\17600cBk?\"4}\1058055\1113533'\n\1018860\12101\NULa,\1094524'Jl\1062808\nS:<\GS\1064267t\ENQ)5ow\1055684\1089983\ACK\163808\&6\16512{%5\1095796^\1024905\1066253$\GS\DC1`W\r\31878\ftiT\SO\1071558\&1E;Zd\fx\1037175\DC2\121349\128283\NUL\992725\SO)\1055782D\992230" } @@ -112,7 +112,7 @@ testObject_DeleteService_provider_12 :: DeleteService testObject_DeleteService_provider_12 = DeleteService { deleteServicePassword = - PlainTextPassword + plainTextPassword6Unsafe "hW; ''\v)Eou>D.9b\26680\1057700\t\CANTe\NULRQT@\1002782\t\ESCNaY\186493\152332\NUL\1075769~h\150526\1097365\1021739\96557CNA,(b\DC22PB`Xe\ETBI\GS\DC4`_YVE*\99429?k\61179S\169097\1020602C\1008782\CAN1U0\187652aq\a\1038379o\1085901Z{)%b\SUBX\146310B_\1049028+ag$=]\1063273\20925;QY&M]\171897g\ETX\997959T" } @@ -120,7 +120,7 @@ testObject_DeleteService_provider_13 :: DeleteService testObject_DeleteService_provider_13 = DeleteService { deleteServicePassword = - PlainTextPassword + plainTextPassword6Unsafe "\aF*\1056058.J\1072915b\ETB\988555cDt\1012701{M\1007131\1064187\1046279m\a\1016299xR\1074348\1073660\1089710\162480\1023560*\999059\DC4K\GS\SYN\NAK\SI}<;\US\19573\1074826EN\DC1\1068526\&8\1059620\b\175256\1033927\1027860\SI\t\DC4\1029714k\135937xj\DC3;i5\ETB\\\100057\164428\187088\16736kE\ETB4X\9242WZ\US\143241y\"\994502f\ETB\SOm\129169r\ESCh\GSwN&Q:K\68129\141105\64409J\99068\11299\1011531\ACK\1047483\1022907N5=&b2\SOH\995316P\996499E8\1092551!|\SOH\ESC:9*5\1070832:h2`\68032\DC3g\4440G\1059756\24716\1041762v\SUB\SYN\1041661\&7+5vGr+f\t\ETX8\SUBvk\EOTP!?[9\29758\1111629:\DC2\169224\3221k\137789#\45939Kx\n\174109\1042790,\ESC\44492VM\994186\14327pG\172068\1062750\&0%{;J\ACK\34102\1044167\41271r\6389~|\b\ve\DC2|\1096047\1101984).!\DC2\1029543Jw(5\46702t\63234\160349\49957\&1\EM\987340xC\DLE\n\RS{(\DC4\\\v\FS\1008849|.5_\164914\&2d5\991610\SYN>\SOHw\1023032Iw\164733\1020115\"\52458\US|Zn\"w\RSb?\984015" } @@ -128,7 +128,7 @@ testObject_DeleteService_provider_14 :: DeleteService testObject_DeleteService_provider_14 = DeleteService { deleteServicePassword = - PlainTextPassword + plainTextPassword6Unsafe "\DC1\22955\USO\v\2556\187895\ETBX\187498\STXL$\1039087$\a7\DC1c\CAN\"(\18295\1038981\1088462\STXW7\136317]=\1083968\t!/\SIg\1037805\49165n\"\\\"c\US\DC3xB%=I\1009690\ENQ6m\DC1\166931 N\EM@s\1112051\ESC\\s\190990\"\986386x.&K\180570\&1b1]\1052809\1021645vQ\149506+z\55274\1085173f?6\1009631y/Z\CANA\t#\\02x\159617WF,FJ^A_\1084872\&7o\FShuK\ETX*#\1033514]qK/W\145176QI?@\1002085p\SUB\DLEg\ACK\1077055\DLE\141695_G\149813{\ETX\STX:)A\1021354Qd\RS@\1094570\"!\ESC\SOH=\CANHu#>\153870\&0'Jmv]\STX\SUB\NULIj\1058287HGw\988502\1054602\13562-\SYNCR\SYN\1069893#E'w\1104113;\1055038\1095467f}2GV\1109390\&1\140032\1018034@7\GS0[O\180741\165383\&9\168573Ah" } @@ -136,7 +136,7 @@ testObject_DeleteService_provider_15 :: DeleteService testObject_DeleteService_provider_15 = DeleteService { deleteServicePassword = - PlainTextPassword + plainTextPassword6Unsafe ">s$:Gkysvp)1g`^C:\1097285\DLE\1042037\1060873}\SOHgS\EOT\1014770-,Wg\1059821W_r'\50698\174026X>\1057524\\\37373r$\f?\1047617L\aLAw\DC1h2\fJ\153848x\r\"\DC2=\989118\SO\ETB%\SUBp\ACK6\1041904\26946\NULqJ\1042107UZTK\CANu\984810\1000877.A8h\12469'\DLE\DLE\45777\DC2\v\119004X0qXo\1016220MVB\188426Hrv=\NULG\177882\f\ESC\f\1072755\1063768\RS\tB!t'\US0v\1094678H\ESC5\1052529V\24969\179998E\183293E\1089081L\142474\2212d\1015371\ETB{5\b\1085433r\fN+\ETX5\917990\NAK ==5^\24976W\1094896\\\1087724\46810r\1034783\1099955\&1D7\NUL$\150130\23080\ETBU\983748j\DC2F!.\1011478\&5\1005216\1010480\&80\152760\1068360\96897\&5\160838/\17409\167739\1064995\21447\1069604\1091697\29270L\150565\4350\1008571GR\DC1\1024278:\834\1104790b\41841\1064198)Jn\48676\1051373\US\16820\998967/>'d&hQP\28827MC\SYNaq\83339(*\191321\DC4>l\39576~\RS*\DC3OAh\33301z{\33059JK\aYw\DC3;d\FS\1051228!W\SYN\992738n\SI\1030884V\US\EM\160254Mp\1061773~!\20793h\DLE+M\DLEi\EOT?5t@4!/0\1010526D;\US6Oj\143485`+\DC2^\182923:)7\SUB\164499\16263y~\ENQ#!\179196\15568\1057566\n2\DC1Z\113767\1005048y\t?\1081555&w_\1019029S<\29361O\STX\65137\DC4\1039625H\SO[\DC3\rH\1085267\1056334\1059773m\EOT%\142377\&2\SI\24993\57810\&5PC\1003072\&1\12115[\v\140175\n\997659M\n&?q\1088276Q\1021868\&9\96760F;v\DEL{<\1051978\97388h\92704\NUL\NUL\DLEn\169914\15551\NAKl@\1028759+\SIzb#\FS\SYN*gX[{H{)\b\44577\42072\DLE8H+0~@\25029\1099003;/+\v\CAN* \1018086\28116\&4\awM\v!KR\STXB\119577B\SI.s\NAK\ETB\EMD\120066\DC3Y8Md0\1016568KHS\1087988\ETB\ACK\1050352\&9\162700\DEL\f\1065134uV\988036l]\1076941\&9\1073834\NAKG\1052530\136250?\SUBm\179047\n\RS]\DC2wkgn\DC44\145653\1042881Ah@\182842\1051447k\998400j\61720a\994060\SOH\22802\RSo\SOH\7673.\1109414\1029022\FS\SUB\1057319\&7eW\40506\SOF\fD\DC4Zq\DEL)\50727a|\1039642\ESC\44613\1084395\SYN\990755\6567c\fl\144203\vk\n\ENQ\1051810h\b\NAK\NULpc\1003207:\21145'6~\18883\136716:\t\1113525*\1062609\70033;\177276`\DEL\\XM9\1099577Y\"\vX\1096963\vI\1064869t<\1019336F\RS\29205\&5\12817\EOT\ETX\1051299\1007913W\1082285akuU\1096842A+.\1102005\189823\142876\DC1\1075627kJ\1036321S\1016366t\125116\&7.J\EOTf\30180X\1097269\DC1WO\152648`\1079570\12741\1027795\70515\f\168890~y\DC4\DLEc7BGJ" } @@ -160,7 +160,7 @@ testObject_DeleteService_provider_18 :: DeleteService testObject_DeleteService_provider_18 = DeleteService { deleteServicePassword = - PlainTextPassword + plainTextPassword6Unsafe "<\ACK(\94072\&7\tg\28689x\US[\1015715$r\129494\21483n\145539P]\1095866\DC4\ACK\119849qP\163760W\1023008\1083754\1112401\1039398\146736 d4\180222dw\US\NAKS/\13356.\ENQ\99074Wc\1057568\137602\1029979=;\SYN\1005115 \DEL\a\RS\47125\&87R\143400\1101827{\f{\171248?Bu\984165An6\t\997933gw1!\25291]\1096376[M\1056072\1058952TBu6Xs)WmT\990423\168687\ESC<:\SI\EMi\DEL\24488{\25238\&78n#\DC3\134177b|\b}\RS\\7|W(\190278 w\ETB\1100914]\999230#\SOH&\94715M!%.\SO?\168524\1011741lM\1095332\DC1Y\42522\78297\&4\STX\SUBn\ETXsGD\991578\r\1080713\1023773$\171\&1d\62022p.\US\ETB\SUB\FS`o\DEL\EOTnX\53121c*owi=&\158540\182939\b\1016827\r\t\SI6pTy\RS\GSk\1036061-O\SOH0\29622P\150571X\1015616\1078661c\1053453\&6Oi9\1119ua\5870F\77833S\38185NtB!KUf\152878\&1\1093703}\1026958Q#m\ETX>Z?E\SI,Nmc\n\US\1094741\&3\DC3\f\DC3`\SYNt\SUB<[\b\ETX8\151074h\1003990,\NUL\99442'5\SOHC\92301#\40443\ACK*Y\72211m" } @@ -168,7 +168,7 @@ testObject_DeleteService_provider_19 :: DeleteService testObject_DeleteService_provider_19 = DeleteService { deleteServicePassword = - PlainTextPassword + plainTextPassword6Unsafe ">\ACK\137107\SYN0P\44360\172737C\1082549pv\1097196/\ETX\187668L\96910\186315O|i\DEL\ETB\SO0\RSL\58627\999623\USvv\DC3Ka\1076973.>\GS?\1109724_\\\1011475I\62581\94753\1080472\&06pm`l\1041148\13550\&0\1010473\EM^\12453\ESC\f\EMB4g\r\1098984\18005\1107485\n5^f\ETX&G\23020T\n\FS\NAK\1039529<|\ESCw\n\154667G\US|f\DLE|5\DLE\1882$\1074936\1090991`^\140786\rA*~R.\22974*Q$Z*I\1039592^\162938K\57774C\1070778ZF\1060972\17798)\DC46\ENQ\SI0\1007803ze\9764\44341\184077\1080223\ETBj0\1111883yx\ESC\EOT|VW|\nQs\53595]N\1083127h\1042851\DC4G\1000718T3I%n\1017404qER\1002839A\148023\162227A\33101a(\ETXP|\t^\78794u\1097203\DC1\EOT\a\SYN\CAN\b\16644Y\986968pn-\SOH#\998434\984832&\1017292\&9G#\SI\EM\"$K\ETBgg6e\ETX!vQ\DEL\159252\DC2C*\SI?~\STX\1007689`Ue\"\1059830\USkh&9BZUuVl2\RS`\1025452\ACKxtOiU#{%x[\v1@\STXa&\RS\168275\ACK~PXM\154154\CAN*}&\1062787wt\1019634\1016421\EOTVT-o#\DLE\FSnw\SYN\1013453r'/u\DLE\15428#\DC2\31268\SOH\f6\14115akz\FSF;wq+\54251[\ESC<\174919(E&+eOh\DC3h\1028793T\24665\ETB\46823\ENQf\1109264w1+b\1108914s\"\33736E\1019264<\988069j\1071489t?\48682u\STX\ETX\US9/>\1081043\994356\DELT/\a\174484\1014268\1032749yI\49343JS\SI\\_\1043925\&1W\DC3:\48547h\25736\nZ\SUB\f\54942)No\1020941x\184463H\1106526v\78215\164362\&1;\RS\34983\160864.\1058424*\SUB\174437\1560TA\1112917^s3\1038351[\1053260\r5J\1049652\n\61845\157334>,[`h?-W\1013026\NAK\a\ACK\"\1004020WM\26663\18741\&5X\1092442W^KY\f\1049222\161658\&4d'\54786\DLEU\ETXQCbQ\33992\&3ci\\\aj\132464X\EMV9\100888t*7JF\1046979R>\136\30675\127315?f\164663\&4\ACK\1071913\993153\SUBjY(#r\1005229o/Yi\1041186l\bX90G\178671W\NAKe\1036212\983560Jk\"\USmW\GSz?+S[RM;FI\189237\CANT(\EOT\b`c\1005148q3B1r\182228)N\EOTy|Vsjb|\94530h\1034532g|w$5\139841\&8?QE\26569d78\1034073{n2lJZ\US6\DC1=|r\1055494\1044748\ESC\DC3x!w&\1113410\54516l\1023594\988578\nMR\1083559!N\62887\1030136\1009971\&6J5\183632;\64651\100866\GS\1008941\35467\74968\CANe\GS>\1013377\1100280w4\nI\57731r6\21870]FN\NULW\167503*U\1036724\987806\r_w\fl\27837\EOT\ENQ\4427\FS\33006\153667\4617m\170844L\b\DLE\46159\a\GS\1068707#3\GS\7624X6\DELP\10065\DC1o]F\1003847G(_9\ACK\a6\ETXP\DC4[i\1031304_A\1079639{\47811\&2+fn\\pyx\"B\DC4\1007948;9,\38686\t\ACKo\142161'BV\1033757\26211M\1050397N\DEL\146663\92331\1041718'\148612\1061104hf~2:\DC2" } @@ -176,6 +176,6 @@ testObject_DeleteService_provider_20 :: DeleteService testObject_DeleteService_provider_20 = DeleteService { deleteServicePassword = - PlainTextPassword + plainTextPassword6Unsafe "\46501M0\128273%rM\1066180\&9\ETBC\SO=\FS\n1\ETB4\ENQ\"E\"FUnG\994814!b\68765\1011902\1107231\5233\f\163953eO\39323C9o\167833a\1226\151640~\1075567HEg\1096875\&2?!+a\1035907\10212\1020711n\1045468#%|m\993099\a\92674\&8:[\DEL\US\1085710@qD1/\\O\DELG!\33361\1000772%w\"\1100656&\SUBE\DC3;w]\1028104hz\1082600MN.\1056702\1087194 \183582m\CAN!2;\rAH:9#\78634\&1%\1112704?6\1039413\SUB\GSC\SOH\n\DC3oj\NULh\t\1032478[BH\1075969a\1036438:m;OKj\SOHz,<\36751R\\\a\1006373\1083019Z'z\996413\1094670\DC3V\RS\193yq]G\999388\187945\11204\NUL\23900\&0-\v\1085533\66631\SOH\DELf\18953\17727f/\DLET\ETB\1073706.x\190003xR&\1051834\DC3Db*|\172679d\"fHx.x\f\1052427!\vv\172092\SYNlSC4 #\167493\139910\&2j=\ACKE8\bjc\NULC3`\1028591#\3507\1062196\1059545MTJ\1057868[:r\SOH3c\DLE\r\171193|M5a\DEL\vCn\17756^\1062889\DLEyc7\"\DC2T#\"ms\rM\1020351N&\NAK\1088713CS\SYN\185705\ENQ\DELbe;Z\47369=yS|f\ESCA\DC3\FSF4mv\1088644\NULN6u\ETB|\\ \ETB\38645\48401(u\97780N2\1016220\&5\1025214\NUL\"=L$`)" } diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/DeleteUser_user.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/DeleteUser_user.hs index 44f0b442e6..9dd5673cb3 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/DeleteUser_user.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/DeleteUser_user.hs @@ -17,7 +17,7 @@ module Test.Wire.API.Golden.Generated.DeleteUser_user where -import Data.Misc (PlainTextPassword (PlainTextPassword)) +import Data.Misc (plainTextPassword6Unsafe) import Imports (Maybe (Just, Nothing)) import Wire.API.User (DeleteUser (..)) @@ -26,7 +26,7 @@ testObject_DeleteUser_user_1 = DeleteUser { deleteUserPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe ",{\10918\DC3+N\990428vR@\FS9!\32962\1095967\132743\1031785r\50784\165924\EOTI\"\DC3\5340B\11369\167798c`|\\y\n\ETB\DC3\DEL\SO\49188<\166120/\1083390<\NUL\NULKb\47635|}gR\1104392{Co\EOT\rs\f\n*\1092907Z\36101\46995\t'\ENQ*\1008372#\SO\1081986\&9\97636\1026187}Y\1038579\1003574E\96986\1054955\157626R\157990\1077770\1103915\&2\35474\USPz\989153\62209\100578dJ\28235p$\181726\147587\ACKCK>e\1025093.V\SYNZ\1012000$\991977_\\32\SO\1024367<\1085422\&9\180308\9418\DLE\45088\43788\&0\SOHl\580%\93829R\32321P\1103673\49967\1034072s\1051699\148059\DEL#v'\31252\b.O4+q\1089294\SOH4\\\44923\1057096:\19335\1108450h\1058358\149044j\1069371:\170426S\SO\22009&?|k\f\1022867\110853\1059952?B\63878,\ENQ\60405`\n\28583$A!\DEL\1106269\67412\1015597\1084387hP\995722g.\SUBT%\15767\ENQ\DEL \1049040\1107594\SYN\135063[\b3m\NAKx\1038301R\38226\1092951;o\NAK\GS1/\DC3'\USkJ\aj\EOT\1052888p\EMs0N%\USE\1003193g.\1049874\1070891\SYN\1074653Nh\DC3z@STxV\b\15058\ESC\1081717nn\1084652Jo\166132\1023132\1040173\183342N6W\1103253{O%\aB\1090554\DC4\NUL\SYNU:=Ju6x+c\SUB\990611\SOHq4=\132456\1041804\996777\60658\191155Q\CAN\ETXJPZ\20619\ENQ,yhT\986649O\1014584\97898 aa\1102985]\FSby\43830\RS\SYN\1074854\1091939\&3+fF\178234\EM/\aro.A\1058743\1098278\RS\SOt~\28232\&1\1062085\&6\"\ETX :\DC3\1058912\NAK]\19200/3\180229Eh\986202[^\r\1051464>\39271\EOT\50522:)\DC2(\SI3\ETB &I\EM1\1080871\1064005\1110201}n\1090723\1093617>\DC3I\DLE\DC2T\27705\910\6692OC\\.cO\9674\150822L)`3\149695\54201\13587\&7a\44602\&6IZP\f=xb:{\DC4V\1100330\FS\ETB-\ENQ[\26182#~u\EOT\1063480\SI\SUBy\DLE\43293a@V\DELt\10635\SO\25539H7\1091349\83252\1016899._\136128\EOT\1000253\&2R\fM^\b\96127S\153652\b\SUB)'\SYN\FST\ESC>\EOT\r\DEL!\44242F \166444{I\GSu~\1060284o\1023532\CAN\FSm\1093003\EM\1045923d\1087374\993670\10888p\DC3\NAK#\DLE2\156504H\48798\171349R\1012790\SO\DLE&\r\51683V\54264\DC1\133076bv^\SYN\17875[\FSR\72263\&2)+5'|ra=z\18561\1041523It%\SOU\1030747\1899\1023446\n{\DC4\ACK&\DLE\15439\DEL=q\1063403\151712H\69696AcdG\DC3$u\1016581\18473'\fb\ENQ\29426I\1082097#L.\tU\1011873\138795`?0\a\991182\b?\1089514yUi\tF\1050163\1056400\1095998\186269\ESC\166099S\983951ofMG\1031514n\CAN\29054{h-\1097025 Y\\\DC1k5LV/\9229a1\172378hp/.\ENQ=\34753\DEL\DC1#\EM!\1110491\FS4rle\1092960\13860t+DR%bt\33229\a#:\54127<~c\vG\b\1076777\&7\SI\1052277>\SUBO\1036320\1014954\1051971Q2MvZh\1087761e \1021372\1070638@r?\1022986\30038\SOb\ETBD\SOHtz[\171743\1004458\151595\1105567\1049173>\50845\FSdIw\ESCVNNHC\180827\158161\1022859\n\FS\ACKSg\vYl4O\SOH/lh\SO\1008051\187397s}\1094202cBj.6(}I\v\b\1003162\DC2U+*\1094516\74868kN\SUB\bc\DC4u6y/\1103688\SUB5fb@|\FSB$F\tB\ETB\vKT\1024335\nK\n[)I\1088045\1065263\3490N:Q3N\14782\&5\1092440\1042791\12796g\RS0P\1092304\FSeJI\137378\1003363 h\FS\158388\1060823\DC1|:3L=\118968\ACK`t?\SO[\DC4g~\1006662vDt\SUB\27163:\EM\1065374\148984Kb&\DC4\ACK\FSW]K\\4\NULbh\1104358|\NULD\986616b?\SO\CAN\vs\131943%>/\STX[d\987777M\1033221bN6.\1109554m\US\153271|?yp=" ) } @@ -46,7 +46,7 @@ testObject_DeleteUser_user_3 = DeleteUser { deleteUserPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "x\DC1M\EOT*J\316\142802otr\SO=\1040043p\EOT9\CANl,KZV\1067142t\DLE\1012713@X\NAK1mO\DC4\n\31224\EMh~r\529|\99161Rw\EMZ(ox2_r\1049820\f.\1079432\1049985/-m\995255\983683\SYNL\EM\1011184\ACK\1028373A\143399\&8H\1004049J\SYNN.\NAKk$\179892\49480uX \1036176\DC1'\1037726?a\SO8\tHWh=+~\SUB\SOH>\156021(\FS\b\144391%&\ETX:)\121442\n\163728\US8" ) } @@ -56,7 +56,7 @@ testObject_DeleteUser_user_4 = DeleteUser { deleteUserPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\28257>\\\EOT\ETB\t\SUBG\SOu\SIRc\SO\1032332\1069664`fx\b\\WS%u\184695\ESCq\SYN\ETB{\1043200u\132227\&2J+\991190wPx^,:@\DC2\1111400; @qtte/\SUB\12658xK-PebR3Z@xm\54615S\1039691\1040385\&15\r\131131\27782\&4|\68779~\NAK\SICdK\72290w\ETXEk\1024874\69726\1021311\DLECe<\a3tJ\")\1023039DFS\ESCVz\DEL\26766RrC\24731>M?&98\t\DLE/sd\44269\t\1113861\1052288T\1082236@$\b(\DC1\1063735nQ\1049015_s\DLE\1095686\&1,\a%cQs\t\vjv\149281!\SOH)\989176ySU5\SUBZ\DC4\51597\&6C\t\1016478\NUL\SUB\143627,:\bn<\23254__%\146727\v~\1003989\EMp\\L-\168147\1042687P,\34232\SI6\r6g\DC1\139418'M\1066187\DLE!\1113788\41035\163185\STXu\SUB\STX1\45563\DC1g\DC1C\128936X\"\DLE\167200\1063332e7\4661\r\16176&ID\992936YiI\172121}X1)\SOH>\1105083\NAKO;-\23857,L\aH\US(46\1044861=\ACKm<\42689{\b\43552\FSj\48581\RSRO\EM|7\DC1z\1032807\1022631MPuVS\1075970Vj\DELHL \99898^\191138\avJV\1009008\f6\ETXq\135703\11910?\NAK\1014331a\SI\1090088C: A\1109438\&7PC\DC2\1029802|\US\152550#B\EM\SOcx\1012392CDbe\SYN\US{\RS\SOH&q\EOTo\SOH\DC4F2\190380!\GS2yw\1097641\\)\tI\1003495(\SYN gC\996904O\t@_8\DC2\1035771\187633C-\SOH1|\1076623\53026\189624*\78577\DLE\1043155u\67731\CAN\15755\100476{r\SUB/O\167439\20396w*\48236s\25454~e\174154\DC1\SUBZ\DC1\tLq\19823\1041916V\DC2xp$C9\DC3o\121009\25470Re@ay\1025116{I|fB\r\DLE\ETXwp.\1088198\&1\ng\43494O\EOT AgX;\292\176868\987367\179002\&4\"\1046707-DCk]>\US\991342\998426=&\ENQ\38601I\rj*h~5}UO\1064900]A\167783\53106v`bG\b\182180'S=\1042350\bs\16914j\162395!%\180962\29997\&0\177524F\1017620\ENQ\127319H\34913\b\163469z\SI\1095125\70736EF\1010473Iwi\47514\US~oyMX\1005192\DLE\172447^`j\120793\65733/\168644\984564+OEOOR\133982\157536\&9\152283\&5\SUBv\61619\37860Hk|\396TPlgJ9\SUB\97181P\\b\168136\US\1107942N4\6767C]g\DC4\1054708\&6gcrJu\1079214z\SYN_\RS[\73457\&8i*D0\DC1g_\83327\&8H\1035462J\FS\1001497DK`\1035153i\1060\&0\14549Ee\ETXI\EOTh\986134\DLED/\1033010BuBXU_\95252\1049573\ENQ3q\SO\SOC/\66673\1004193\DC3j\1047533'o\158072\169750\127292\29164\FSd65\159707\&5@\1073064\NAKY\168362\a\1049190^D\CANBqb\152736qiAn@\SUB\1026419$\t(\"\ENQ\\ )/Ox\v\1040720^@<\152925\DLE\166372Tf\989295B\18798\172322L[\EOT\39991\ESCp\t2wk\28126<\1006625\1072395\18465\\\tIIS1\EM? x\1010946n\r/VH\11847-ss\DC4hJt^\STX$\174465ZW\1042455\94980\NAK\a\141027\ETX)\DC2&5&\175072iR\142955\\\DC3L\166552KQ].\1059101\US\26940\ENQ\156315Xs\14425<\CAN" ) } @@ -69,7 +69,7 @@ testObject_DeleteUser_user_6 = DeleteUser { deleteUserPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "J\164818\v\FS\58220\SOH\184812\v\133910\187607\SI\62253Xs`\132379\"\153527>\SO\188116\STX=\83173\FS\DC2\ESC3=>$o ^\30925w#Z\t\1095929D\155852\1002231'\US\27008\995756\51434$\1078087)X8\r;\DC3\12910\CAN1/\"\1067520\SYNf\US&g\120000\STX92\FS~a|@b$\"\t\3923O:iL'\131552\21103,y,:@\51458\ETXZ:p\157887un\US\rWL{TE\37942\160731\1091064\59155v\b4=\1102393\CAN\ACK7Qt5^\17474\DC2\83216>\28812]xX\SOH]\97240,\1032106W\v\1031401\175059\DC3\SI\ryV*S\US0n\ETBa\27904\190942m\1016789$\1072622k\SO2\SOH\168765>\37214\NUL4_KFpk\142656\nW\SUB2\39922\172793-v:<[\24199x\1061848\996738\SI\DC4z\SI\v\29363wG\SOH\GSK,U+'5#};o\13440\10224B\SUBG'\187069W\1104874$I2>\1106768\160795\135857O\1050864\991910\57347\a\CAN\ETX\48757O9/U\47600t\1004486jQci#\DEL\1086435i\1017512\nirLiE>W\1091429=ri![\t\SOdMq\135635Eg\a6\989151\ENQ\6204W\184988~\27197\SYN[\131476\NAK/&A\25479\FSo\tSh}j\98237u]\GS\\%\n\1066402\1001053\97379\fYZ$1'mf\54620J\1023186v\b\1063392D\65349]~\SO9\ESC0\US!G\DC4+\CANn\11315\1082274>Ap_\95735\59843Zq_v Y\1067992\ENQx|\SI\151387\1091488`G\ACKnA<&K|?z\169630\EOT\4561Gs\1059564\179816\DC45wY\b0^\1044201\NAK\188342f\48790\ETB\ETX^$\SIR\23704|G\DLE*/\1041058%G\983974IEN\1002073YB\USL=\CAN9\1056515\EMk8K9_(IGM\DLE\25558\1088619{r\EOT\152795\185010\997406\&8\EOT\ETX'X\133574\RS\DC1&/:\ft\FS\r\SI\1009729$\6436Ux\DLEB\175019\13871@\1036594/#Y\123184N\191398!\190307\&6\f\SI!_#P*C?\1101946o%T\ENQ\EM\1055906\1022305\&4%.\ETX(66Pl\49159\EOTg\SOb\ESC\1071261;\997978{\FS\RSxz1@KQzs\a36;,\40583fi\45696Z9\99723\NUL" ) } @@ -79,7 +79,7 @@ testObject_DeleteUser_user_7 = DeleteUser { deleteUserPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "-Y#\ESC\8768\100736\61306\DC1\SYN-\1082323\DEL\ESCF\1088616\DEL2B\DLE\175192S\1017727oHa!\998363 gO\132282\v*{\39036*\144312'\DC1V[wm\RSvcAc\ETBj\a7\999516\1065935\CANr,d/\\8\1005307\&1C\132790\EOT\ACK\194565)\SO)O[f\1057692\&0&r3\178360a\1032155:|diTt5\1108760~\58247[\aO[{\ESCng\1030208\1086820\29646^Z\STX!Beh\1089966p?\a])\US\SUBu\986388\NAK\1009827\1079660v\ETB\1068559a\ACKN_{$\186771\49376F\65033p\191328A\\\1104167F;)e1E<\997253e\1034433K\1042458\996650x\n\v\41580ZS9\CAN\1038191%\1093684\DELi }\169917VJF<\ESC\178204\1097154\&3Y?$|W\1078228>\49906\990375\1065456b\vT\1102903\&40\1084741\10788QA\SOHE\v/\NAK\1039693\156492\USc\156071\ESC-\12890>`\SI+r\92253&\SYNl\aMZU);+\t\1005643\158045<\1077172y]Q\1019706J\SOhk\8555\SUB\n\DLE\996513\37991\&9\RS0~]\201D\ESCP<\1055077\DEL\135536\EMn\ETB\997253zR\v\1064754.-\US\1045483\ENQ/\SUB\119962\DC3\1008898[\9549+\1080191OS\989349\EM\n\DC4\ETB\123146T*\SYN\1072104\n\15960Yr\DEL\SUB_\FS\RSG{]Rg\24269\152334\32142E\SUB\DLE\996289\1028922\1014150|'\DEL\STX\ENQ-X0\FSqTS),\135645\27548\CAN.2\1016261\986530\195085Rz\DLEtV{\11980\32971J\1007370d_OgT\167641>C(\78522\135941\181939Xr5\aK\SOH\94944\73955,\46440\1110129T\DC4A\1002880\&1\1005136\b\1078353\CAN\188916\1050246U\STX#cz.\SUB\14258\146463\v\1016088\40212\DC2Oc\3604iO\a3B\1052387\1082309\164508\&9\1036725r\SUB[\NAK1\DC1{\1036529J7\1050723\ETB\1064669\1023944\DC3\ETX\163427*v\51586\1094918\v\190919&N\1039743\12127\992063B%2/\EM\15189\&7\149005\SOH]\180237Z\1023499IM\63096q\DC3y%\1007697\171535}\SOHds`\FS(\ETB\SIOX3C?\ACK\NUL\15552]\f\1038145LsTu\DLE)\STXU\DC2a\32523\132875/\r |\135613NB\STX|jW\SUB\nAx8/%L\1016547\1041040a\1035331f\NULm\23518\EM\GS'\SI\1082779\&2y|6\ACK;\EM\GSO\b\SIAq\15495\SOH=\1079959\1068730\132485q3_z\FS\CANN\1014783\144014z\26644\&7(s\DLE@\1030914epd\160891[\CANU\STX\39913\SYNB\175383/\1092672\DC2l\54264[m!\DLE>U\RS\148494\18469~\1000635\ESC~I\r)\160426\175322W\22908\52144!gS\4928\&7\DC2''\70812fg[]\f9$\1047326\v(\b\b~_\1082662v&\tA(\113719[H\173008!\987785@H(2i=`&Np\137001N\49236u\53359\DC3\1043638\DC1\roGt\134438\DC3P(" ) } @@ -89,7 +89,7 @@ testObject_DeleteUser_user_8 = DeleteUser { deleteUserPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\"\25995n\nt\ACK36>w-,38BR\CAN\\tApY\38941\EOT#\34073\f\EOTIy1\1092356c\1109760\68848\1040093\1056234\1050372\v\999446\ESCF\19676\1002121x\133776\162378\SOH#\1010744RQ\DC1\10089\ESC;eb\EOT\58983Tk,\154609\"hN\ETB\1020046\15999_\172130s\1034886\GS\1069446$\ACK\996097\&56\1069180\1023110=pFL\996693\EOT1\999027\189450q'\SUBo0\NAKkYJ`oC(j\n\SO\1075770<\ESCCd\1112434K\1081452\33189odp\31545\&4!HblpH|g\1084667$\1013094+#\97343sU\991504J\994858\146926<\994057\1020567\NAKX`=U\1069344;@\SO\CAN::\DC4\ACKH\4586NU\1057487\&0<\\\1008119F^!p|\DEL\7771W\RSh\FS5i\1055567GT\1008019F4\1089491/\171185\fxu\1070423\EM\1079430cHB\DC1yB\CAN%I\1013436<\1108955\1088025y\1068546*p^\ENQi\a\95854\CANs&W<\1050891eTS\1076082\176194S\DC2\1075553\57741\DC3\35842\&6\176750|\179007\DC2\1008082\1036657xUt:\191333\1053992\&6Cf/\FS\SO\ETB\41780{3uu*yU\SI]Ja\r9\992821\a\51100?\133538\&2\STX \aZ\DC2\FS%o=\1080665D\\oKR}\63376\19548%l2\FS\1090700\SI$u4T\RS/\DC3\187985\72791~\rI\1037313\996830O\r\EM)\NUL,BBa<\1000190\60458\994383yS/2[\134720\1023984\FSc\SUB+=A \38319\1112593\SUB\ENQ\121427}^\15560c*e4\183382\ESCH\69976|mIqg\1033937\95948ka\1068017\132160\CANdVIb\STX" ) } @@ -99,7 +99,7 @@ testObject_DeleteUser_user_9 = DeleteUser { deleteUserPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "RWx,BvC\995412RH?\1071958':1:!\tef-Z\1070126&3|\SI\147238J\1002103\ESCn`\SUB+\fk8<2.p\135835B\78427an\ESC\1110170o\152222\&7_\1002074\1034151x\NUL\139716\NAK\FS\DC3S$ \DC3\1026719mv:tA}\SI\v\NAK5Y\65338\r\53917\RS\133773E@N\1070500\188131S\139428\155037r\1046699\1101345O\ETX\r<\ETX \v\GS\DEL\1075054\1018431X\a@Uz\vm\1108256\NUL\2585\1093848\1053685QM\100635\b\1042948f\129607y+\187307f\1082020X\132099N\1024065\&7=(H[\74429#9j\1008273Tx\DEL\r\987200\1075169b\141631c+LT:\DLEpXTce\CAN\1110769h#\DC3\DC2\1040771\t(65\DC2\54640{Gudp:{xrBR\190136Gx\143683hIt=$;nI\STX}eR!\SO-S\ACKUxc0\SYNA\3997g\1007392\5088F\STX\STXrr\1073345\1089585\NULZ\173660\EM\137272?t\190044\1061225\&7\16014Ji\131904\&1\61154\SOH\DLEt\thf4\ESCe(\DC3\RSm4\DC2ji\68181\1025631e\FS96\v7|\186815\162340\1105613!\20207\&2\10079\1025357:s8{\1079566c0\ETX\DC3}\NAKDR\51929sXh\1061611\n(Q\184570y=\FS1cW\153936M)}]rd\STX\10335\&182\DC3\NAK\\wtjm!\1045227\DC1\995589\a\1005862|W\NULp\158697\1055375\SUB\EOTz&\183304ZhHC\\\SO\1053403\r\150308\SIg4\CAN\NULL\1020178\&4\1051594LZjpCm\ENQ\1086359\STX\f\\\22501\&5#\132667.uC\SOH\NULG\38852.*u\27778\EOT\1082062@\t\988230\144605a\40767T\f\STX\134115@\ETXm\1070794\NUL\1001344a\r]\50684\&2\1001759\DC3Y0~bn\42786\GSM\CAN\SI\a\bQ#w\EOTg\147818\NUL \49308\DC4\\.[P\FS\1040392\CANR/\RS\\A5\n+\SYNN\14443\&1\DC3\ESC\136755\135263\fk\15419~M\183377\1057023d|*\155494#\31829&K\SI:3sS/3\EOT\DLE\1065709\ESC\1068641\131772\SI\SOZBX\SOHH\1102066\22169\ESC\155249b\1085981hxn\59233D\ESCh~ql-\14173f\150424#1Cu\1030999\1026708r\NUL\17377\1047000u\1072279\&3\NAKN\ETX\DC1\171981;U9,\43282\24608\FSF\995303D\a\168430\17758v\DC2\13750#\DC1x'\1072246\145863p\EMIh\1034083eu\nM\ENQb\1087842L\ETX1\1014483\US\ACK\SI]\1090316\CAN\152984\RSz\ETX\DEL\blq90L\NAK'\1113186)\v\1096725\16854\121127\29559\1020319\1061557'\1022793\SOH%\121002\1080217\"\1113760e\39611d\DLE\83492}(%H/2\44296A)\187645 er!\DLEf{l\n\97338&\ESC\59000svY\1039978f=\1107506ErdT\984170\&8._\986943\ACK\"h>Z<\\qe6D(\DC4-+\ACK!\147217j,U\ETBRCg\162281J\1049541\ETX>4\184738\NUL1|;\NUL\1094707\1035418\135287\"\1095549E|)=#w\1057555\ETBF\US\1022792\NUL\1013280T_v|n\DC20\44358a\996521yt_\ACKu!@\111250\1063895\&6\DC4\DC2\147418\&0:\DC1Ltr\ESCVI\1066739\DC4\GSkNtg\1039402q\181066Mp\32815\DC1\US\131636\1042732\&6\SYNZ \60502\DEL;W\1008353\NULW\134502\157983\US\ESCw\FS\US\US$\1079616\ETBBmYR\v\1014879\SI\149886\1101224\SOHwxd\EM\150211(\DC1f\160388)h@)\1068367\1051571r\DC3Y\ETB.\1063944\182476cW4\1020293^'\23551x\ENQ9\34085s8nkWn\1069738!\1091418\1044228\132147\&3M\129413e2h\SI\DC3%lDg\1028671\1076060\1027036M\EM{\1053261-)%f[*\\\DC3\b\151759$\SOY \a\USM'*\1013024\f\t\DC3\SUB\DC4J\97713\1090375V\ESC4\142654e\1038512\SOw|:x\175478\f\th:!\1027351,C\156891Ky\97788\137726\996012]pIy4\1079291\DEL\157756@\DLEr\1088430|\ESCu[\6775\DLEmu)~" ) } @@ -109,7 +109,7 @@ testObject_DeleteUser_user_10 = DeleteUser { deleteUserPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "DEf\995953\DLEO\1112387IF\1018888W\"\v\\\b;]zM\1071377\23128 3w\14364y\NUL\166060r\17371\159152\ETB[$\29768)(\US\SOH\96988v~6;z%1\183138\EMVDq\DEL2D\r}d\16158b\NAKp\f+\984171 \EOT\DEL1o\ESC\20207@x\EOT\167702!\11015]E\1008380J\ESCEq|^B\129550\61851\1072452\983216.\1086245\&7Dc\129309Z\1056350]U8\151615-,\CANw\22557\EOT|T\SYN\140434Ci\SO\156914\NAK\a;\n\1032235f)9LAZ\155620\96218q\t\27953\92933\168287\1038355J<=x\NAKk3<\1016443=)a\183320o\20799\&7\EM{E\185083V2m\NUL\SI)\74133<\1010040mi\135346e~b\ETX4;d\1096421\95938+l\138421~\1025050)\47887\1081073Kf\t~\US\23339Y@.\1064116/fj\49708\SO\64163u\1018983\&3\1045609c\1054361\48727\ETX\"N\NUL2^\ETB\CAN\\/\100156y93\128261[\CAN\1082771x)\v\ETBB^\120659\16424\&1>x 6\1046680KaR\184097\187114z;\998769tM\50200I\110614\1009560JGg\1089145\187776cV\f\SUB,\58179_\17448}^\t\1015257!.\STX\1052843n\1064280KX\51846\1011892\NAKW\131231B<%\f\1023380iS\1045577\DC4fnxrDb.`~\SUB^O(6uQ+\16635\ACKk`\190906\ACK\1072121O+9h\re\59428\1101905\1009879ctL\ACK2\DELBo\1094964\42112op\1031461\53721\166324\1002985\24412/E|\7040\SYN\110686\97200\985916\1104539'\ESC\185159}\ESC\DLE\99031v5~\1033967I\DC3\DC4b\ETBiP\tqT &\132812\DLEm\14002\CANt)%W\1076667\STX\178617\1035532Fl\GS5#\v\RSj\FS\1092024\DC2B?Wp[G \121082#\n\1084063\NAKaUjX\1087016\1693jj\DC3\1110721M\148439\DC4a\178171\ESC\ETX*\DELtaY\ESC\1027240f\DC4\1006985\1073426\DC2:PE3.\1087786\1052705\ESC\14084\1092444AP\au\173705W#\EMOQQu\NULF|GaQb\66840\51966\ETB&\1006478\150264\SOHHcHY53(Vle\EOT{\26139qCI\1079105$O\STX\131616\75044\119356\96109Z~3\1011926\125020\n5\DC2\144751\156510'N\RS1d\194845\52133\ACK%iK4G\1026970:\1097961!\SYN`(MB\STX\169393-#\1093100\STX\28181VYN`3\USH\45562q0CwG\14050d\111086\ESCY<\176362&\1101927J\GS\24020C\r.^y\1083812B\ACKp\171855|\51383Y\DC4X\24580WJ\"\1102730\59282~\DC2\SO/V\DC4\STXg\GS\45130\74894G\1056487gc^P5\986447\1031127Gm[\\G\CANJ\ETX6~=\ENQjjv<\991743m\1111141|LP\1049188\69405\31149A'C\1342V\1053084\146906\&1x\DC1\GS\DC1J\996270\996444\EMD2\165583\1104454(?a)AQ|@\1101918\STX*\USS\30346)}\128551\&3i#\EOT(%x\28699\5808<\1040802\"\ACKQt\1015182 ~\bkN\190923\ACKv(\DC1\41416\68000\987488\DC4\168029'\1035733q2!\1097279\998967\DC3\FS\128095\1102863j\63443\64552C\rY?{*\155215f\23392\CAN\1005922\&0>\ETX\SYN\SUBT)~I.\1629\185876\&3\1083175\DC4{\1068268%[P\146642|TXL\b\ENQ[\\\ETX9\"\n\t\984727\1021548\6411{C\97205\SUBI\t\16636\GS\29107\171775^c1`prF\31303hQ`e\1049776\SI\NULWB\NUL\STXA\SYN\156568\1103684)\ENQx\DEL[;\FSV\SOH\DC44p@sTVt$2\1067401'IS=Ft?\993905\1110778R\172997~\179596\65912[5\983209\"\b\STXVu\1020553&\47669" ) } @@ -119,7 +119,7 @@ testObject_DeleteUser_user_11 = DeleteUser { deleteUserPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\DEL\160093JIT+Rij\94505f\157645\DC1\DC2{\NULH#X3\62524\1072672\26964i\194786)D\1069923Ld;IBx!;c\162434\1046550\&5 y9\ETXj\1042272t\995387J`s}\ETX\DEL<\1074500~I91\EOTY~D@\EM\1012059\45710\186458\171549]~\134098\994559:\USe\171722!+\DC3\146161u\SOH\bEsr\1063176o?{1f\1111788Hg9>\1086451C\95010\ESCU4\119588\&6\3761Lma]\NUL*\\\NULrlx\tu\188812r)\34486Z~|\r\168153\RS\"\62554" ) } @@ -152,21 +152,21 @@ testObject_DeleteUser_user_15 = DeleteUser { deleteUserPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "`~\1085300\74381\STX\NAK;6\1110284\144222iG\14576\&1i\1097267\53723\fK\2408\142796t\bU\36160]V\ETX\ACKZL\ESC\43147\993663U;\NUL\1090656\DEL\DELY\1095589 V8Q/\ENQB\ACKW7\3724\1004393Jy\163842'\DLEg\v\1026388FV\DC1\1106448\DEL\168341-\1059077t\DC1o\46938BfB\ESC\t\ESCy\ESC\1003163e\1087305\r!+C3\1025711\1058620\1069646\14425B\174788i\DLE\SI\1113758\1027853\v\UShr\151546\1036074H\DC1\SYNBq\"\165134QV\RS9w\EM\fv%ak\b$1n~Rd\FSZ\999395/grxh*}\59067\1050682J\"\ETBe\30800@E\52868wz\NUL\1024827nB:ul\984780K/\1108795]\ENQc/\1026300\RS*\138552\169061wMe}Vu\ETX\v7j\1090936d\DC3\1091241?\146365\bN#A\FS%=aVy/\ETB\135572\1061670\SOH\1069435\176679V\1110548Z)\140187xz ~ia<<)]\1092774\&8D\SUBmJ1\143318rP\DEL/cxb\DC2\ACKB\vF1\1057079\100791/Dwx\149379\189467~U\1037291.r\155147\SOH\ACKuC'\SI{\1032000W\1023722X \33614\ESC\54105S6\SI\SYN\ETX~\986150\5026\n\1065178-Jq\1038225\tj7rI\59344 AfuG\f\EOT+\984152\&7w\125197\146671O\65737ZsK?\1041859e>|>Q\1011732B\35718LL\986208\&6v\r\23230H\1079873\NAK\SUBS~\ETX%&KH\SUB" ) } testObject_DeleteUser_user_16 :: DeleteUser testObject_DeleteUser_user_16 = - DeleteUser {deleteUserPassword = Just (PlainTextPassword "Dzq\12497s\DC3\176472\nUA\SOu\fO\64025\&4X_")} + DeleteUser {deleteUserPassword = Just (plainTextPassword6Unsafe "Dzq\12497s\DC3\176472\nUA\SOu\fO\64025\&4X_")} testObject_DeleteUser_user_17 :: DeleteUser testObject_DeleteUser_user_17 = DeleteUser { deleteUserPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "]\189881\&0;\21905\&8\1102345\ENQ\1072235\EOT\1031047\156181qG]\189335\&75\32161\&5\1076501mtr\SO\997648i\1015251,4\70725#5-q\SUB\1059901m \173612k\1039894S~F}/s`\ACK\DC1^i1R\ACK\984136\NUL&\1107283\SOH\180348\156061\1087810\1084250\137873\&5,n(\1030794`Dl!\1074758\NUL\f-\1088486[\140404zr\"=\"}\14052\b\12832\9758|\DEL\64369\139051{Z)^8\DLE\CAN\SOH\162567\EM\1102003T\72232M\172847k~A@PW\1076896\STX\SOH^2y{\EOT\v\DC23hG\aq\SYNY\FSH1\US\1068326&.\28278W?`6\DC2J}\ACKCv7\14936J\STXE~R\a:\FS\bI\SUBimv\ESC\176638x_-S\164125\";G%\US\1089117\137663\GS\ETB,D\54436\40275\&4\38545\US1F\DC1kgo/\31748#\DC1\64524\73002x\175276\53831o'pxg&d\b\1099762\181122zk\994395-\f\US\SI\f\141843\1013008\21725\&3:\1047905y\154041g\NUL\170529pR|\136104\aoH\ETB\45475#\STXa7\baQZ\1022685\SI\STX>TQQ,z\1107531\&2/\SYN\ENQ#'lS\NUL\f\aC9W\34152\f\t\SYN\ar\35059O7)\FS[E\169287X[\154757[\bumB\SIf\SYNG\t\990961[nkl\DEL\GS\1048567>o\171664\26118\1028464\1043159J\15442x0\1100565\997895\DLE\1038551x\1045803d:Id4\n\ACKJ\11576Y\1030703\96279cR\ESCr\tmv\NAK%h;FZ0\30360>4\SI,\166028\179771+Sa\CAN5\GS\SOG\1004157\1018324kC\n\ETBrV\NAK1:\60993\1005021\188875Q\173038\1102019\STX\166768\7726J\60090\f\EOTDd\1090665i\998643\EMVAv\1067083N\\F\1063488eR\15138kOj\140038e\128981\65029\US-\RS@\DEL\165589\1057690C\"\GS\\\1000607\997545\1079177)Hs<=\1054988O\135934\28549d\NAKY\n=|&Rz^#\DC1\EMY+?E2#\DC4\ENQ5\FS\SYN#\ETX\993389\ACK\rj9%\ESCv.\ESCK6K\NULT\1033898wToLS\40177(\NUL\1044037|\30327\CANJ\35219#\7776~\1000924\STXd-z6\1013014\5996G\171672\DC4J\rQ\171233B^?A.@\987892" ) } @@ -186,7 +186,7 @@ testObject_DeleteUser_user_19 = DeleteUser { deleteUserPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\FSHAC#-gZ\CAN)3\ENQ\35169dM\NAKZ\ACKMt\22966%5\1059774l)R_\1021311Z@\137057}\fV\1073183Pvy\US=i\\\1006321\EM\DC1\"pe\39927^:\a\US\EOTh\190319\1097763\GS]Y&\ETBwZW\n!m2SYb\165378OCW\rq+\100202h)\16110\34963p_~2p\26693\53392\b\92300\v{9Wn\54261{\NUL7/Qn\21885^K\DC3\ETBCP!+\1007021!PfV~HT\1082228GN\64860\11361^sSR\172656)\30249 \1010886\10209XlA\1097013p\1059263\181879L\f\39754)3!R\ESCH-nD\SO}KT88P\SO\ACK\RSE\CAN8\ACK%]\72325\158918\1031266G\26036\SYN\155599\142308\1071584\EOT)\DC1\1056995KyP\ETBF\FS{;)(\SUB\74099iml\\G\984976;:\1019785\NAK\71307aku\bt\SOH\25350e\1035562X2!~\167816_*\1093728T\n\185843\49627\US\STX=\1013155%\49994Ui\RS{\1010490\10312\986114)\987880\CAN\FS\29461p\122886\60694\984199ae;;S\18626OIumd!\DELo\DC1\34900\1044339\17656\143765\CAN;\147302B]\1086550r\r\1091034\3546FM\ETXvl&7\ACK:~^\DLE\a\1104972u\1010729\US\1048737g5\1109511\34793\61795\USR|\SI\1018493Z\vN0$W\1050773+qN?\SI\164700 s\1041827n\1091643>x\DC2\40934EA(CL]XZ2G6\168684\71683\1000797|9>{\ETBD\996538\1109355\28862:\41417\n\181162\&4\178375jon\v\1083748\1006948\b\a\SO\1070168e\157331by-7\1099262\DC3\STX\46217-%\1037353\CANL\\\48652A\DC4E\SO$r\30347ajn_\14532Hn{n\147691\1091725\CANP\f\a\1057510\1093951\1300VH@v\1000846*\ETB\EMbB\67871N\ff}G\CAN_\190086bt\v\NUL\SUBHZkT^s9\5757\38046$Ai{o\NUL\1031710?\1020061R\1072184\r`\10708\180291'$)c0;\US\190443Q\176915\&5hI\61737\32248\DC4q\ta\135573\1054497f\5133\1047548\vIc1+X\EM-b%z\13146\ru\52404\&7h\ETXvc\51035}a\1069669\&8\RS$YXL\51100\24975G\1050080\DC2n\FS\987904V\1058564\SYN\fEF\ACK,g#K\ETX\DC1\NUL~2\b\1080410\&3\172935\n>D\99869E\RS#\1024688\1064967`xj\ETBM\DEL\DC1\41241B\FSQ(\f\vW \NAK\180409i\SO\CANs\984900(/mjS\1070763W(\1061553{a\DC2\1018032b/t\1107796*miP{=@\3993.o7\132550\DLEvO\1066216\DC4\NAK\7587/H9^.Sof=\46317\134970\&3i\43237'\nVI>1y\RS\15606\182177/ie%Yb\53218{9\DC2P\DC4W\au\"A\1019169~-\1090930\1097077Z\1066476\96842\1005524c\40482g\1009344%\146114k\1027651wL\142425J+(m4I`Z$n\1043635/rm\ETX`@`6|\EOTeH\b\1104196\147523\&4\62247\NUL4\1050156RG\fl3\992511\1112108{1)_m\RS\1037055j>5\EOT{vm>FM\992125\1066248N\992179\1095805\n\DC2t\SOH\USk\178143?ZM\v\53249g7A\ETX\1097802\b\1044365\DC2\ETB\121264\1111453\1064396\29722C\DC3h\1094932\DC4ag^#\168543\995729iE\27864\t\DLE>er\US/C\1086286x\DC1\1015244}\EM\ENQdk\152744Cv\1037501\1028365\1036484\NAK\ENQS!S?7\1020740\167324\ETB\142555\1090736]5\1064738\SOH\61858!\ETB@\SUBu4I\146324X\129593K\184981&\2217Oa'\f\37854\27808\31568p\NUL.{E\SUBO\GSNW\168663\53158 \1001507\&0\7882\SO+*\EM/\1011367\SOH]6" ) } @@ -196,7 +196,7 @@ testObject_DeleteUser_user_20 = DeleteUser { deleteUserPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "4i&\SUB\n\173868,\171993\1039350\1026684\"\990080\r)\1085158X^`Xv\190792\STX\991685UN\n\1023916tQ\1042234h\16663\&4\ENQ\SUB\bj\1010588Y\DC1\GS\EM\64743\GSX(m\33189\175828cXz\fEx*M\SYNL6;*\1001951\188126\1087528\RSD\61798}\1031791Ywk\210V(jsn\1066362KS\1004751\"\28906\47519\190071\\\r\992724\DC3v\46565C\DC2l)BxF\148222\1091231\SYN!>x?\1078350\1048680aj\5633X\1030175t\132787\156320C+iz\t\1113889I\\\1026350O{\ENQ\1008267\1070869&\16283sYx\1032981\DC1\182212m\ESCzg\73780l\21318:\a?xav\ETX\1002956~\SI\vs\1060884\1034204V\DLE:\STX\2149d\ETBoJa\14654\1095521\1079157\&2\1026557-\SUB\51869\2671J\ETX\FS\EOTi\DC4\63614lo]`\\\f\SI8[dK\1083834\&4\DELz\1003491\1024311\1067559\31725\1020277\181003\179474\ETB\DC3\\o\1032076\SUB\DC4.\151401oTs\1061082n\US\\\32545\\z\1000536Dl\13249\65070q%iW\1086344(#\193EoypfS\48303\1027584\36379S\184984b9$\1080140\\\f%\DLEVc\v]q!SSQm\NULC\ETB\EM}\SOH[uN?\150961#\SUB\1030157o)]VqA\SI\1083167\fBtO}t\1083206v\41336X\DEL>\NAK\1026675P\180353d\1020631#\97378\USf\131892\141248C{H&]\989317#5exl\1083095&n?\SOH\n>&T}4\21155#L\GSr%o\SOH/\1009860\&2ID8\DC1'kog\\mw\SIe\ETB\1064993\ETX\DC4^O\176040~t0\EOT&<\DC4#ym3\188511;\NUL?r\DC4*\STXE\1089111\995413<\1038436\&7M%*-ekKjyQq (z\1064832\ETB\NAKg\1044642K\"\FSvu\ESC`\t]C\1094587" ) } diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/DisableLegalHoldForUserRequest_team.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/DisableLegalHoldForUserRequest_team.hs index 6e4ff531a5..23f41b3ea0 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/DisableLegalHoldForUserRequest_team.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/DisableLegalHoldForUserRequest_team.hs @@ -17,7 +17,7 @@ module Test.Wire.API.Golden.Generated.DisableLegalHoldForUserRequest_team where -import Data.Misc (PlainTextPassword (PlainTextPassword)) +import Data.Misc (plainTextPassword6Unsafe) import Imports (Maybe (Just, Nothing)) import Wire.API.Team.LegalHold (DisableLegalHoldForUserRequest (..)) @@ -26,21 +26,21 @@ testObject_DisableLegalHoldForUserRequest_team_1 = DisableLegalHoldForUserRequest { dlhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\DLE!u\174573\1086873b5\"\1105219\148890+Fs1\187553\DLE\170725nvK}f\988606s\DC3\STX4\a\RSimc\94846\985128^\13322\1009291t\SUBVj\f\986777m\1078383\v\FS_\1048655\997083$f\92689i9\251<\SIur\1089664rOXm\1062450].x\DC2_-n\v=\SYN\23636y\68460q;#U\FS^\1004706A\50096zCRM\v\1003244U\ACK\ETXp\STX\DC3@\141963^\ETB\ETB2w\1078713\NUL&zzV\21262\1028817J]'\96401A&Q\RS\1044409^M$[\DLE\83160\DC4t\187845\1018026\b_@\186849V\r\r\97250\31907\999596;\DC2d_L\22075jc\\/v\DEL\1060317)K\1082499A&\43366:{\1056321(z\1054346E\1061644^nT\SOH%sY7F6\1050608s=\DLE\160876(\SYNK\6418(\1096676\999373\1105905!\SUBkCg\185749\FS\n\v\"\175586\SUB(er\44639-kN\41922)\162725_\83389\142049\1078689i\r\FS3\SOHH\1109560S\44220?\SUBLjJ\990727\1046419VK\51310\DC1-\SUBi9\GS\DLEYv\169385x~\1098359A\1063931\&8ogom\v4b\1018948\r6(p7\1029564p\ETX%\SYN\SO\28562\1085197\94690\&8ROt\38768\1045101\DC1;]\1043790\1000357\1072046\"\171513\142637\13581My!\FSI\94917 \DC2~Ep\CAN7RWv'zE\EM!\51077*\a\164936\188452_\1040332d\1001615\SOH\42755\38673z\DC2&S\1043620e\26270n`\ETBZ8\34947`SL+\1038406!-X>\1093716\FS;\1002690\ESC\STX%\1023439\SOH=\NAK\EOTfySG\1008698\1107005FC\1026321j\1008009\SOm-q\ENQ\1083396\SYN+\50861\1074767\ESC\1092040\1051848\177416m{OEPX&I\1095111c\ETB<\CAN}.\180848\ACK`>>\1074538{\afV\SOH\985078&X\SUB>E\v\ETB_\1045249Q.D" ) } testObject_DisableLegalHoldForUserRequest_team_2 :: DisableLegalHoldForUserRequest testObject_DisableLegalHoldForUserRequest_team_2 = - DisableLegalHoldForUserRequest {dlhfuPassword = Just (PlainTextPassword "!=\GS'Qt")} + DisableLegalHoldForUserRequest {dlhfuPassword = Just (plainTextPassword6Unsafe "!=\GS'Qt")} testObject_DisableLegalHoldForUserRequest_team_3 :: DisableLegalHoldForUserRequest testObject_DisableLegalHoldForUserRequest_team_3 = DisableLegalHoldForUserRequest { dlhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\ENQ\RSeCX~IR_F\27706I\NUL\DC4\46606V\188243\132133\NUL\27730q\f\183409Z?\"!re\99401,\t2!yH\ETX\156366\1089121\RS\993451j8yo*\STXo\GSZ8\10023\1028711\1027008\42302e\165633bdM\1036139\1012811ZL\FS\bI.\EOTo\DC2\1057540s\999320$hR\96265UU\44822+\178407$CMlQ/# \35974'rr^\n2>9\ETX\1107536T\185327`\DLE\FS\60360\10720N\"\25719y!\33486\&8i0d\187913\ETBU\EM\SUBCpu\95454\SO7:\41830Il\1008470\1113558\58806\100816O\10802V7Cx*\1040240\45044z\ETX\48112,q\1070740+\96524\1029355*)4Y\8528x/\DC2PO\vP!\SUB3Cr9\1110059\20514b9\DC3k\ENQ:rg\EOT!t/\169687\3642H\1030883J\65612A\1042135\48756$fO8a\1078298R\1058275\"qP\1101802\146003gB\1007872c\NAK\ETB^7}2\SUB\43048\FS?\1078821\DC3M\f\1070731\&6T\ETXaiO#\DLE\136093\SI\DEL:\139871j'(}\STX\SIZnkY^\NAK=f\7255\NUL\1030663z\1021288h\54147\&3\1091029\"0P\46909s\1031298.G\CAN\ESC\183505\140701~\CAN\1069410=\1033738\1086445Ho\DEL\DEL\1083573\&3\DLE@tSWBH\1051121PKv\1023834,f\189861f\FS\1079877\144743'" ) } @@ -50,7 +50,7 @@ testObject_DisableLegalHoldForUserRequest_team_4 = DisableLegalHoldForUserRequest { dlhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "TALD1A\26863Cp({Fx\78046\&1\SO\136200\66327gx\19800B\178841\ESC|yg\1082304s\140747\"\28351e=N6a\1012391\42044?\1063559Ge\US\50025Tp/\42306\1046529x\1052628.\DEL\US\1076433\120078\1034606\1007712[\10680b\DC4\1076862e+eE\143357W|\135513T\167573n\n\"4KI`\1096511k\NUL\136345\168857\1010595\141395\62296\&0U^H\1043176\SI\45210\1107681\1007248 0\42509*\r<*u_qU\US\1030212o\v/\t\46963\&65B\1039768\STX\74526ODY\tx\96142a\1101638\EOT/g4~D\SI\1036440#H\129481\NAK9\ETX\DC2}:\DC1\44859j\DC1C.\SUB$\25957\1012147|O;\1074253r/\136772'\ENQ-y\ESC(g\12401\f4C\67360\35949\NUL\FS[4\ESC\STX\5416\SYN\143136\145462\1074248m\DLE\146902>P\b\1056160\&1y\1048386\1047245r2\a\149582\SYNR\RS\1073435\1077517W\DC4\v\DC3\30698\DC2^iU4\83218p`\129083s\146484\996891\&5\3393N\v4k\a\SOH\FST\1043340\1016106a\NAK_Kt\1035338\ETX\993166#0^\r}a\1081372\NAKQ]]\NAK\SYNO'\997141Z\984824\168059=\DC3_+o\SOH\RS|,\GS{2eA\181062\FS_\RSd\1107443\&7\t\1057953\"Xt\177221\EM=\ACK\DC2/\SUB\189573W\19401\1030800\991697rW1\61452\16130\100486\137066\ACK\NAKh\14381\50852)\SOH\1032247Loq(\1041127/\EOTl\NULa\140559\&3m6/hm\ACK\NULKB\UST')uhM]\SOv^\1086243P\ETB'FEje\157206'T\NUL\ETX\CANh\1012689\&0\1004010\&1\r\1063805$\NAK\992626bz#QwC\"\1000344\1021042\SOHoe\SUB)^r?h\EOT\ETBryb\1051679\&7Hr`+\1102731\CAN6\n\STXXh9\r6{}\1042168\35690CSEX\152014(\154126\SIE\RS}\a\CAN\1025953\ETB\ENQc \STX7\NULa.H\t\7254Y\n#I\190965\SOk\20757\22865H\1005453-/4\ACKqu\SI\1020280(Vu%/aOk\DC2)r\DC4&v\35227n$P#XsN\187026JJ\DC289a\fE\SUB)c5\1076475 ^S[Xy\f\DLEO\183830\STX-\983705\63922\DC2E3\1091200x.58CF0\1092374\nW\31681\147367\24664\1039879\b7G\DC3jt\167374\1089244;fH\ETX=\1109425\ETB\1005605\EMd&S\DEL\70130\&4Zo!Of%\ESC\137125\988445\ESCQ\1019984\v\SOH\1048443tq*\ETBZ\v\1062650\&8\DC1\b\1074421\&7d\NAK\61936ye\21328\166246GT\34576\20172UGZ7\SYNw\13836\1000822p\139519N\NAK\1112355\DC1S\188328\CANB\189929hnp\GS[(\b9\100737*\"\184490K)=\DC2*N\1002031cxO |\ETX r\1047031>\51123R*\1027232\986211}OB\v\US\51534Z,\178499f>o\ENQu\1009340n\NAK\1058787\1013193]O0Z{\ACKPPr\STX|F`\989299\1067107" ) } @@ -60,7 +60,7 @@ testObject_DisableLegalHoldForUserRequest_team_5 = DisableLegalHoldForUserRequest { dlhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\137156,F2W\33586\ETX\1055743\f\FS\1103290e;\1070540|R\1008632_vuS\CAN\32906\1062476}\t\DC4\vx e?L\FSN|]\1043861\r\173436jU{i\1112471p\1029796\96436\NUL2#:F\161345Q\n-\1002592\US$ro\DEL\174685\1079227\1050297\"+\4056\&4D\156623(\4692\47896[\73447.P/k5L-Z\FS\ETX\169470\ETX\1109152\&0\990199v\986964\1021120u\54721o787U!\119553\aazr7X8\CAN,\b\GS\DC4Y3\STX\SOH]T\n\1021771\&4\29791.)\ACK\24435\t\ACK~\1044420dh!Gr\1096271\1099393\1065094@\1082309\FS\a=\177710EG\139822\\Gb%\EOT\1032116t\988067/\1057076\34326=Q~\30529*!5\1073270\DC1N%i\GS[\189726\53459\SO\SOHm\133273a6\45951\EM\63546#e!cy,\US\160425\EOT\SOH}\154254L|1\ENQ\1093965\1068402V\1017932\994173I\143306\CAN\135685w\RSG2R:\1071789\STX=s,b=$\1093971\CANC@\26088\189707[K*\a\1051879\54615@\SUB\US\175284\191110$\ACK\a\996381\94721\55108\1028051oKNX\NUL\1082090hwB\1081878\11490\t\1000729<\1069096\&5\1105099\FSa\141634 G\121352\1069615VuJ\121135c\GS:,\ESCZ\1001141:4\5244vA\SOH\EM\97726\DC4\64445\DC4\1030130\1092884o\nY\SOH~<\119888\n\999238}k2\1026114\ETXeBs\994225\179391\70848\DC2\DEL\28166\DLEJ\DC1\SYN/A\1070073Y9m\DC2jh\f\t*pbP\f#hIO$>4NW)m\DC3!\ESC\nu8_\1003189n\SUB~\RSw'tv@\DLE\1042553+\168767(e\ESCeC\NULt]\12721x\STX\SIt\RS^2\983583\1058945\40503,x\GS\1080792\DC4\SI\a(\166635\DC1JPH\1049791\va!$9\1036144uvi;yI)\1088740!\149408\144821\GS\SUB\1783\n]}\NAK,Sc[y\DC2cKWe\53738d\SUB\ACK1\1024362\br{\68366\1062491\1012706\&0|L\1112363\ENQ\DEL \DC2\1068869\151784\DEL}\SO\GS\180805yn+E\138680E?$JdA\f\f\99947lc}n\GS\3392x]~K\DLE\NAK\120612h\NUL\161575}z!\SIkg\41868\1000386a\RS7=\1027374\188926Rf:\987405t\f\1013877N\DC2/9\DC4^m[T,6\1060563\NAKM\1074631\190293\1044836\1049145E0\17200\&6\150809Y\1098882PGW\179545aQ\SI&\NAKaJxF\DELJI,fkq\141562\US\1039152gE^\1882\1028959L.Yc\48647\n\68384\&1b? 5zq\62455m\CANdQ\DEL\992427\US'.x\1061261\1028692\11504" ) } @@ -70,7 +70,7 @@ testObject_DisableLegalHoldForUserRequest_team_6 = DisableLegalHoldForUserRequest { dlhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "R\DC3\a{\36991\1113773\b+Y:7s\DLE\48789\NAK{\RS\DEL\DC3\DELK\1099092R>\DLE6\34536\43654@\1065633\ACKt*\152992?&u\NAKD\1045185V\159414VT\GS^:M\47321oP$j!_\1049153w\STX\v1G\CAN=\187600q\ESCl<\1019880E\166849\996644\DC2\a(\CAN_\185267T3\1062339\1075933\EMJ\132488\&9\20338uW\vE\r\SUBm\v&rHX\148348\1106565\NAK\t\NULr\DC1qi\DC4X\166695\ENQw-\n\67752)\"\SO\ETB{'m\FS\1083327*\STXI\1093959\n9m[\999240\rXHM6\GSlb\92712=C\120377\1042814_A`Q\SUBXZ%\1002753ip\33183\SO\163617z@\vu>I\188831M W\n$q\13694" ) } @@ -83,7 +83,7 @@ testObject_DisableLegalHoldForUserRequest_team_8 = DisableLegalHoldForUserRequest { dlhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\RS3(X*M\1074473R1\FS\\\1078886\n\1048977 \nS\1087271\ETBn&\1052667\1109789>\ETX}\b+\135281\1035307\ESC\RS\162808\134313]u\SUB\1015059GRc\1058375\FS\SO+(L\1023704\19425V\151937\vc.\EOTnc\1055529\173862\1075343R\ETXGwH6G\1084470M^Z\fPb\DC2>\RS!1D\1045468S] \160151J\DC1H0\v\1078393\19536\a\1008981\STXU)V&\ETBg\"Z)i;\98530\166448\177881hr\NAK\15526m\r/S\SOH0\1025932;\78251X\SYN\72990g\n]Z\168630\SYN*[B\162742\ACK\USCK\DEL]\GS\EMn:t?Y\SITAH\62120P>\158348\113729\SI \DLEyy\994985*\fH^u\ETXfLdT*\1086034uL\14732;\1107038G\EOT\46567\DEL\bowOh\EM\NAKJ\NAK&\v\DC44j\1097986\1034128d'_\167699\1026702\32970!7h\63748B\995849\44685~Ui\FSK\988721Y\992591\USu\RS6lN\STXS\1075504\123585\&9?V\DC2\f\bK\30130\1056261\19537\DEL\129389\NAK83\rG\ETB\td\1021815-unxx&;$qE\1046247>\"\1053222\1007082<)8\1002599\1056609>\f\18973S]\66333\11217Z\64575Gj\20617\DC4C4_\1037272] i^\20388-\NUL\986665s\1003453\DC4mBQ\1058400\128363\38166\161835\&4V6\999664\&8\1038552\GSh\1105403\156942\23486\27291{5\163315\USK\DC4&\59703~\SUB\ETX}\"|T\1108332R|\168758c\1064049.\r@\1109116\9796\NAK\1072775\&8O:\FS\a\158887\995546r\FS\1023196!,k\DEL\r\152323\992729y\126490A.\137635_Q\DC3F\DC2&=\167397u\1044832\134784\ACKG\161722\146474\DC3ra\70791L\STX\DLE\1080645\42378/\38012.,\58445\70455@\DC4\158717n\"BJ\39450\ACKx\1047052im\1033933\nZZE6P*(d\b\CAN\1107216bd\tY\10150R\152660\SUB6V \ACKp\78680\14872Do\DC31\US\9674Tm\DC2\\D\994127%j\34324@ya\b\NAK&\1028278\SYN\DC4\152108\1025885B'@d\USW\SUB=m\rb\DELW1\33772Y\185176\50621i'UUp/\1071799lce\44834^\5695]Z\vW|ki^\ETB\78396ao~B;\CAN\DC2v%9eK\50571v)\1068396\&3&\20929Vl\5353,m\EM\36252r\SOH\1055578z/\ESCU\CAN\1071814o\ACKfv\GS\STX+\118788\34417\1077962'\f\110636|m^\ACK\140182\988509\43913\DC4|=\ACK\RSIo\NUL\NAK:v~'$F\NULAN\SOHs \CAN6\ETBdU\20450\97568\DLE1o\SUB\415vLZX-v\bdL\SO\ETB\1008160\US?\ETX>.\38753\97737\ETXG]\a\\\NAK\170879j#g0\1086688\1010321Q\DEL-J\156417\b'\50042\59953\GS\990157F\1016713j5\DC4\EOT\ENQ\ACK\STX\57867\STX\143717\EM\1004501\997969}\23649\72293\FSgb6W#\12414\STXdN\ETX\37832Z(\FS\SI{l\1008931\136310N\DC3\1071835\SYN-\DLEL-IN\1051293\13368W\DEL\1029064\FS\1098268Ztu[\1107734D\1114040\RS\1079148]g\63318\ETBf\78471\167180[\1079945@\1098815:6P\aNZ5Z\119190\&3Mh^\SO\1097591\SOBK\121063\40187:M\n<9Kl\1079461r\1035096q|\125187d$3\r\DC24oYn\n|1\DC4\SUB\97315\97875\55151\f\53760\&5\SUB\160887h9Fw2A33M\146138\NUL?i\34707x\DC2\1106739\999490\EMN\f\171047\DC4`KKW<\99728r)" ) } @@ -93,7 +93,7 @@ testObject_DisableLegalHoldForUserRequest_team_9 = DisableLegalHoldForUserRequest { dlhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\1056977\&29C{tq3M\al\DEL\vI\1050648~\b\1110813\fX\1053943\992978z2\1005194[aN\144114\132416\a\\$Dt\33925\1039812\f[.\ETB;\49045\&5|H\1047643\b2\1047407\&2\ENQnj6\NAK\134942Ll\1060357\132861w*\1094502\ACK\nz@\vxKY*k\37224b>\DELf\1042108\DC4\NUL,\SOH\SYN=kLke\SO\1007612\167493\74451\rT\1100885v\1020168/|\1056324\1092502\\\a\66566K\n{_\DC1\DC4N\1047702}\ENQ]\t\1062283\15527I\CAN/%\170414Nn\148830\DC4\138203\1033396\96781\1099838\NAK\SUB(\SYNk\1041267\1012693\1099151V-/Y+=F6\v~\98724(\nk\4552<\SO\ENQ\1084826R0~\NULca?u]\1089777)\"\EMikt\83423$>\1080793\&9TWfRW9\96810\f=\1020621\65458\156823\1112768Fj3%#dITe=/hJpn\DLEmm \DC1-\52411:\US\1098767~ \119134;\172201y\STX\ESC-Sbzm@" ) } @@ -103,7 +103,7 @@ testObject_DisableLegalHoldForUserRequest_team_10 = DisableLegalHoldForUserRequest { dlhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\\4;\1078108/\1103094#\SUBdsMNM$k~Z\1071658@9\1101446\DLE\NUL\ETXL\NAK\DC4\a$\28818\SI:}L\1091922\1089789N&\991946bbi\SI\177077Z\1015361=)>\1101192I:nS:j\\\15427\SI\DLE~W\CANR)\CAN:\DC3y\ENQcRMR6\1075969\1095092\ESC-E\1047142B\1001169\1099672\142679\1039237\SIIK\100128c0\1100211|;b*^Cu\SUB\1081858-6\1103229H\"\28775\988619\31091hi\v\b}\1106253!%Q(,7_\20137\1062440Q\988571\STX\101072UM\154199\f\1004185\a\n\STX,oy5\"\NUL)8\1077175\1049411\165040\917936xE>\ENQr\13644\GS\157841\b\31806>vg^qq\1074681\ENQ}W\1110906\18244\RS\1055709\ETB\a\"\SI\47932\ETXH~N\152108\SUB\153079\21090\1057755\b;\127364}\95325\a\35559M\1108532\&2\1104\22056l)0\120563\"\DC1KksK9!\29718\a\153386H\DLE\152298\&9?\69659F\STX\SYNHbN+C\119187W\1000860rq\8044) \54184ikgd\175981\1073095`\\\US8#/\ACK\rq\380X6\148564\DC1\1091319\1015371\t3\RS/r\v/\SO\48134\59330\RS\151261\154333`m\SOg\143711`\STX\34862@sM\147293\EM\151217a\41021~Q6V\168544\32889<\NAK\tm\43998\tD\GS\STXag\1074761f\ENQ\68634\125017<\n\146025\DC4;\1061082\83432\NULn\t\1097366LsR>e\988793\135335 ~\SO\1030430v\NAK\DEL\178087OtG)\FSG$S(L\1100630RGo\NULCpS\150271k\1045488\1019829\SO4JQ\190598`E\DC4\"UhU0^\1025713\SO\SYNX\994907DL\GS3\US~>\ETXua\144563BE\b\SI\1001296$ p\RS\21010\f\99998\1062429en+K%\\0hj\NAKn\138181l.\CAN H#(\1022229e \t*c\SO\127997\STX\1098269S\f\t@K$\1078353\1076083X\1056802my\SYN-S!;GD\127033\&7\DELmR\1104557\1098490W\1040598VO9\r\DC3W\1036971dUy\DC27\FS 3\1087328\60153~\EOT\1018898\CAN\53239v\95021_\54882\&4\SOV(\987344\r_~c\1005253\1113740U\985754>\SYNZI@\48631\v=\985902w\SO\1009838\\r\136510TB\\O\USf\986493.\RS\1100447F\1005935\&8\1037045c\1025048@\92756re\140406`RWT4\988878=(\98167\NAK\1110832\&6/B\1045666\60927xB\7768\SUBAn\v\1111483\19769\152888\v\DC20x\SYNa\1040386qn)A\SUB|.\1068783J\29094\&6=F/%\ENQl\DC4\ESC\DC3J\35848\ENQ^\1022626\&7\1113259\EOT\ESCv\1057558\DLE\1104752#\94953Y\3853Q\1044460\n>\156688\DC19\1059825X\NAKM\ESC\993337\SO?|& Hc\DLE\STX\USAAo9\1109375\1045975`\156816\140018[\1112640\nF:H\STX'\78616d4yil\1094789\185864Y'_\51222\174383y7BE\1111649q&F\1080462\169052\ESCk\DC11X+\1032511n\993300M\1038503D*&C\vU;^\1076930\f\f\ETX\27063O\\kjc\1107560T_tly\1106435P\SYNpvy\1005812\STX,\1105495GNJ\f-SZ[\162880\RS\NAK\1042444Q\990602\127142\25966\&9+]\67204\21408'\vl9N\129072r\4945y\n\ETB\36970\1030380)v\b\1084728b.\139932'-ed O/\64787\143241\NUL\GSi7\t@Z\1060820\1078335\1010737q\ETB$\35888M\4715\59678;\148678\ETX\STX0/\74368/\ty\1985L\1106904\41667#K6 \1000136\RS\1002888b.\1025132kR\991621d\SI\137399\180738\DC1\1091604\"~\a\SI4fF" ) } @@ -133,7 +133,7 @@ testObject_DisableLegalHoldForUserRequest_team_13 = DisableLegalHoldForUserRequest { dlhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "8\STX\1072451\1068903\rX\"ckM\bnF&r\nHyH8\NAK\RSuCX\f\1098900Th\176254W=\EM\tr\47778J2\EM\32809\1066932d\SI~`<<\136247\DLE@h`iC\EOT\155795B\1047490|C\1113109l55q\ENQ\22461\\h\n+cA~\DC1Pm!\DC3\SYN\RS\1004033\&7\139016\EM\1013347}xp;\993766|\1084406s41J<\bn\137605\DLEa\DC1v\fr\1063531\r\DC1PmvcY\1074985X\991896(\1041471Q`;\\ye}>\1043971p)_z\a\STX@D+\35422`\59457\1102491Z\1102224,\"M\73896\188577AU,\ESC\EM\f\1044963\73984k\1053138\&8P\17188\b\ETX+\FSb|}:\47630\9538\&2'iI]\EOTM\158518\64763~\1021113\&0F\SOH\1011207;\EM\f\GSfU>\USL\DC3>POLEgRw[,c\v\ETB\DLE;\1089061Z\18954\997051CS\989339_|\40969\156078R\185997\44024\r\998588v3\ACKZ\DC1w6j0\38546\EM<\RS\SUBlf\SI\v\SUB\1017303\17012\ETX,>\ACKzkR\96169\NUL\ENQ\1090031\NUL\DC3\49207\&9S\1095916\&3nMf\1057825X|\DC4\DELW\SO&R\137307\ENQ1\1101838#\134114\a`V/\SOHzcAG@\ETB\162068^u\985108\FS\STX~\SOH\GS\ENQ\1035648BF\CANX\ENQJ\DC4\33692\f\DLE\ENQ\"\149392(\1090435\166525\EOTdYVr\133518\37203iwV \988297~\vF\"v.BEs:5\63267O\b\52067\&5.b\"+l\1027902=@\1029276 \52481eEM)S\1057107u\CANz\EOT\CAN\RS2#\SI\993596Y3\ESC\1029064\"a>V\172124A\v\60971\1070697\40037<^\48448\CAN,\15308n(\1112112\SUB\12655Ui\100590\RS\1036823NY_e]b\RS{]\ACK%x*\ETB\NAK\99143\SOi/e\"\1028480!\1065618\1041290\138807\DLE3\145476js\1047064\&0\rf\DEL\NAK)\te\4351|iyx&S\136766\SYN#\1041655Ew\24962\DC2\STX%\EOT\US/\20946hhl\tJ]EQRat\991368\DC17\DC1\SI\SYN\1041701r\11798," ) } @@ -146,7 +146,7 @@ testObject_DisableLegalHoldForUserRequest_team_15 = DisableLegalHoldForUserRequest { dlhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "2<\SUB\1061776#l\SYN\35861GSU+\SO\&HC8\1064048\RSA\1031118\f)C\149283Rv\68899;]\tsXg|\DC3\DC2~Zh\1081015]\167443 ,,\f\EOTt3D\1104249\SOH$\SOb\1047735/V\SOHp\143836\\\CAN\156414\&1\16086\989969q.bi\1043439\1106000\&8\148292[\1112480`I4\rClvXoVMa \1087202p\STX \171992t=\f>s\8518AU\CAN*\31231\DC2\baF\1003280\150410\&3M\990082\987645\DC1\GS$T}c,\1107255X)\SI\1065692\CAN:\5877r\1081341b+\194980\SO\ACKpC\29633\1085602dN\EOTX1V.v,5$\1059118\160899A\DELHL\n`\96699<=K\DC4L\1043275?H`Y\DC4n\1066659`[[\1022413x\43429E\1014860U\DC4N?fa@\DC14\GSw\78020\GSlT#Z\1014396{x\1064270y\\" ) } @@ -156,7 +156,7 @@ testObject_DisableLegalHoldForUserRequest_team_16 = DisableLegalHoldForUserRequest { dlhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "5mts\RSeJD\101017G\1024610\n\NUL;'\FSk\34030\SUB|#\SI\GS#qX\ACK\EOT\NUL\DLE\NAK\1011167\DLE[\1011378\44435\\\1020908\152906\1074669G\SI0\SUB\1041529\DEL\NUL\166513aa^l\ACK\28321\1076288\1105880\FS\EOT\1056619pw\CAN\173918\1102487\990138x\DLEu_\n\ACK\1071387\&8k\\G]\ETX\vVt,\SYN_\ETB\98461^wfT\997979,\1110299\ENQ\1080110\1043260h!-\96990\"\SO\13020L`\1034814\EOT\CAN9L]8+\1107381\&0\DC2>sCt+E=G\US\17009\fyl\992384z\1093413\&9\135901Ej=aWY\b\31402\1029152\DC4\GS\te|\149427\15370S\SO\1096262\ETX]24\26535\33402\ESC'Qht\51661\SInn\187843\8756\1054743\1042651%$`Z-{\SOH\3947\NAK\"p\SYN\DC1w\1040486\&9\1019040 \1094916V\28987\SYN?MR\ACK|\1107795Sg\DC3Ow)6\b\EOT\1022947\189348:_=\19751\125055_q\t6\19939\ETX.=\1068056IG\1017728u\rD\DC34\r\191262\1107229\&2\1007977l>\DLE\1006415\ETX\US\1114088B\SI|\GS\1055334T\vc\EOT\38761O\190864X2\f\\/\9864Q\SOHx\SO\1097786\&6\DC4;U\DLE>6Okl\1017177\1043467/\96766#uJ}W\1078196\151020j\61019\173214N-2\1032852}\178363t\FSN\1113158w\1055630\ETXL\DC4 \1063714\110826q\5364\GS\179869.\1012209f\NAK9\SO\NUL\58867\6843.[\1100631P[,\162401C\1016288\GS^a\1021533\NULrV!\176693J\1027041\13756\\L@\1070259\73024Q`\1024251(~5\ETB\53665\1065448\ETBG)l\1073126%a5\1075104\NAKA\1050996}\SIO\160160\r\34976{Y4\71046E\32630x}{v\CAN\92963k$TD/FGz\1113720-Jn\RSl\1029807nR\1068766ibM\FS\SOH\99576\SYN:0\\\1001646}\142554z\DC2j\71478\1101868!\DEL}R^R,\a\STX(\146575\n~\\\nq\133444u\b\f\US\ETBv!MY/077TuVp\1107001>,\a=\26180,\20646^P=h\DLE\SYNDk\1051814\178060VV\FS\175662\1042605K\SO\SYN{\1011889=\fJC\DC3bz\183805\GS\1022762Oic+.k\1046343\1060705%`nJ\151244\v8\GS\DC2,\r\187845\1087729\1041782v\f\119090V\ETX]\1006050\RS44q] ?),\ACK\v\994821\NUL7\1042803U\f>w*mp] 5XZ\t\1092996(\11587\174895\1071933`\917919E\1113749\1046416\110681\190695i\FSiqq\68901\SYN\998474\170919UO\ENQ\DC3|prf\EMx\EMmy\f\138615\&1Rh\EM\158371\&5)\83341*%\1044313\NUL\187352l\157350\1049402~Bq7\185563[\1024646\174601\GS\1081326W\1067218\1099979&++\148827ds\SYNR\bq`>\DC3Wi3\43625%a\ENQ\DC3\1006501\DC4G\166774\1112661\bI\173028p3\95084S:KMF\191078\ETB\15767|\SUB$\1326e~n\60852,\US\ACK\DLE\1004065wUA\ETBAV9P\1087842\97338\NAKT\135110nFu\EOT-\1000056\&1\FS\ENQEZm\100827f\1055620)P\68613UI\1074217)i@\1018435\\\RS\1075964(\30549z98\49922" ) } @@ -166,7 +166,7 @@ testObject_DisableLegalHoldForUserRequest_team_17 = DisableLegalHoldForUserRequest { dlhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\EOT\ACK:^\GS\16145\51488kx\v\136233w1b-&a\1041767^\USu\1031079\ESC8&A\DEL\EOT\FS~-\DC19x\ETB\1068510{\SYN3{h\1103543\1007249\ACK{p!>\ESC=\1054365\1094989z\1060045S\1079063\184671\CAN\1012446|\40305\SYN\"@\48150\a\ACK,l{\40848\RSp\SYN\GS\ENQ\1012392o\1069269&\GS0M3\f\RSd_<\NAKq\148933WX\a.W'Q\146044\ETXQha+\n7f\ENQ\ETBJz\32395\EM\NAK\RS}\\\159953\1009323\15016\ETB\1039232w,q2+\SO*\1040561C\37833\DC2PZ\\\SYN\144398\54464>\188115)\btf\58069\991411\a\ENQc\fS\64394[\100917R\1005512\&4G8\1092065\96379\\\RSi\172616\992460V\ACK\136370|\172932~\1022809\fN*\1097069\"f\SYNJ\997759\1031828n\184335\NAK\SIb\985082!\US\a\1107336\&9O'q>\ficm\rx\FS'\"\9056)N) \1088088\&2P\US|z=\RS@)Lcq\ETXCh9Zu\5908\125094\177177\129176\1099409\176306\\8\a\GS\1083327\1029683\63381X\984591%\CAN\DLE%,|\r\r|h\1699~\DC3juh\DC3`\ESC\NUL\52668\19543F\160156\98819\1099629\DEL\1107629V\DC3\ETBv\NAK\1073987\&2S%\SUB\1044226)\166168g\119025NR\EM\1029635!\f$\1063919\&5\EOTJ\ESC\153978@\DC2\STXo\1090799K5&\DC3'[1rq\50618i\SUB\f\n(\29827\DC2\1085601c\986587>y\t\NULmz^\127315Z}<`\1026480Y\v\1112322\&1S/\147530=9\994723(\993072&\984034\&6\v\176802\111057&\CANr}\1049784\ETXFR\ENQ\20802\FS3(Oa\SI'\138930\1046230\SYN\tpv\1111250I\ETX|#/R\1054822\142977\ESC\1065166\ETB\21999\&3K\n\143212,\STXS{\1019846\1000394\&2\145106\&8mP|n@x\NUL8v\47297wz\1055217k\EMU0qX,kh1\t5*W\1031841\&2\98390\r&\SOH\SYN\RS\DC1\ETBv$\991824\1088760\ETB" ) } @@ -176,7 +176,7 @@ testObject_DisableLegalHoldForUserRequest_team_18 = DisableLegalHoldForUserRequest { dlhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\173000-~[xoqL\1040831\DC1b+'\STXq\45581hq\CAN:}M^\1029861\ETX,K\ETXo\USF?Nr\150266o\19797\v\DC2\ESC\DLE-\t5`^aT;L\120479B\55145\GS\1080656\f\DLE\NUL\FS\917626\43663p\GS\GS<%\ACK\ENQ6j\33042\92244'&l\STX\1084687\reRXT\1055294Gh2X/\1038276T*\SI\74036\CAN\ETX=;\1108007?3,1\v\999596\78002\aF2s\166542Q\179370\DC2y\28278TP\27413\1042267B\DC2b=4\SO\170992L\1085500\1096041\b\\F\STX;\SI \NAK|\1037094\DLEW\1094736,A\EMA6~xu\ESCb\a\SOHVCNAK@\n\20434\a\1034691V\SYNI\SI2373a:p\DC2x\1002884\&3R$L\SI$At\1011039\27378\12873\24374knA\EMM4\DLEv'L\1070227>d\135444\60848$\1033810\SO\EM\1012596\ESC8}\987721V BSkE(1\SO(\12737B\1077488\136089\1086639\1028470)\1092169p{q!\1065680v\20871]D6}\t1\SOH.^\9370\\JK\118884(@\92436\1083377\38900Al0cR\1082850\1095486\&9\EM\122897\SO\&HX;(\NAK\15523\&4!\DEL6\ENQ\US-:$D\134046\GS\1107538\1099423~\ENQb8\\$P\992410\&3w8\171729B\57460ke\1081609Q\1060206L\ESCuvh\68899#\179929HAFLm\150886\172513\\\100415\149040?e\31840\&5\74530%|D\nj0\DC2\1057115i#%\"c\65666]F^\21063}Q\37991\143650\145971cZ.Z\1065492%\1094684\DC1p\r3V\1089586\ACK\a\1085053X\1061035\989882\1021456\STX\GS,7W2U\EM\ETB\43867\EOT\997836\13216g" ) } @@ -186,7 +186,7 @@ testObject_DisableLegalHoldForUserRequest_team_19 = DisableLegalHoldForUserRequest { dlhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "tW>N\ACK\ENQ\156553P\98989\NAKo\DC4\DC4\480\ETBG\170037@FO\n\186976\164260\&3\37044}JI`y\1059743\1044433`L\\\94716\DC12b\10086\156739S\1112898^\15450:\EM\FS*\NAKxG\1008243\166447E]\1010238M*\STXK\996396\983484\SYNQ\1029209\CAN\178616\NAK;\SON\ENQ\vQj=F\ACK\SYN\24968\1068584\ETB$\1027925\1074414=\32781`\1059900gm.b(UoA\RSj\DLE\135679j\1087100\35340\16783\DLE+p\58667\984919-\NAK\1026178\1100882W\1040375\151533\999602\ETX>\US{aXZ)u6m98\ESC\1103062q" ) } @@ -196,7 +196,7 @@ testObject_DisableLegalHoldForUserRequest_team_20 = DisableLegalHoldForUserRequest { dlhfuPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\SOH\1090411\987719\917546N@\STXeE\1009320\DC2\38423QX\t\EOT\183739\97887\ETX\DC38\992944:i;q\\{#E\\y\NUL\1097460i|\n\SIQ\\\153154\63789? B\98470\STX*\ETB9g\1011682-o\171916\SO\184270d\48655G\1067659,\\9A\159257z4B\1060680l`\5250\20029)\137565h\DC1\RS\RS\DC2A\1027146*\998539H\153749\1013061\1007560yYn\74762.\ENQ\1000052E\1012723G\ACK$T\1068275\53248\996771\DC2Ps'M\1003244\CANP:\1089966\1012070\1011379^~\1081392:tY\155136\1113210\140606\1067806b\ETBi\185038'(l/2\13019\1110791\6066AU\RS}\1016212\175045>\150189cOh\US\1041520)\SI\178205C=\DC4\996492\DC1\f\\EUTr!qiDn\DC3\6560H^z]\1043613sU'\1064297*\1092892\135184\SYN\1092332\\?r4N8\150758\145604/Rm{i\161396\ESC\148391\&2f\9753\EOT\US\1002238v\9484\6456\a\1051412\1000785/S\186057,\163481{8U>\19453\1069252n[B\1086919\1075357#\RS\1073968)T.\bL8\58589\nLwe,5.\15664[!\142190\1059076Fj\b\RSZB0\1047758mA@\US*\151356.\1013324ug7\62644PhnV\133219M\ESCI$=p}\1012053U\78685\185319^a@\EOT+\1094609H`\SYN\137702\51295oT\DC2\1620\173878F\1088796mFIK\143925XY\r\SI\1110775\SI\1070319\&1f\170751L\t0/\1064629]6. \1076067=}\1096293\ETB\1039042\&8K yc\n\25971vB\1069997\EOT9/So)a\1079964\SI\ENQ\1006077\1030029\US(|\72879\153195\77924\1102728:\r%$1\1103287)\995216SV\US2\NUL&}?\156161\&4\ESC\993875\DC1\24448{&V\175250&j\174031|h\a>(\SUBp\999742{a|-\nI\48606!F\1065206Z\185244\1002850\ETXp^S;\1113340\DC1.t!\bk(P\1021089\&1\1069055\1110160u.\SYN\SYN\SI2+4GY0o\b\176875\1070181\SI\63017Y\1098137|\1061114[l\v\151199\1113022#pI\ESCh\988895b\SOH\SO\NAK\159323\&8LSZ*1j\ETX\STXH[\1038899U\9497E4 \38886\ENQO3\1013769\13164\&3\20597\SYN\EM\SUB\ETBDWx\FS\SO\1050546\DC2l6zyh`\1107516<\995287\5048\DLE\1030175jnVnp\1084498<\999998R\1086070\1041154\1003989Qx\47440fh\138713pT\DC32`~f^\182475\DC2\v\a.\1010483\1048085F*,n\DC2\DLEX<\RS6\1093220!\74827H4\SUB\RS\1033283P7\21571\SUB>}@\1261=Y\STX\1077833\992088\&5\fQN@\EOT\ESC\118803\EOT.N\1083134GR\59679nrUjL\ACK_~\997932P\1075694\a\997202\ESCeu\ETX\165207\1074269wU1\SUB\176161\71075\96435<\NAK\EOT\41113\ENQUI\SYN\1111364\a\ENQM\EM/\DLEg\1032925h%avVk*rO\DC2V\181926\1043707\18575\&70\\\ACKz\1023704C\ESC\45244\ETB.ua=\1026257#h\133873mQM<\1092362,f2O*M\10666L\NAK5^7O\GS\SI!\1096512L2hN\1032015\&9\NAK\1081847/\CAN\97854t\135019\1056562\40928\DC2\DEL;C\DC4\EOT\td*\1008203WaX\DEL8fA\93819\174949\nv/Rq\NUL\EM}\ENQ\\\ACK?L\ENQ\1026885?" ) } diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Event_conversation.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Event_conversation.hs index 1370035150..e106a2c0cf 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Event_conversation.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Event_conversation.hs @@ -31,6 +31,7 @@ import GHC.Exts (IsList (fromList)) import Imports import URI.ByteString (Authority (..), Host (..), Query (..), Scheme (..), URIRef (..)) import Wire.API.Conversation (Access (..), MutedStatus (..)) +import Wire.API.Conversation.Code import Wire.API.Conversation.Role import Wire.API.Conversation.Typing import Wire.API.Event.Conversation @@ -76,11 +77,14 @@ testObject_Event_conversation_3 = evtTime = UTCTime {utctDay = ModifiedJulianDay 58119, utctDayTime = 0}, evtData = EdConvCodeUpdate - ( ConversationCode - { conversationKey = Key {asciiKey = unsafeRange "CRdONS7988O2QdyndJs1"}, - conversationCode = Value {asciiValue = unsafeRange "7d6713"}, - conversationUri = Just $ HttpsUrl (URI {uriScheme = Scheme {schemeBS = "https"}, uriAuthority = Just (Authority {authorityUserInfo = Nothing, authorityHost = Host {hostBS = "example.com"}, authorityPort = Nothing}), uriPath = "", uriQuery = Query {queryPairs = []}, uriFragment = Nothing}) - } + ( ConversationCodeInfo + ( ConversationCode + { conversationKey = Key {asciiKey = unsafeRange "CRdONS7988O2QdyndJs1"}, + conversationCode = Value {asciiValue = unsafeRange "7d6713"}, + conversationUri = Just $ HttpsUrl (URI {uriScheme = Scheme {schemeBS = "https"}, uriAuthority = Just (Authority {authorityUserInfo = Nothing, authorityHost = Host {hostBS = "example.com"}, authorityPort = Nothing}), uriPath = "", uriQuery = Query {queryPairs = []}, uriFragment = Nothing}) + } + ) + False ) } diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Event_user.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Event_user.hs index 3e193a3e66..7394b74f5a 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Event_user.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Event_user.hs @@ -29,7 +29,7 @@ import Data.Text.Ascii (validate) import qualified Data.UUID as UUID (fromString) import Imports import Wire.API.Conversation -import Wire.API.Conversation.Code (Key (..), Value (..)) +import Wire.API.Conversation.Code import Wire.API.Conversation.Protocol import Wire.API.Conversation.Role (parseRoleName) import Wire.API.Conversation.Typing @@ -287,11 +287,14 @@ testObject_Event_user_14 = (EdConvCodeUpdate cc) where cc = - ConversationCode - { conversationKey = Key {asciiKey = unsafeRange (fromRight undefined (validate "NEN=eLUWHXclTp=_2Nap"))}, - conversationCode = Value {asciiValue = unsafeRange (fromRight undefined (validate "lLz-9vR8ENum0kI-xWJs"))}, - conversationUri = Nothing - } + ConversationCodeInfo + ( ConversationCode + { conversationKey = Key {asciiKey = unsafeRange (fromRight undefined (validate "NEN=eLUWHXclTp=_2Nap"))}, + conversationCode = Value {asciiValue = unsafeRange (fromRight undefined (validate "lLz-9vR8ENum0kI-xWJs"))}, + conversationUri = Nothing + } + ) + False testObject_Event_user_15 :: Event testObject_Event_user_15 = diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Login_user.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Login_user.hs index 802503a39a..cb3c93848e 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Login_user.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Login_user.hs @@ -19,7 +19,7 @@ module Test.Wire.API.Golden.Generated.Login_user where import Data.Code import Data.Handle (Handle (Handle, fromHandle)) -import Data.Misc (PlainTextPassword (PlainTextPassword)) +import Data.Misc (plainTextPassword6Unsafe) import Data.Range (unsafeRange) import Data.Text.Ascii (AsciiChars (validate)) import Imports (Maybe (Just, Nothing), fromRight, undefined) @@ -31,7 +31,7 @@ testObject_Login_user_1 = PasswordLogin ( PasswordLoginData (LoginByEmail (Email {emailLocal = "4\1069339\vEaP", emailDomain = "\ENQ\n\FS\ESC\997356i03!"})) - ( PlainTextPassword + ( plainTextPassword6Unsafe "\b5Ta\61971\150647\186716fa&\1047748o!ov\SI\1100133i\DC4\ETXY\SOR\991323\1086159Ta^s\ETB\SI[\189068\988899\26508\CAN6\STXp\1069462-9\983823&\NAK\1052068]^\13044;>-Z$Z\NAK\r\1101550a\RS%\NUL:\188721\47674\157548?e]\ETX \142608 C\SOH\SIS%8m\1091987V\147131[\1006262\&6\171610\1011219\164656SX\n%\1061259*>\t+\132427Y\989558\993346\GSU\1067541\&6TU!*\40114\&90\1055516\RSV\162483N\t*\EOT{I<\1084278\SOH\183116!c\\\n\1107501\183146\DC1,-xX\EMV?\t\168648\1054239\DC2\DEL1\SOHu\SOH\63459\53061\SO+h\ACK::\RS\21356_g,\SO*\v\DC4\1093710HFF\188918\1081075fF\ESC2\SOHT\DC1)\fc\35905l\1061547\f#~\STX]\1035086/Or)kY\1031423\SOHNCk\1067954\&5\1083470x=H\NUL\23760\1058646\1099097E/$\DELpbi\137522\FSKi\15676\1018134\t7\"OL\54208\7516\&5\43466\NUL(\1030852\166514\SOH\149343\994835\25513C==\GSTV3\DELl6\999006.Z)$\16723|\172732\1090303J;O\GSbw\vI\1101024I\SYN\DC2^\149630\STX3%i\EMW\138614\DC4\1113619tsL5\147087W\96700(_,\1091179*\1041287rckx\SOH\SIs\SOHJd\140574\SYNev.\DC4\DLE\99082.\1106785\996992\143448\US_\ETBf\STX\SO\DC3\1043748\&6O\DC1Q\SOH'\GS,|]W\SIa\62568\151062.\v\aH&-L\DC2+\147179\1095524\EOTm)\19925\181147\183368!\185223\142946m\DC4\DC3\1034282m\GS\185509>>\"NDw\1076877hY\1033831sFKz^ \1108187\&5Qec\NAK}|\1108194.Q\173114imb\1027220 p;\1089082\SYN\1065748kF\1102854r8o\DC1" ) (Just (CookieLabel {cookieLabelText = "r"})) @@ -52,7 +52,7 @@ testObject_Login_user_3 = PasswordLogin ( PasswordLoginData (LoginByHandle (Handle {fromHandle = "c2wp.7s5."})) - ( PlainTextPassword + ( plainTextPassword6Unsafe "&\RS\DC4\1104052Z\11418n\SO\158691\1010906/\127253'\1063038m\1010345\"\9772\138717\RS(&\996590\SOf1Wf'I\SI\100286\1047270\1033961\DC1Jq\1050673Y\\Bedu@\1014647c\1003986D\53211\1050614S\144414\ETX\ETXW>\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"})) @@ -79,7 +79,7 @@ testObject_Login_user_5 = } ) ) - ( PlainTextPassword + ( plainTextPassword6Unsafe "\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"})) @@ -91,7 +91,7 @@ testObject_Login_user_6 = PasswordLogin ( PasswordLoginData (LoginByPhone (Phone {fromPhone = "+930266260693371"})) - ( PlainTextPassword + ( plainTextPassword6Unsafe "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"})) @@ -103,7 +103,7 @@ testObject_Login_user_7 = PasswordLogin ( PasswordLoginData (LoginByEmail (Email {emailLocal = "BG", emailDomain = "\12137c\v}\SIL$_"})) - ( PlainTextPassword + ( plainTextPassword6Unsafe "&\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/"})) @@ -115,7 +115,7 @@ testObject_Login_user_8 = PasswordLogin ( PasswordLoginData (LoginByEmail (Email {emailLocal = "", emailDomain = "~^G\1075856\\"})) - ( PlainTextPassword + ( plainTextPassword6Unsafe "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\162681/\t\NAK \1010386\1013311z\33488Bv\1109131(=<\SOq\1104556?L\6845\1066491\2972c\997644<&!\1103500\999823j~O3USw\DC2\ETX\a\ETB+\1024033Ny\31920(/Sco\STX{3\SIEh\SYN\1032591\1022672\27668-\FS.'\ENQX\98936\150419Ti3\1051250\"%\SYN\b\188444+\EOT\STX^\1108463)2bR\ACK\SIJB[\1045179&O9{w{aV\ENQgZ?3z\1065517\&8\4979\156950\990517`\1063252\"PE)uKq|w\SYN0\ESC. \ETX\73440sxW\160357\1001111m\ENQ7e)\77912\1008764:s\CANYj\9870\16356\ACK\USlTu\1110309I.\1087068O#kQ\RS!g\1062167\CANQ\US\172867\SYN\ACK|\"M\"P\US\ETX@ZPq\1016598gY\148621=\a\1057645l8\1041152\&3\995012\1022626CN<\147876gJ\1038434]\94932mX~\ACKw3\DLE\179764\&8\a6\EOT}\DLEi\DC3L5\1032336PY^|!Vz\ESC4\36208!iLa\12091\DC4\1059706\167964\GS:\1042431\149640h\\dLx\1087701\EM\194900\SUB\134635R%ps7\95168s\1074387fg\nIf\1067199\DC1l\SUB\1022871-n_\6065UY?4d]|c\\[T\ajS\18838\55046\37136aK\1025430\1112672\ETX\FSx+" ) (Just (CookieLabel {cookieLabelText = ""})) @@ -152,7 +152,7 @@ testObject_Login_user_12 = PasswordLogin ( PasswordLoginData (LoginByPhone (Phone {fromPhone = "+153353668"})) - ( PlainTextPassword + ( plainTextPassword6Unsafe "n\1095465Q\169408\ESC\1003840&Q/\rd\43034\US\EOTw2C\ACK\1056364\178004\EOT\EOTv\1010012\bf,b\DEL\STX\1013552'\175696C]G\46305\1017071\190782\&4\NULY.\173618\SO3sI\194978F\1084606\&5\21073rG/:\"\1013990X\46943\&6\FS:\CAN\aeYwWT\1083802\136913Msbm\NAK@\984540\1013513\EOT^\FS\147032\NAK@\ENQ>\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"})) @@ -222,7 +222,7 @@ testObject_Login_user_20 = PasswordLogin ( PasswordLoginData (LoginByEmail (Email {emailLocal = "[%", emailDomain = ","})) - ( PlainTextPassword + ( plainTextPassword6Unsafe "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"})) 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 dd3ff032d5..6af4068cdb 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 @@ -21,7 +21,7 @@ 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.Misc (plainTextPassword6Unsafe) import Data.Range (unsafeRange) import qualified Data.Set as Set import Data.Text.Ascii (AsciiChars (validate)) @@ -42,7 +42,7 @@ testObject_NewClient_user_1 = newClientCookie = Nothing, newClientPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "]?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 "", @@ -62,7 +62,7 @@ testObject_NewClient_user_2 = newClientCookie = Just (CookieLabel {cookieLabelText = "&h"}), newClientPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "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", @@ -82,7 +82,7 @@ testObject_NewClient_user_3 = newClientCookie = Just (CookieLabel {cookieLabelText = "\fr"}), newClientPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\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", @@ -105,7 +105,7 @@ testObject_NewClient_user_4 = newClientCookie = Just (CookieLabel {cookieLabelText = "\FS\rz"}), newClientPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\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.\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", @@ -177,7 +177,7 @@ testObject_NewClient_user_8 = newClientCookie = Just (CookieLabel {cookieLabelText = ""}), newClientPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\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 "", @@ -218,7 +218,7 @@ testObject_NewClient_user_10 = newClientCookie = Nothing, newClientPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\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", @@ -242,7 +242,7 @@ testObject_NewClient_user_11 = newClientCookie = Just (CookieLabel {cookieLabelText = "oq["}), newClientPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\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, @@ -269,7 +269,7 @@ testObject_NewClient_user_12 = newClientCookie = Nothing, newClientPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "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, @@ -339,7 +339,7 @@ testObject_NewClient_user_15 = newClientCookie = Just (CookieLabel {cookieLabelText = "\1095013\176877"}), newClientPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "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, @@ -396,7 +396,7 @@ testObject_NewClient_user_18 = newClientCookie = Just (CookieLabel {cookieLabelText = ""}), newClientPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\"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", @@ -420,7 +420,7 @@ testObject_NewClient_user_19 = newClientCookie = Nothing, newClientPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "#\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", diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewProviderResponse_provider.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewProviderResponse_provider.hs index 10a9ce0aab..57ec09e538 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewProviderResponse_provider.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewProviderResponse_provider.hs @@ -18,7 +18,7 @@ module Test.Wire.API.Golden.Generated.NewProviderResponse_provider where import Data.Id (Id (Id)) -import Data.Misc (PlainTextPassword (PlainTextPassword)) +import Data.Misc import qualified Data.UUID as UUID (fromString) import Imports (Maybe (Just, Nothing), fromJust) import Wire.API.Provider (NewProviderResponse (..)) @@ -29,7 +29,7 @@ testObject_NewProviderResponse_provider_1 = { rsNewProviderId = Id (fromJust (UUID.fromString "0000001d-0000-0013-0000-00210000002c")), rsNewProviderPassword = Just - ( PlainTextPassword + ( plainTextPassword8Unsafe "\138212[\1082070\DC1p8pHZ\29723M=\1043373(\SO\131480`x^\33407\184340\70093\t\19287p1\GS8B\1057223nFIuy\1068405t\1021942$\25186W\\o\142655\RS1\1084828j\1032739\133945\STX\1042020h\96420wi1\1070943\&7\12671\1084658`\126472T=)-6)%f1Q\SYN\1023011KD&*\SUBJ\986931\985888\STX\160818\69797\DEL4I;J\1062600,\159971\an\3275\13542e}\134953\&3h\DLE\DC2n6\1054550\ETB\ETB\DC1\1078866\RS\bc\EMKt|w_\f\1093382\187749\&0l\DEL\988035z\47132{\SI8\1005551\178976xqL\DC22>KVNuWk\\*,C\CAN(\188830\1054405)\1041136ywK\184067\166077\1098197I\23817>\1030026/\1043223\1041775\DLE\CAN\v:\136297\1060327\&5\FS\60000\1067017\FSYcK\1084187\143092T\1022344~kZH\30896V\1053398%\1025175R\t\1036947kR7:1\DC4\r~\46163;\1064061c\150551\1066250T\18604\1050741TN\137473H\GS:qE\n_\19012\DC27\ETX[M3\ETB0\1007017b\29148\1023744ZaZ\133404\1076592\DC3C\31899\v\187154y\1039076t-\1067572\&8VZ\v\186206A}\DLE\1107535\USL%J\1021982|\ENQA\1056579OY\DC4\FS\USa\120625\96272\1103955\&1/\51238/yV!b:\SO\DC2\"w\67699o\DC4\34388\DC3\DC2G*\1059727\191235\24912\EM\10540\DEL\144258\1054269\175926\ETX\"\SUBv\1082956;\989491y\44695\119661L\1017538u\v,\SYN\159965t=\SIR{>\NUL\19349\1070542\60751\999120;]\1053198\1044506\1057260\CANP\67366\1001748coG=\CAN\US\1014511\a\180084\162281\ETB*\177541\EM\20803\a8\fC\CAN\188800\rG\ETB\"]\1108090\1042950V\1014992\DLE:\9239\144437NC\171536z\fGgD\DLE@\ACKu':\ACK.0\1064350\NAK\SYN4\1073143\74776\r\DC2S|tY\40210\1070961:\100205\n4\134018Q \18123D\62039\989813B\142958,\1004136\190489\SOt|U\23856\&4\GSs\1080418*A?![\1063357\ACK\1079431>\\q\21961\CANn\120341c\29668IT\NUL\ETXBN;\9933\&3\52688\GS\DC3\1008367\SYNd\186705\&1t\DC2W\DC2\ACK\186433Q\NUL=6r\7836J\EOT\1079466@_\1087887\1038981l[xQv\NAKIb\r>/Td\GS\SYN:\998371\1094244\t\SYN\USDI_J\SYN\992565S\aC\5486\v\189566Z;\SO\194666m$\SUB4x~8\DC1\1022653\a\EMhhd\184558juGj!@ML\1088412-/X\1044395(\ACK\1055896X\37186\SUB|\f}S\163604!\1093825.DZQ\984192\151502Fo:4\26045B}`q&\1029492\1105700\45469m!wS[\1080960*$\45686\&8\987971\184543\1068840\39210m&X>\1059492\53035\1108711\1075852#\NAKRD~\US\1093122\&7\1052020\127964\180226\3559\&7'b\n\125080\v=1(\1068369\SI~\1104211\45179\STX#'a.X]\SYNtOW\39046'|e\35527\nG\147096I\f\150806\ENQ_\1100915_\ENQ\995095\163296\DC3\1060897N\5391\1023349\&4\n\53249\v=_\1101321K\1090586b_\171219+\ACK{\NAK*\1042558m\178486\&2aF \NAK$m\66289[::\ACK4\SOHG^\SOH9\148570\184675BvhE\n\1085245\61886q\EM\DC4U[\1082739\&5Um\131793L\54268\1015\n1\59799\r\DC4\165398\3088\&2sM\171691_B`d\132349\ETB}\to<\28373\&0_l\1013283\DLE'c!$2\SYN\133150\bFX\STX\RS\27526\1014674\1062904Q1H#F\121131&\1024058\147528\ENQ%=\137887_\166376\a2LHu\ACK\149657BX|lT0\994969\rY7\1017402Q6f\984562\168756\48627\185931*kU\EM n!\DC4\94586\DC4\rY\v\172116O\NUL\"-B\999538\&7\34674L\149633JqB_HFq\STX\ACKgtf\18120\&47\SYN0\ACK\DC2\1079614(>lOos\NUL9A^Mw='\RS\SYN\1007442\&9\v\DC4\156813\1009598\27686[\ESCE\30314\"\1046242\DC2.m7n\f\ACKJv}\137548LI:Ct\tNO\f>\ETX\ENQ!\t\1111513|-\2792{H[\1016313q~!\EM\CAN\\or1\ACK_0.\1006891->\3608A\SUB\25940M1BN~\b\1035987Sg\b=\ACK\1061834\1040713\DLE\DC1\1143'6\FS\1036205.]5=\995135y\1088921>zw;\1049155\34930d\63777\68484\1073559\1007539\1059519\11491'?(1.,\SYN\t" ) } @@ -47,7 +47,7 @@ testObject_NewProviderResponse_provider_3 = { rsNewProviderId = Id (fromJust (UUID.fromString "0000001a-0000-003f-0000-003500000069")), rsNewProviderPassword = Just - ( PlainTextPassword + ( plainTextPassword8Unsafe "U\DC3<\FS:\1055837\f\NUL\1030423\21688x\63687\185235\172632lw.W\\W\NAK\SO\\Xk\EOT\1106631G\1057402&\1018546\DELd\33650w\DC1p9\148200f\1033632\ENQ-]\157035\NAK\156753\182277\147513Lm\1060882Epp[\42118fq\1094307\172614\1112918\SYNM\NAK,!S\54308\GS\1058361\ETB9\r\1070070\ETX? \1087043:T{\52641C\1022936\12060\ETX\167155\fd'v\1010270N5\1103279\SUBe\fph7*\RS\1044638/1Br\NAK_\173\132738K\73060w<[\97377v8\1035947wY){\1096914\159315\37970\1076780\1044951\136701\ETB< \ACK\DC4Bi\139268G\1001751\FS\"\142742\63162\1023974\1009763m\1079549\1018524\1109082\&5\NAK\ENQo0C[e;\997701Xh\133384\12354\&1\1044141U(I5\1071453\987926A\142352V\1003510\STX#\\Fn3\1047889\DC4\ACK\ACK5#/oGCZ>2\";i_\136456\983895q\n\bwe3\9438\ESCr<\1030309SMx\v\r\ENQYz{\DC4\v\NAKk\b\1105881\1062614\175129@gF\1018445\1036543\1011732j|\863W\10189-\RS\1093095N(\SOH\1033043v{\SOI\ESCat*\EM&\1086808ob\48726\nI\1034239\t7N\17443\SUBG\1112408'\1104782a4\1049666K?\984578\a3k\FS\EOT\182555\bN:'$Z\1036469h\1061943\b\1070954\1064837FCd\FSy\52431\ENQ\FS\183488?W\DLE\CANp7h=x\NUL\281U!\157347\147744\t\1102817" ) } @@ -69,7 +69,7 @@ testObject_NewProviderResponse_provider_5 = { rsNewProviderId = Id (fromJust (UUID.fromString "00000030-0000-007c-0000-003800000004")), rsNewProviderPassword = Just - ( PlainTextPassword + ( plainTextPassword8Unsafe "K`I\1032122\v\v9CR\vCB\44032\n\ETB\1080971-\184952A\DC3E{\23976\1108295\31480>w>s\174419\&6F\ETBEj\DC2\1112922\5700\22504\1051572\SYN\48694~\1098632\33937\EM]6~\1042505\1080067\SUBG\992754@\144281 \";%e\US\a\986429\173782\DC2)r}\RSz\STXu:8\2013\ETB\1106259w\1060131\127318\SYN>\SUB\ESCfLC\t\1042789GO\1017427a\t\15131\US'6\70130\38312\181760)\143993\996555Qv\29719:$o\128256IN'4\32506\&7\24568\DC1\\\NAK\f>6\1055019\"6K\ENQ8\19156\&38\CAN\SYNJ\DC4\1067119\1058023 E\DC4\35353hk\1004971\ETX\ESCw\42692ed@c\1022322\CANo#\6275\1057346r\152854Di\1062244\1019013d7\18410\DLE+%\1095623K\78676Wh}*\f\157471*V/*\ETBcl\34514\986569\19161k4\DC1D\DC4\54645\159498\1088536T\US\35130\f_]\ENQ#\b\RS\164933\143980~\28418\DEL\20140s\n\1037109BIa3\997264\&12h\nZ,~6\1051611N\EM\3776S8\v\126483*Z\153582WaYm\169836S\ESCCO\60320\55044\"Nr\1108281q9+6\1083317\SI\DC4x\SI(&BJ\DC2\1015336\1082932Z\SI\1086314+\1059098n'\ENQ\FS\SUBC\FSK`F\146519<)Rl\FS\SOH\1089\989681a\1008472N\SOI\ESC`j^\RS\1088348\&8lx\NAK\SUBZ\DC1\46121\&1\SO\DC3\DC2\137204\ESCs\SUB\1095365\1096550\30228Mu\1036279\1059875\1025297Y\\b_\1058884j\156794\n\991844#\176907BpC`\1090453\DLE\SUBh=2V\1031360I1^V\7737\FSw\RSN\NAK,|?PE\nnS\66455\&9\1070635w\1104327Mc{NbuQHTeA 2\141584]\14488\&8\DLE~^\f>lA\GS\15373_\165\138717\154295D\158320J\1019877\148490\&42\1112182%O\RSk\v.\DC3v\1072504\20020y@)\989449\983903\&5\t\64937l:B\SOH<\1011816\SYNr'x\1011365\&8v\GS\NAK\SI\61835\24965vd{\1007773Z\t\139836Evah{\\|V\NULWh\1075431\US)~$\183370\993019#\r\1112685\5314\US\DLEh\1072923\50190\n\US\984257;Y\SUB\USc0*z|\bU-\1060937pgrepF\EM+TMCL\DC4!w\149591\DC1\"q4\DC1r\1099732A\NUL\ESC\RS\tdP:C\DC1|m\1021538\1051724=\STX\96764iMkN/\1021634+x?\EOT3N,\140980\&7\167789\&9\SI29ch\73983F,#`q.V&jF\DC2\38222|\1091027q\59326AFe\1108954]\v\8527\68247\EOT^_?\ENQ]\ENQ|y.\FS\985814f\179165\t\1086909\SUB[o7J\44822\DC4\abTt\998335\SOH\US\1061559\NAKvl{\EOT\SUB\176132\1007737\&0p\185453r0\bj\1078215~7mFE\"|l_0\CANOt\1065399\&2\t\"h[\1029990\RS4k\STX3\63898\1022953;\n\FSX\STX[#\996056\SYNF\f@\1056757-3\47636\1092415~\163138\&1\ETBt2Z4\DC2\32681>\EOT|/\CAN)\n?g>\984781\STXB|S\FS\EOT\b1N7k\"jg3A/\ENQ`|-t\f\1098225ZCb\38163\98012\1100210@pN\EOT{\SYNpz\STXAmAn\CANx\1094784\SI\EM\985088{JA\155838\140449k\1010379o\1066863\146988\4403\146168\985629\ETX\DC1w-%\US5wg;/\1015621\RS#dZFHtV+\t9\1112260\ESC\42755C\\e\96608f\vs\NUL\145260\169629o\1004991\68610#*,\1048482\ENQe;\47663\DC3\t_\168590'\b<0\28931+\1056885\1076420\t\1085492\1047003i*%k\\3m\999742$b\1075285b\US\bHikq\83524\"+S\US%\163377\SUBc\GS\NUL.\DC3\1100315\ETXw\7845EeF/W\188253\v\998044\&8\42528(i\49282 \DLEL\983821\1042779] -\NAK\ENQ\1074685UK\1093335f\tu_?3\ACK\DEL\vl\1066716n%\168928IW\119103\&3\ae\988596jZ\26730yU1]9\n3%M\\\152553\FS\983270)\DELP(\53079\US\"\1011331\171767>\984664\vkJS\SOv\NUL\1070508\1001694\984041\SO\DC1,)\DC3O0!VIJ\42024fxRa\44520\&6C5\1016544l\18451|\ACK\1049870\SI\ENQ`\bD\28847&\EM\1036800t\US?}\DC4\137694,\161747\51197UM{j\1005584\SOHZ3\157339\&0v\DC1 =s<\1010946b\141570-V\1056895\&7\178850N\160477,\1091265\1049627\SO>0dV;Yg\20502\SI_\\JVY1\1104658/|\1044099+9wr\SUB*!EV\"7fuL\ACKf\94000\1050214:4u\t?\US\a'dh/\STX)L" ) } @@ -91,7 +91,7 @@ testObject_NewProviderResponse_provider_7 = { rsNewProviderId = Id (fromJust (UUID.fromString "00000023-0000-001a-0000-001900000007")), rsNewProviderPassword = Just - ( PlainTextPassword + ( plainTextPassword8Unsafe "5\1092933o\20001\ACK8U\1095864\&6f\1034622\EM@\189365M'M\1007596\95653\171599#H?\994372D6Md\FS\DC1%\1010873\a\DC4;Y%Hb\SO-\SIt\ESC\SYNIX06\r+7Lq=l\96279M1_?\SO\1002400\SOH5\1090065J\DEL\1084934\ETXCS\190331\1095730z,2\EOTB\ENQ,\RS\SOEAS_\SOOs\n0:n\51887.8\28383]\\" ) } @@ -102,7 +102,7 @@ testObject_NewProviderResponse_provider_8 = { rsNewProviderId = Id (fromJust (UUID.fromString "0000005b-0000-002c-0000-006f00000060")), rsNewProviderPassword = Just - ( PlainTextPassword + ( plainTextPassword8Unsafe "w\a0|bL\1041357>pq\14334a6f\50385cE~\ACK\DC3]u^\31448\992480VE\155770pCtGC\FSe'\1000113\8681\SO\NUL\38836\1046332C$ck\1113411q7\RS#IK\a\1902\1068299:\1037089Y,\DLE\1010276\NUL\SO\SI0U7\162670\SO\DLE>\62663c+;r\DLE\1011919]^\n\155935\&7cmhg?\1055991f\1033834M03,3#N7F\GSEC3O9A. \CANK\DC4k@\1100987\59889!/oKv\35560j\b\23513<2V)\63885\&6\190852\1025230{u,\NAK>\145163u\16221%\1099463odLyX\1086528z\1934\"\DC3\43017\157283^sjT\176583R\1109666e8C 1z\DC4\a\ESC$`\\5\DLE K.@\141310$Vki\33498J\993401\STXT\157592iy\SUBF3,\US?\1032412,\NUL\44298<7\194563\1007500xY&]}6\1100863NsX8/\DC2m! \ETB\ETX\9857:@>\1001703>-Qc~\US\DLE\GS4+U\EM\1105092\153785a\1038694n\148671\SO\tU#S\EOT\45479\141125A\FS:\23376GCK\ACKe\30922|,\ACK\1107048K\ETXUkS" ) } @@ -113,7 +113,7 @@ testObject_NewProviderResponse_provider_9 = { rsNewProviderId = Id (fromJust (UUID.fromString "0000002f-0000-0043-0000-00590000002f")), rsNewProviderPassword = Just - ( PlainTextPassword + ( plainTextPassword8Unsafe "\1017623-\170417I\1073880\1079299:H7\73769\&1\DEL\1011215P\1111716\191385\DLE\USc\147301ERN0/\1007222-$Jo|\1113499a*\DELd\GS\47078+\1112334\1090143\&1*9j\1060438A\1097559\NAK\1074913\SUBMy\DC2cD9\1022888\1063555\DC4^Jjw\NAKvA\ETX\NUL%\1069500\NAKv\NAKu\143421\1033237\62936/|7\ENQ\176376\1039199\1090687Vm)\748*\DC1\1037993-C\1096341x\NAK\v\STX 9?\1027486Hxbh\24647\&2O\FSRwLw\FS\59665\140292\"L\188953+`\16674\14828+\DC1\ETBb+\v\1020111X?\1068966\1108715\165032\ACK\US;f\26706j\ETX\SYN\27846!;~\\q-\1101499C>\r\13051)\EOT\DLE\ETBVH*\1022877>)=\a\NULp\187391?_3yA\1000130}Uj8)\RS\SI\190547\1000959\CANt5 bBI\DC40gs\1079998\&0,+\176371\NUL^e`1\SOH3\"C`n\US\988424sM\1068774\181834\EOTi)<`Q3\36378|\1036759\NUL,z1\95394\&8u\b\37396p3\40124ty\987708\23678\158073\1077193F\183075s\SYNdnr\EM~\1031005;\176121\DLE-\US\1056632\37044\EOTS)1d\DC1s\1081234=\ENQ\139383\45860\\qQ\1025253\1063416\1011241id0\1096482^/\SO\1049155\EOT\STXz\NAKQ\991965}m\1032905u_\SUB#\37646'|+fW\142886\&2db\SYN\1059598\SYN\58628K\a5x?\175246\141042\25637\50193\&8q\39942j,(\165690W|,\SOH\f`\1091911\RS\1081342``i;1R\1057606\1075652OkM \ETB<\STX\ENQ\1054951\58357Y1\fPPLa+\DC3e*\RSP\EM7s\66725P\1095199O\148627}}\1033919\US\DC4e\988074\&6\993604\15117\141567\SOT\f\1066032&VWrL\1014145vW\ACKW\1043861'4\1074359\f8f$\1044125\59097\1065470\1073021?\ETX<\DC4'2VX\f\1031453i\SYN\58680A^<4uZvD$RRuz@\GSb/\DLE`Z\48797t'\SO\DC4K\1095174\SI\1101267\r}\994070\145225\STX\"\ETB-\1066978?x\174128TF\CAN\\f\78660\7986K>'\CAN?5_b" ) } @@ -124,7 +124,7 @@ testObject_NewProviderResponse_provider_10 = { rsNewProviderId = Id (fromJust (UUID.fromString "00000071-0000-007c-0000-00680000001d")), rsNewProviderPassword = Just - ( PlainTextPassword + ( plainTextPassword8Unsafe "\1048701m9\NAK!-=\f\1061646\96960\SYNg]\ACKC\DC4%\1074261f\SOH\46137:q\986397\&5\r\ACK\CAN\50922\FS\SOH\EM\CAN\SI\DC4\148027\10570\DC3\b\DC2V\SUBE\1113465)\\\987516\1112790\1052602`\32710\61505_@9\141069\CAN\ACK<\nnt\CAN$\ETB&Ty\1009812>\1080244\119172\138090\99297W\1014152%Yw\r\18504\RS\ENQo\SO'Yi\11499z\1042745=8o\SYN3\"'&9Cr\SO\NAK?dW\DLE'e0m#\92409\\`4h\137612\DC1,\1079782\1038558#\SI,epa\1080883\158772\94806::L&3\NUL\1005587P2,9\153294\EM)\30474\&8\1106873\ETB/-\DC1\DLE\US\994231n\DC1\34565\1081971h,Y\3266f\CANxN\aJ`#u\1080006!p\1104595BPM\64345\987805\1063126(I'\145201\1027533^!]\54361\r@yK\186407\1025019\RSF\1083246\1027354\37049/\CAN\NUL\b\SYN\b\1053210\&0\1044038/\NUL5>m\\\1007670AV\SUB>'\ESC8z>tzm\1039869\DC3!\NAK\SUB\SO\139294\146437sH_\SI\1028114\DLE@O\CANO+\121271\ETX\329F\RS\ENQ\DC2r\190233\40286~\1027557\CAN\DC3\twej\987756l\58928\SOH\EOT\1048137i\52393Vn\187739\&5\1070931\1093930V\DC2\US\n\63582\1068693\1098294H7b\133809{0Gm%Jl\n\992688\98100\\g>\1028133E\71478EG\SOrX\1012985\32794-\ESC!9p;\EM\45343H%\FS\DC3\DC4H\1046506-}\EOT\rm\DLEa\ACK_O:Ch}\178361\1023498R\137757\1073369C\"+lN\NULRC\78540\47971\139193>\DEL]\1028169+\163752\18298W\EOT{\a\"(J\140114+\1056956&\17359\1090368b\DC2\4828\152968+4\DELgO)CG\1093523\1007885tt\a\CAN\1071475oo\r3]\1060308\1084026l\ACKeJI\n\DC2/\62523>\SO\SUB@\38154cOX\ETBq`\n\DEL\1077276<^.\168043v\987250\142611di\1028462\ENQ0-,neX\DC4m\132418\&9)\a'3\190678\v&\19684@@v\1085276DL\1004211+\\52e\v'gK\1029348\NUL\29568\121198\1019231\1084180\&0\12067s\135906R\EOT9-\ETB{@\1108545B\SOH\43195\681q\173037\46382\SUB/\1008864Ga'ep\21847\72250\149851\1069409\&3\t9\t\992917*\"s4e1\1080399\&5u@x\SI\1092505T\a\FS\SOH3\GSdQ)\1055250;*v\1074731J\CANq\20976\1075946\1004990Pls\51000%\1051701L\182762q\21932N\1038559&\v\FSKgzh\1094884\&5t\SYN\1009006;\48207\27988\&7\DC4,\EOT\1113125\r}gD\DC3\1063350\rg\CAN1F\US:\NAK[S\1078198\EM|\EOT1nE7jeq\136364\992933\142920\65285)E\f\b\ESC\1027040\f\a\STX9,\SI\US\1032618~Y\ESC\992913hJ\1056354N{qm=W/+fZ\1097767\b\157383>H\160989\&6S>>\1091581lD\1081514\995028j8\SYN\1098166\DC3M\1059316\EOT\rO\1034516\DEL C9\548-\RS8\176289TR\fihwWTkCZs\ESCl\ACK[\21520\ESCO-\23136/@`\n\29582\SUB\SOH?\1012556mmPX\NAK\1057622g\CAN\SUB\190853\184963\&5\142823R\1027339\GS`5}\172782\FS\99600\f\FS\SYNK\GSxIt5D2\FS8Hg\RS\rW\146535\DC3\20417\1076627Ro\48821\SON[GAUQ\22020Q\988184\"&\176085\1069113\1055338@\SOH\4900&O\60553Q(B)\148878\53902NU<*;\DLE\f{x\171325%\137622u\1102622S\ETBng+\189490HU\40443\&9\1098608+\v\a\axr,'%\EOT\147569\r`\SO\NAK\n!2\ETBk\1049271bW\42873e\78605\1069671{\149832 \a\1015719u\DC2\47781fh\1075203\ETX\98198\NUL6\182414tb\1061469AKkyu\NAKd\RS\172284U\US\a\132925\FS\996923@x,w\NUL]Hv\185497\n\SYN\139641\1083937='@L8\28425\997304\GSTvF\NAKcF\tb\ni\47348p\1045000`\1053503K\1047025\1065259zOS/\1091190#^fE?d\1031273.\SUB*0\US\120731\137529\65724\127541u\1066706\NUL\DC1\CANP{8l\173947@{\SYN\1092993Tx\23842X\161315\1029837\23654@>\SUBSf>5K\72389\"*.\42366\132149\SUB2z\DLEc\1036641P\ETB\NAK\ETB;\144113J" ) } @@ -164,7 +164,7 @@ testObject_NewProviderResponse_provider_14 = { rsNewProviderId = Id (fromJust (UUID.fromString "0000001a-0000-001a-0000-003000000051")), rsNewProviderPassword = Just - ( PlainTextPassword + ( plainTextPassword8Unsafe "724=\34304\992526&f\SOi\DELez\SO:\1024059W\134230d\46325\141726O\SOH\185105\1050604cs\ENQ\68828dpspp#\1100148_\b\bY\1050723&\1004106\ETX\STX\989453\US\1009575\11840$s\RS0\f\1030707\167487aV\1084346_(#)\DC2N\f\158415\178760\SO&\189209)l]X;\128037\57713\ETX\1098191\32267\GSA3\EM\a\32135_\DC4\ETXaM-\GS-\1058649\160113\&8\ESC\51962}q\\\DELW^\120936Dm\127366\1013089V3\DC4\f$VJ\SUB\r&D;Hb \DC4\1009918I&x\SI\1046375\STX\63934U\DC1x\43692\1089418\DLE\1017551R\173377\133432y\185740,>\1049170\"\NUL\ETB\57794j)\174031\USFP\162987X\1058411\&8!lQ\EM}\25577I\FS\FS" ) } @@ -175,7 +175,7 @@ testObject_NewProviderResponse_provider_15 = { rsNewProviderId = Id (fromJust (UUID.fromString "00000010-0000-0058-0000-00560000003f")), rsNewProviderPassword = Just - ( PlainTextPassword + ( plainTextPassword8Unsafe "\1091642om\132322ry\1056061B1\"\DC2`(Z\ETXk\30043W\ETXO\1112372\&2\1085084\SYNX\97307L\DC1\vt\1096512\100044\&1}Q\SOH%oCv\7377]g\13125O0\SI*K \1073473\984176\STX\47770\DELbW\163314\19913V`1v\ETB\177150\v\t#[HT\1101777~v\140600-'xVUw\1083894,yR\SOm\92516M\EM\aYD\175755K\999758\DLE&\9169\DC3\119610=\164640>\1100386\SO4|\1080989K=\64562lD\1567>V\1085709oP\1066359A}A\144421\FS|\1058126s}}12\50438\1063104h2\NAK\DLE\"&sc\an\ETBm\ETX\n\NAK\NUL9\ACK\SInOuk\1073566Pc\999541\EM\SO{\GS^-\RSI{\ACK.pg\31455}\1079199\tyU\188276\175045\RS\1016175\ETX\DC3\SUBa\1055256\bh5!\183396\&5\989379n\1056705xd\100307X]\188498\42434\987030\&6\STX9n\162457e1-\19416\28346\1090880>\ETB2\bb\9645\DC3\DLED\GS;BM\DC3\1095146`<\1087719\RS\1018604\DC2\179844\EMs\167380\13627?vn\1105003\1009932#\SYN=\US\16448:1-\1107923C\"\174396JyPB'\NAKmuu/\983586\a\1048248\&9\a\EOT&\983584*'8\132864Cv=s\tP\1008677F\SI\1073061y6\191043\1008473\1008543\1649\95882\78034\1071083\988548\1054714&\97077cB\n\DELKh-\1095311\129323\&1Q\1213\1084070|\GSpcv\1002430\1051352P\139193VJ\DC2\SYN\1083098\DC1\5465\31330\83353~\1105633\120870\"_\1058706LQ\1093459\SYN\19540\&1[\b\1010436v3\SII&\FSn\US\154643\27086@=\66693~\EM(>m)\1034607\994772\tcS\1018902\1008902FE\USc\DC1VPEa\ACKe\1060808\1091838oH\b\DC2n.\1062323:\1097774E;\NUL\4333\150525P{\991902\1032220\1006459J>\CAN\42032N\1087225\184669d\1090264\FSPqG\\\169555\DEL\EOT\1012977\1056480!=/3\\\170244Ot>hF \1009843\1089883\NAK\25782`\RSw\SUBt\SYNa\1005192" ) } @@ -197,7 +197,7 @@ testObject_NewProviderResponse_provider_17 = { rsNewProviderId = Id (fromJust (UUID.fromString "00000060-0000-004a-0000-001f00000040")), rsNewProviderPassword = Just - ( PlainTextPassword + ( plainTextPassword8Unsafe "`\EM\113749\1031501B\DLE\1061644M'\1006456\EOTs\"\994582AW\1024774\995360\1057395\4635;|Ua\988675\DLE\ETB]\1054510\45394z\STXDR\1034286h9-\DC1;\GSyR)I.1XQ)js`n~\1017027\r1\989672 $iJ\RS\DC4\47918\1042259{\1059804\132487\1083033hI\SUB\1095148{\132528\&8\DLEg\GSc\NULa{|4\ACKm\\%\63350\1032001r\ETXh[(3k\1077565\&9\1113958T\96762\&5_-T\\_\11287\179256@\a\22773x9\173193M\SUBD\141109a\1035380]ajO\DC3-m~\1043103\FS]\n\CAN\1103364\&7\DC2k\b/m\US\EM\RS&\1034709\US-\US!E~k\1070052\133625\1106268wL_By\DC4<#KDn\69382:d'#)\DEL\FS\EM\1075474G-\v-\131607\GS\ETX\GS\149409\96444bH^%5p\990495\173756_1;x\STX\ENQH\46603$\DELZD\ETB\992103?G!\SYNeoY\1089956a\vb\1040891]\990176\CAN\n\1083485\70082,\177295=\nDo\CAN|\ACK" ) } @@ -208,7 +208,7 @@ testObject_NewProviderResponse_provider_18 = { rsNewProviderId = Id (fromJust (UUID.fromString "00000025-0000-0042-0000-00710000002e")), rsNewProviderPassword = Just - ( PlainTextPassword + ( plainTextPassword8Unsafe "\ETX\SI\SO#\20994\&9F\DC4\DC1\b\RS\1038287\NAK^\SYN>q\1064789r*~\CAN\1078817\1091992_q\DC4\ng ;H(j\ESC\b\US3*\989954\SO\NUL\DC4tez\STX\24881uGQGgh\NAK_j\1017134\CAN-\1020321NT%}v7\SYNYQw-@\ESC7qqVR*\DLEC\1076304[ InEyD5-\NAKW3B\1092906\ENQ~\1075196\ACKT*\1052273\9838J\18029\1028663@'w\t\1036823\\\1004590J\136317>c\1075087q\EOT\140145C\1005212\40815A\SO\SOH b\t\r_\1058766\&1g'\173330\1050951)d \1002671\1107873PF\DC2k@\39807#\ENQ2\r\n\1103694\8053\155924\176984R]rIS\993395\23030\1003223q\179266cR%|\USxI#p57 @\EOTR},7DL\133968a\SYN3\1057427\ETBw\1044594\1059078T\190786V\NAKwq\EME6\169555\SYN\1106251\STXS~\1077630\1037308\160082\1003234znW\1064190\GS\16633u\SO#\v5\DC3n8\v]AQ\30358\169782#x\DEL\1002450\1099525\t\FS-a\SOhxy\33298\&9uAu# !\189724v8}\97431\169998\1064028,<31=5e\67392Cd\165859s%(d{';\1057621\RS\194682\1066872\998845:H^6#/?\157782b\166636#\\_\1006503|\1061856\USd\FS#\fx\21701a\131086w\SO\b-0v\991756\SI\RS\66415{l\185772w^\1072628\1070986\144315\48115e\1113976r\1170I\SYN=\185280\SO\52729\\#8\1039520\996341=\ENQ\19279a\ETX\SO~\13412\NUL\\%}\\9^c\1085832\1100766\131949B_\1097503:\1070445{3Zh\US\174948~?_\154630\DC4-\EMmX'\1081292\186651F\11150\135815;\DC3\NUL\1089609k1I\1063656\SI\GS\1025160\96522~\SYN3\163717]\139965\"\19808\EMoJ\DC2\1047448\DC2Lgc\66900NFl\1064494UG\135239\r\37895\133238-\1075428\b0J\1043496\NAKi\n$\DC4ta\EOT>\35509B\157829`\1097802QQ\ETXT\f\a\1093522\1084487\SOW\EOT\1008444\134699e\992469\GS\156212a\1048619|2\999471>`b\r\ENQ$2dG\2451\SUB96{y\993171)S7G\1027170\&2Y?/\t\DC2k\SI#+\EOT4\ETB\1100073\1084657\146998]\1078697aMVd\CAN\ETX\1633\1034776^=;\12517\f\t\SO\ENQDFX\n\1020374!\DC20\12964\25037[=z\STX\ENQnY4\bLQyH\NUL\94684o\SI\1064434[Ro.\ACK1\ACK\DC4G\186332\1097423)T\30543\143448\126071\&07Gg~~`#I\1008483R\136262S.''0\57452\t\n\bfq\"\987491\&3UE\1033013|\DC2\RS7\b|\ENQ1#\DLEP;-\140504wKs\ETXpx\996531\189463\1066741\157855j\38454dY\988641trN+d\ENQn%`\1051604\1106330'#\ACKv\STX<\SYN&x5\DC1\CAN\tg\SYNq\154184\EOT\1031634\145658*\33025m\NAKJY\1069693n\ETBX\US\n\1064842k\a-\1093451\1017060\aK\120920\180904jjm/*J\ACKuHO7s\1051455D\"4\41295Q\FS\1063290,.Pi\159694\177428\1098504v/\ETX\"3(\DC4\1029505t:(:Mp\ESCb\\;Tyn#1\DC4\54968!\GS\DC2\991758M\29055\1083849A>E\n\SO\DC4u\147867U\1099918\1025765b\1027348je\61622CpD}@\SOU\17748k\"s\183751\\FNI1\ENQGH-t\CAN\28981\ESC\1068139\r7<\ESCiL\1003515\US\SIL34m\42015BFV^$BjE,8o1M9w*D%T\41964\t2\159457P\DLE\ENQ\ETX+W\ETX{\b\CAN\1100458\156489$\40574\CAN;\175154tD\DC3\1083785\39085\ESC\\-l\\9\SYN\119573\RS\92594\CAN\n/\SO\30422\ETX\1019633/2\STX\78820z\60183\&1\1081243\38950c\b\1102296\1041365T\1067898KZ\SYNV\171073@\17636\23222\1044591\1011231<\20778JE\EM`\NUL(XK\156049\EMKv/k;\ru\SO@VUEd]T\STX!\100766I\1084773\DC4&Z: \1021345\&0\DC1}\70723\SOH/d8\ETB\163270I\DLE9\149783^=:\18877-\NUL\ETBW~n\DLEw\EMs\CANB\EOT\SYNn\42044Bo\15614\DC4\1089962 1nER`b\EMbM\DC4\bd,< [\163704_\v\47338\1068094\988206\&8\36737g\1068927\NULB2\SI4S>);\175899y7\50162Wbw8C`_J\1114031\1094412._\35364\SYN%\1056737\67621\1004020\1036767f\ETX:\133669L\194963R\1096622~T\25511\RS\1002799}\DC1J}\18224`|" ) } diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewProvider_provider.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewProvider_provider.hs index cc048f9a95..3524b4c008 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewProvider_provider.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewProvider_provider.hs @@ -20,7 +20,7 @@ module Test.Wire.API.Golden.Generated.NewProvider_provider where import Data.Coerce (coerce) -import Data.Misc (HttpsUrl (HttpsUrl), PlainTextPassword (PlainTextPassword)) +import Data.Misc (HttpsUrl (HttpsUrl), plainTextPassword6Unsafe) import Data.Range (unsafeRange) import Imports (Maybe (Just, Nothing)) import URI.ByteString @@ -72,7 +72,7 @@ testObject_NewProvider_provider_1 = "eX\US\b\995223Ok?\129176\ACKY\"\144658\DLEW\44441\SI\CAN!u\\\97087\EM.J\992919Z#A2dEH\n/)\994489P8\SOHE\1063260\1072166\&3q\34380_\128663e\FS\158188\\|7H\1056055\46088\1058968\1043255eY\1106811&b\35965\&5\ETB\68661~Z\DLE\1089240Rkli\128950b\SOH\7147E\fNh\138726\&3fFi\t \GSw\DELd\42692HuDSl-\1000121e4:S,p-\ETX4L.$1\DC3\ETB\1017098r\1061346hEr\49284yJ#\DLEW\CANZY\NUL\STX$b\135460\FS\15599Y?\172311\1102259\GS\1020106\134118\96722\SYNl\1096699i&k\53037CB\176916\43991\n-l\23023\170774Q/\SOHtt\164573\1101690A\1098123\RS\1039858j\NAKe\1068011\83452\1106397\NAKEN\26963\RS\511\65094\ETB_f\154470\1062072\66387{.w\991394K\178858<\bV\SIpR/\1066633\50900\60856\EOT\3148\1082502A7I\1109396$\\\ETBk\FS\82981U\1012822W@8F\267n\DC4R\a\FS\163953\FS>\FS\n\STX\1080936nI \1054614V\1026699\RS\67087O'`@H\46961 m3\148748+^s\USm\1007141\33896M\152042\33066\STX\FScP\986670\DLE\137136>\1051639F-\DEL@V\985094\1089834\ESC1\ENQw-\SUB,\1100026\ACK\1088545\78144\1004530Jrl\STXVX3-\1065111\182019X4a\1113560V\SUB?=\1053376|\173660loc\SOHV\STXn?\1060746)U\1002872\1082612>\53460\59632v\1000605\&8v?\11036l)\SI\a3\1012928O\174317\158398\146688U\1068621Dm\STX.V\185462\137591\455\&0\35026\ESC\1043419\aF_\1091056\1093536\1043719\&0\1054413\156291\131934\CAN:OK !jPv\b_$s\USSC;\ACK\NULi\53285\61206\FS\1066412Ze\ETX\985175LI^1#\69683\DC2s\61897\SYN.|a\"\1092800{BD\NAK|\1036119l_\1103748\1024281\1019820\CAN\".\1020906D0\\\SYN.ZR\SOH\31433\EOT\1100127+\133413'B\1066652&7LM\129170\146670\ESC\1008529\SO6`>\FS}C~UE\147745~A8\EOTB\1083151\DC4b\FS7=\n'\159715m^z\67715\&8[\1038028\&20\EM \987450\1017409'\RSu[\\DI\tz\992390z\ETBFz\1021033>)\63924\NULO\SOH{\SO\1006356hh\1055488\29196b\DC1\48178\1065099\166710\&68\1074840&u\30251\1038941\f&W\145237P\158967Z^%{;\\$jh\1061320\r\rll\DC4jI)\FS\SUB\\\996923t\44820\GS\74802\&0\US0f\DC30Nmzs\CAN\f\ESC`\DLE\162810\161070^t\DC2r\183989.f':\1109934l\17508w\171346\1022383\&9\14042\&5\1110132\ESC\1041237@\95112aU.\1062393\v]\37115s\165263Q\DC4\1075995\DEL\5225\134431\15103\FS6\CAN\1064420Q\n\137666\1015259\1108266if\EOT\FST\1013036\40256k\ACK\34918\SUB\SUBb5H4~\1035553V\171666]-\1046754\DC4\DC2`zh\1091598\DC2~\1100400L\DC2h\NAK\CAN\160701fJ~\999801^\RS\141113{]ms\96252de{\STX\31083N\1080942H\179600OP]\1023361\149175\ETBz\985800\a\163992\&4\132045\986873\ETB\GS\"P\ENQ\156813\48597L~Oj\"R50G\52610\1077153lhcU\1091924\GS9\174773\t{as\143998\"\67306v.\ETB\SOHSCD/\FS\DC3\169000l\1086905\78356^\183726\1094002\RSo\152541\135639$\37530T^\EM\GS&\ENQTsl_un\1012503\1018353\GSMy:pX\5892nB@`*J(\r\ry\139052\DC1\10228i\CAN-S}\1102675\\\74911Bh\1104717lY\ENQk>`)\EM\173078gZ4\1064140\RSIAJ\1054448WgoA>\53622\987489\1042604Sw\1113231\7766\&8if\\X=\US?d(\SIt\44993\194758\DC4KR\1089533\984650G\NAKC5\NUL\1077670c[\59836\&7-kp'\159389\SO\1037212\41495F\1033061e'=\EOT\a\DC3Z:q\bw7\1044001\131787{]\1031412Ah|!$\62490\auzh\DLEo\1072953\SO\1021796\153148G\1023308\DLE\21074\&1p6\SOUX\SO\4214kY5(\FSV\188078\DC3\NAK\1071166\1007087.JJQg,%c'aq\t5f\1012607\39545\154628\f.\ESC{\r\1046768>'O\47202\DC1\ETX$R\ETX)\997568$\173402Hj7}F\FS%\1110017\1083305\998552\&4\95158\SO\a\179995Av\988645\\\STXWq9\SYNW\ETX\991965C%\b\v\SYN\37742AzG'4Dj\161358|+S\v\STX9|0$tLe>ol\DC4\EM\1088003\r\DELmBK\27258\1077075bX\188456Pw+\SUB|e\30882B\1041022\&6v\RS\1074170\\" ) } @@ -104,7 +104,7 @@ testObject_NewProvider_provider_2 = "\1099787Ygo%\944I\1005116`F\ETBYT\US-3\992556\27798\DC1G'\14811<\33364\1054185&\43295\NAK\94529\73798\ESC]\DC2\NAK)!\n2K3\4250[+{\vC\957\1009692\DC4L\111084\t\1050716E\ESC]AF\CAN\35119\1083267\1063239\f\NUL\EM}w[F\18536\NUL\188964\SI2\174320^z\120621>\t\SOY\131199$4f\ENQe\r\1067591\60319E{\1108553\n[>\1107778\991883\1112693xa\SOEG\1034253\1012045usz\CAN\1080970Im\FS1[$\992090\&9/\r\51589S1U\155047qK\ENQ,\DC2\25888\1032623\&2\994470ElX&92\185686T\t\ETB\tkM5\1027665,\998362#\1067165~'vmCROC\1005145`q(\SYNq\1083849]<\136352h\CAN;\ETB9\49328\153749\29441X\131818eH\ENQ\1007071e\1098892\DLEXKDs\SUB\SUB \SUB.\4007GZ\124942@\1006684QDOJ\183338\1065192e\110786\1049183\ESC7\992658\29915L\1029762\DC3h=[F\1062586\afH\137173f\111228*AQb]\4441H\178088\159233o\DC3&\174611\&4\t)yBn25pNa\181971Ex>\ACK%x\52036B\US&MFo.S\1069125=m&us\134137j\ETX{\DC2:\131911\RS\1063145\159338\1017553\176300!u_\ESC?\27584\&1n\DEL1\ETBzg\1107110N\f\176519?\15997p\134128\1031849\EM\DC4X7vS\156481\1048345P\SI'\1034676\1045079Nu=.4M\191448Wu \DC4}N]a,\129145\1085383\NAK)|\12357\&9k\1049331\v\1066664\186640\NUL\GS\169180\RS9\CANT\58203l\1049292\997037\f\51053O%\1059997+D\NAK\DC4\ESCY\1000337\138902M\USc\NULY\RSx\EOTc):\1007927\SOJ\1044503p\SObS?[KU\190321Ap_\164005\ETX\986483\DC3C\a/\38086\&1mEmb\\\669\CAN\ETXB\1042722J\39668&\SYNZ\28973\SUBbniY\DC2B6\DC3\CANvm\100846lM*\99266\147320M\DC1\36270\54145\1027786\ETX\143852Ee\158098\992386\DELw\54653\78178\SOH7#[ck\100330u0P\nPpN\DC1`*\EOT:=-hn#\SOH1u:|\51970/\NAK\1059948>\DC2\"\29644i^1ER\RS\996078\&8\1125\150806\127030)N\71298wm\44426\&7\SUB\f\132307\CAN\DELW\1113578\&1jf\STX\n\1011631\100631\36624D\ETBx\RSc#\1056581B~\983966T*\SI\125218\1022061\&0.r\EOT9F{u\DC2", newProviderPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "yx?@/\1064686I\DLE\1095959]L\1029155\DC26C\SOH\37291\RSF0A\1010733\DC1\a\51798v\156301=ul<\SOH\SO\1111486\14157=}\vZ\DC25\vZ\37208yy]\49915\ETB\989906\1023059\ETB\ag!K\EOT~kg\FS3<\GS\155169\NAK\1043661eT\996822j\1101705j+*`\177804\9227wprI&\999931H\SOH\989782Sb\DC4.LaL\1047000\1049757\ETX!x#-\1058415\SUB \1097186\t\SOHo\1060278xc\1056850y\ETX\ESC\101028PH\98408.K\US\1026324b\DC4%6\NUL\27763\SUB}Q\STX\r\b\SOH\1024758\3806\US\1075163\STX!xvB4-g)\42920\ACK!GN<\988790z\1099591\1105946\"\165524n\143776wr2lY\173686\&9k\129536hL\31908/\SUB\RS\1112666}c\1038214\&10R\US\CANisO\t\14749jP#zi\5193n\DELaR\DLE\CANa\SYNd\8141\1082961OrW\1055686\82996\125215\"\137888\51122\DC1+'\bqS\ACK\997894\STX\984520*:\1013827!\168627J\CANs{\DLE\78747#\1109522\993984u\164248\rk\EOTceB\SI\1038689\"\987017\13792q\175366ed\1070052\1066652\95285~\194990~]\10444'\"vi\ACK9{[zG\1095584C^\1100926\26462\&6\SUB\188229-\RSm\v/\RS\185849Y:\1106298\1098425r\SOHx\1111404\1022242OR+\1072403\194678\97452>\181135:q0'\ENQa\1003104np{\"(\1079308\30071\48211#a\1004180\155075QN$\DELT\990246?Dcj\38870:\157406#nW}\97686z\SUB\143116n$\1096601UP`\RS?1Z\99546mx\\\23793!*\1112791\1099074\SI\1109995Qo\994497\1047096\STX\a|/\\6\DC1\163049\DC47e\1063537y\ACKT\CANf&_\36459^sy\1102254\70173;^\DC4\ACK#l:i Y\a|f\ETB.$\52342\t\DC1\ACK\1088723p\a\1099755\\l\DLE?di\NAK\182060yr(\1092195O\1061441\ETX\882\US\166014h\1054820\"j\\\SUB}\ETB\180746\t\DC4\194760\1041315|']u\986944\1047896=8\NUL\n\119249=\DC3Bkuj]<\36839\52668\SI-\144952t\13080'\1009064D\53913&\US\DLET??\1107069\ETB\SOH\169261\US\DEL\15144t\183810\EOT|zUk\DC4\993404\ACKJ_4L\v\r2Egy{\1004502D\1000475\US\51325B\68354%\1043269\t\SO\1094630\95611X\NAK#\"\43540\"\1029360\DELW;ar\144134\162892!B-[\71854\1091829\1032824\17826#!|k-a?O\1081169\66637Y\EM*l\133471c(\SYN]\17396\ETXc#\1037124m;\1098050\186652\&2X9\21563V\EOTb\f5&\RS\162130h\1060067O,gG\40497\30570\fJ\EM\1039903yex\1066851\r\ETX|\NUL>\GS\1061848g\SO\EOTEr\ACKi=O&\32935\95156\66870\"\DLEV\SI\ESC\95998]\vA2\SYNl\121147~pn\1060695H*eK\1007734%K4K~\ETB_O\1077247fV%\994020\DLE\1013505b\ETX~7@'u\1052404\145314\15969FfsljK~\DC4Igb\185366\149793\1018105\&8Nz\1066984\STXDy\120868\1029213\1087480\&3pd3]^NB\4837G\nG}7\ESC\13917\1075937b]\1087002WM!Hf\72865e-\183494\DC1I\1079472\1054949)P\n\bGK-Qh5\fS\"zA\37747\ENQ\ETX\ETB8m|qL`\ETX\138417\167543:\"*\1075522;hq\DLE3b1\72737\ETX{\38227_\1099994\&9\1083603\70853\SUBce\154253\59189\SYN\US\27371F\35445\n\STXdJ\994035c\\4\ENQ\121422\70826v\SYN\FS\ACK\STX\178978iY\1075666\32236\&4n\t\1083298/\83245\DC1t(S[\61140\61270\137272\1056236\&00`\136921~\1013995^\986212\STX\NULe\1060505\GS[\63183\179892TsJ\42513G\1841\n s \RS\CAN ^!b?t\rFe\54373 82\1113441\1093659jf\1009893\rF

,\39509UF\1104490\182554\&4\CAN\1056006\60313:R\166058Jm\1089155\137994G`7_\RS\GSX\158978\&9\171246T\1041338E>@\SI\a@\34297\CANf2'Y\1053735V\1073397U\1011704R\NUL\DC3\ACKu\ETX\r\100983g\984942\&1d\994548\&7yhj\1054148\184847\DC1\178638\143460Z(\DC2\1094778\SIL|t-\1021785=%\US}\FS\33497\EM\RS\ENQ\r\157743\&3\156252\EMB\32802a;!\145184'M\1002623k)%\58543\&0+\1021211oM\EOT3K\SOH$\STX-tR>$`\ESCd\US\CANZ\STX\139918RHG\1047715\1083460T-;<{\24537K\v=7.mP\57785#\984455}G74\ETB^\997689N\SIF*$b\GS\v'J\999322\&5j\44525\120532\1094145s[\NAK\170359\ENQ\5017l\998944\SOHxN\138107\&6\ACK\ENQv\189907>9aR\1072783\991540T\ESC\b-QZ\ETX\SO\1015222.\DC49]\1109127E\141111\FSl\47199\fCww\DEL9Cys\SI%\25544\1049438WY\34917R3\DC2*Y\va\SYN\ESC\GS\NAK\r\SO@ U\ESCG&,>]\1043753\RS\70814\f*j{\111286\SOHG\1050064\a[m\33062\93765-\1014570\987944)3qH]\29853\&2 C5Ih$D\1091957[\1003561^@Ff\42948},{\1001921OemlSN\1101664\SO|wF\1059365\SOY\DC2\73980\1045596Kt]\\\ETXBQ\984428\FS\23489\1039581Db\54933\1029118\f\1071214\38076x\1001367\1023030!uj", newProviderPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "~8\14339\1061876\1676\62256> \SYNq-gd\USrD\ETXb\SYNUx\1012524\145801\8889msB\FSqe\v*,\b\1104122\vs\f\EOT\DLEdh`j\DC1$V^\SOH\SI\180782\FS\v\SO\1014316\1059579\\\"\STX\987401hf\CANs\ENQ{\FS e\CAN8K\1092808L\149813f\SYNsP\1004165oin\1112291\rG\"tXU7S\121241\NUL7C`x\1080124\153415z\n\b\DEL\SOH7\1012528\\\\\n\154325\SUBG\SUB\NAK)d%\993609\EM ]7\22351H5t^+Sg \\\DLE\98151\tbI3\170604E5\1082561\DEL\194652\\@vp#Y\ENQBA\1071377y9z\NUL7\127087\SUBis^&\ESCh\ETB\CAN\RStlM\DEL\n\brT@W\59740;qs\1029789n\EOTf\SYN#\7648\999620))M\b`\175142\154221K\1058957(ftW\DC3\1049977r\ESC\62914_\1043878\1052654*$2,\SYN(U,\EM\CANBFp\vXR\SYN\DC2\NUL=3)\DC3j9\1059863s\1088866-k\152843\53102UG\1001312N}\EOT\63390\1050388" ) } @@ -174,7 +174,7 @@ testObject_NewProvider_provider_4 = "\1056051\1017885\984781e\CAN\CAN'Y8}sJO(q\DELR\70378[\GS\DC3\DELW\1035164\1041367\140020\SOX$},u\44224Kv\166118<\DC2\1005239\f\1051148H&UJ\97244$UJ}\ESC\ab!j\128498T\1029724\SOHeWj\USii\vLc \129430\1067209\&7\\\175545\63226(%\SOH\128009T\SUB\986931KC\179485I\148610#\154740~ZS~+\t\13200H5\ACKu+\185915Z\41528\1014839Yd\"y=G\1026695!\EOT8P\DEL$[\ETB3\178201\175948\863|\ACKI\1111319O\1100613)\DC1\ENQ\47947\990833\US\63986G\RSEG<%Q6\1061516T\RSd\SIFE)`\ETB\1071039O2\b\1016106\188492I\ESCc?nw\1084281!\1019342T\179816t\137942\25333\ACK\149109p\78558~lu\a\FS\30978}\SO\5431aa\1049051T+\SYN2\183127XRZ\SIb#\CAN\6026XzW\1105442>\1031193\1081488\v\17921\NUL]\1044228\"%\RS\129665\43830\1016999q\ENQ\1092919\177399\186906]S%0 0T<6X\SUB\"i\1003139yBz\168891\SUB\GS\1007006\985272$\20354\11788*\\}\SYN\aSV\185994\b\\4>GN_K\1060691\US\1015276Z~9N\67275\140457H#$\133494&/di!\136294,ZJ\n\61284=O\ry/\22180\DC1\166277\&7nN\16820l?\187084xp\139245q\1018235\1113697Ao\NULN|G\1046357f\1012394\990651\140074z\r(;P\37544\40482\RSh?Yo#\ETBUd\43998\1021108W\1086070a\1040956e^\1100815T\140443\1066701x\134232)M\b(\44807xJ\66367\1073911}k\1088345\182770x\1068760E\999825\&3Q \25468\NUL^gd\34593\EOT}D\119962j\1010982\166754\171245\f\54736o|\50891:RW.\1016128\NULt\173706\1022531I\ESCy2mm\28797\51357]\\R\RS$\nV\t0&\1017663\DC4\1097998[X^\134396$2\1092149s\NAK\EOT\119576qQ\36946\1050506\155134\b.\40867N\1094546.pSw\bWI\\\DLE\a%r\DC1\f\1006832?J\fJD\rLS@{\21565\&6,6\70167\1016889\59435\US\GS\1102314g\1006074\36666b;\95879U\STXd\ETX\1071251\v<&E)\65449`\64644k\125039\172235\ESCu#\RS\ESC\48613\&8\1093243*@VJ\62397Gm\1046894\EM\998081\DC4k\NAKI \US#_!\1057109\1106140\DEL\rc4@\120445\&5 W\FSGFS\35586\r\tnGb\DC1\1009269\32356\SI>gCr?*#k8P\t\t\STX\1060205\NAKw4u\69434&`F\ESCeNk< 0\1042035\992964P.\137169j\6312|{\1098822k\DEL\1104751\1035825\DEL\905n\bX\1078110\rB\1073963y\r\132270U\1053141\43931@\125077ki\ETB@\69700\FSo\44871ZmB\136818\SYN0\74183\170316\&8\992106\&8\25195\917550\5682\EM\23212\NAK\CAN\v\a|.nCX\1021453\SO|4?\DC2t5h\93800S\RSu,&\57378m3WL*\54375\US3\25258\GS\am/$+\ENQV\61556M8$}\988220\r\NUL\1098816\177561\\}g\1044001\1084867G\1069244U\993218P\993720\187689\r7\"\ETX\35733\&7\STX\1078395\SYNL\SI\FSlf4;\994123#\t\155078\NUL2:g@'K\1092407\120226\64261\44003v\ETX9\178319D*Cjlr\35509\GS)\150194{An3\ETB8\1049329eu\1028429B0eO\DC2\146624\"\DC4\189274Q\ff\988966\1008054b\179179\EM\DLE\ETXXglf\EOT\1007647D%/dd\DC4\138394l\\\NAK\1093918Q\162404:\152373x!3\53493\1031796\&9\15913\&0?\b\1094421<@\21374RQ\b\ESC\8753W\54203x!h\186330\1000126\&6\SUB=~\190685\170749x\187616\SOH\ESC\182234\SOH\113782\30880\22705\USA\187075\n\to\142657\DC2\aeX\998687\t\1003694\60264F~E\10378q8m\54684\EOT\1001755(^\95224H\1052262\rMlD>\1085731\1022024\1060156rC\1094820pfoc\118891J\GS\140795woa-\1033961A/\ETX\1031114\182383\129557R\1004710oGyj\1053733\v\US\136956p\ESC?vw5]\995093r\182759\1081985#\1098045)DQ\DC4\NAKUh`:){x\148691\1037531/MHDZ\1112514O\48710\&1;tR\190339\1090343\1061274M\1051273\ACKQC\992628\1083272" ) } @@ -205,7 +205,7 @@ testObject_NewProvider_provider_5 = "\1111449^\SYNA%BD\993875ev}Ia\1033204p\52598+PI~%l\1100225\&6ZO\1010258EZ\145008u\173602X\t\DC4\DC3;\44149\1102500c\1044911Nh`\6362{13\fjQ\EMM']\DEL\1046113e\172961^\1063167\&41\99043\173288\&1;\DEL\ACK\ESC;\40119\SYNI0*`t\b\ACK\1009174Art\f\154422\51261\149654|g\1103749dd\SOHP\1112490\t\163330x*\US\DC2=;\153747N\161049\1049506\ESCzH\DC3\\\ACK\77917\161818=M\155629@|\37975\USi=\FS\SI\1087353\1101743", newProviderPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\137367\SOH\ETX'\EMQz\SOH%\1097013\158928p;\47961e\ACKiF\1056884\ESC\SOHgL#\1036629\ETB\1106817\ACK\ACK+\SI\1037568T\10290r\181349\DC4_0\GS0\1048032])'\1079448\ENQ'\153670^n\SYNc;\RS\1093718KwzB\RS\b\100995qdQ\30984\1026959\a-\37082h\180782>~Hi,\1282\1094492\135994R4'a\153830\RS}p\1053939\993503\175828P?\ENQ\63540naw\RS\US\1020900@zr\DLE)\171214&\12850\EOT\ETB!|T\NUL '\US)cM\CAN\ENQ4`\165858F\27567=)U6.\ESC\FS7\n__~\1058219Ruo\1064774\993210" ) } @@ -267,7 +267,7 @@ testObject_NewProvider_provider_7 = "ia\168661\SUB$\SYN\24545Km\bFc\990300\DLE]m\\'\ETBd\n\1074149mm-\172683L\166381\49815\99530}\NAK\997914\ne\n\NULWy\46820\168943\SYN\SUB=P=\23523\ACKsbD\EMfY\1007583p\"R\99624g\1013961H\1095839\82981L\n\1058938>\ESCl>Q\DC4BlW\166260\a\v\aT+'?rI)\NAK\1016220H\FS\SUBd\1059263\SOH\1029139.A=>\a{@GBf\92391$G2\ETB\1086260:\1047803\987901v\r\RSL\v^ujRN+b#\42663$CulYu7\SI\SI\43246R\DC4?\1006221XiDD\CAN\EM\7036X\DC3\ESCi\154135r3\1020364\\mZ\1091745\1021202\1109376|eJ\59191\DEL;9\49243\FSb\DLE\1071300\CANp\1067056\&7\148429[j\b\NAK)\1025923?X]c\163450\1074879\GS\33164a\ACK\11702 .yu\1023048\1038766\146038\1085467\202\DEL\171231\DC4M\162885?Q*pzVXTk/\1030152*\NULD\45442\SOH\127893\10454aD!6]\v5x\34604\1079046>n|#\RS\995651\1103063\SOA\ENQ\13278\t%4\1024993\437\ESC+-_\SYN\SYN\1008775\1106234\ENQdH\140340\1091570-:\SIQx\\\1003528\149456a\FS\999457\133210eJ\1099136,\1011024\EM|\DEL\63124\2975\&4\27274\1039989k;\b\1038919\&6\ETBs~\SO\68678\SOzJ\118813Dm/ra\DC1S0 \DC1\21072\EMjw\DC3{\169195\b\1100120C\STX`J7#\SOH-\\]$\EM\154575`Z=|\1053044\US\60192\166722\a\DC2\1006477[\EMkX?\68666%0HOW@Ew\173850'4\168609[B\97112\1016577^m\1101324\1030618tE\1069359Q\ACK\156696\STX\62137Z\SYN\991127M\NAK_R\ETBN\SYN\a6\DLE:\19226S\\\v\1081595-Y7\f\n6\1069300hc\1012380&N\1058076\1100628E\1111712\vCqHrWV\161935\1060915\131275srO\1051706 \1026421/\STX~\136990\34590C\24875\145233A6WvF|\997538MK\veS.+)4|\f\987738m<_{\ENQ\vW]\1058832m\ETX(&\92538e\12977\988576\"\26375kC\35034i\986239\n/&,7\1002363v\EM-s\SO\179191\16503\1039654\47789\993240_\ETB,_Rk,=Fx_\158682\v\1066632#y\8484\RS\37028\NULV\r\1021016\DLE\EMWD\917563BV\1033949\&6\1075019\2264q\ACK\22313\162446\27420\1007484\b\1058123>'\DLE\1001778", newProviderPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "8\\%\STX\45334s\EMF\EMv5BC\169207NL\EOT\22490\nTASqz)\EOTkQ\1065597UD\ACK0\SO\1001536\1048619\31608\1029373\SYN\1041400\128257\SOH\1074590qM_,\170823+pa8\SOHJ\NUL2r\SI#\1109551-\EM\SOH^\t\21117XuS\158660\a\ESC\SUB_\987790XPC\67429\ETB&C73\1034924\SYN|/M2F\SIS\b\58957\29041I\DEL3{/\FS>\DC2H5rv5\"?!0\145539^SC\bS5\78346XP}0VT\170573\FSD\42106`\ACK\1018911f\1108169\131256Qt`P9~\174262\ACK\ETB\73706\1035701K=\NAKRC\45513\1111656\1049156\GS\rY\1110097e&\DC3d\t\29575\999670\1028927W\DLE\t!\vex@]xh\EMo%\182017\1082183\1078398k\22785\&7/y:\1094694\1012810R\99887A3\15587\1054356*tnwXQk\135931\1071955\1071264\166951\110987\SUB\149345\SO\RS\12606\f\41837'!%p\a\1112098x\SOH\1057708\40264SP\1008184T\158739j\EM$5vA;2Y3\DLE\1078911\65812`{\EMS6\ETB\SYN\139198?\1032088m\FS\34734I\183363E0\19414<.G\DELu\ACK\ACK\DC2\" -C_W\18165j\DC3\NAK\NULC\NAKs\27417\n\FS&G \59660-e\13283\vM+pB|\182560d\r\\,2M\1006641\174549qA\27902\b.\1050452gP{5y0\1008449\SOH\1064365d|\\EUG\48708s\194781\STXF\SYN`\1039218O\DELD\n';\1073324$\57526$D\1062134: r\DC14\100606\ESC\v\DC2j+\STXP\SYNcZF,u\1051699\175940\EM\ESC|y\DELAE'\34865\DC301I\\J\DLE\t\NUL\ACK^mn\173175\&7/\SIs\25751\SO4c!u\1031758P(\r-\t\SI\"\36085\v2Y;\"h\1069038`?;\EOT\ETX&tyLb(P\1093335\&2EC\ACK\983169O+Gt\137064\GS\1052511;wwl8\FSzW\1010249\RS\1023409c\187492kSB%\EOTnC-\EM%\FS\1014861.\GSy\1074065\DEL\1066888\1059368\EOT\1045172\143136\119132\1093794i\1017256&7\168603\DC1\1011326\SYN\1020673.W\189771\&9\49494a(d\1080143,\140528xOX\1103043\1072457\1017273\162724)H\991800\&7K\99033@\USM\19502c\55085K6xk\SYN\NAK\135904CH,$\8540y\FSK\1078320iZ\1103518\a\STX\SOHk\SUBx\66307\1074330h\SI\aYH\1097304\"\33305\1054977$u\27806wk\SI\DC13\DC3\EOT\1029381\1095477>\ETB\1111999D\1071976kHAt%\SOH-AN\20943\a0|\nC\ESCLdigb\SYNO\NAK4\157385>\NAK\DC1\21696\181366\53193\8490E\1091428%\184637huLv;\189960\152600\26674ll(}]8#\1101655\16569\&0(\1039561#\165077cd`-\ENQ\78481P\1035406[ \1089978[$\33084^\1078097,uKr\158491!M\50917#b\146402]i\142972\ETXu\GS\DC3\CANbU.pgb\1060162[N\SI,q\ved\te\FS`<7X'ot\983610\v\158865KK\153342g\184580w\1027317\145275['!\USx/.\DC2\3360JV\1045639uC=C\148379r\1575\"\153627\GSjR*" ) } @@ -302,7 +302,7 @@ testObject_NewProvider_provider_8 = "-\bk8Y|'\51494F~?\1005217c\1062443@\12110\EOT=\2625\12156j\134926>s\SIE\RSZ\1078274VI\984461xZ gz\917991\EM{\46186Z;\97072\149669\1044813nX%h_,G\142406\36906+\141167pgd\1027485\159336V:/\fq~j(+Oa\1100396\DC1f\f\FS\177343/\r\RS=v;\1055293\&9=7\139267\996274\fY-\132435\1080745Z%\1066938\165299PO\42182\1049517\1098441<\150198\f`\ETBz\19818~\143818\164663\NUL\ESC\f\986483K\1113826\156664h\STX\1100664\SIt\SO\EM\DC3\DC2]TM\SUB#j\20431_fzNi;\1031829e3\129428_b\n\1039055\\hn6Y\1045520+g\EOT\EMFJ\DC4?\1034652\149956\43275\&4l\69676\USZ\DEL\1002780'Y\1112729\DC48\167862\996262=PZQ@\1107082\n\998271\50704\989765\184422y\1026191d\DLEe\1052386P\173435L\1001859:\1088420I\48620<\SUB\1045117\f\1079007\DC3|E\1037015\STXI\1066896\&3\n\53108\NUL\DEL\1086453\&7\tn\DC4\nF\986614@\DC2\GS\SOiB\DLE\DC1W6)\167124\&8;\fIl\EOT)\DC3\178947k\1083372_\EOT7<=Wx\54693\"YG#\989937#|=P\1108972\f\EOT\ETB\bok\173839V\1068577\EOT2B]\153805\SOH0e\NAK\162342\38789;@\178942R\RS,NUR\1057281\&7\1043948\DLE\SOH3s,'\1036054\156756\1051100\a\SO\1013540\1098046\&58^\985418\DC4da\166744\SO7\ESC(R\68848x\176364{\13517\110828?l#\ENQq\GSxB\1090583(%j\1019113\96592\f^>q\1065860\&7\40056\1072531XL\EOT.\1107693\a4\179100\1097830\DLEcpr?\37342\&4\1061623\1100388\146350\vS\159853gZ\NUL\SOH\NULh(u\1078620`eRZT=Z\SO\149784\SYNHa\ETX\1082328\180417\DC4x\169037abpmUb\SOH\RS\176077\1005287\1079786RA\1105547\DEL\1038839l),\132201\1045343~0\ENQNa\39711\1018543\157714\35703r?}\1063016\SOH\"\13966_9Ge\987967\EMBE\1317\892\1048693\36155xmP\138321pg\RS\US\SUB\v\172821\34377J\172017H\STX\41622\68388\"V\20657\v`( JAC\1010983\US\21086m[\49797\DC4\ESCe\EOT\1027105\69673/\CANSX:s>k\9171q-\ACKC $[M\54581T^46\b\996164{q\1081890x&G\178501\101049\125070\DC1\1108278aM\1054922N\1097695\DC3\CAN\995721G%\1019194^7\EOT\1013772(c\\\ETB\1027044\161679gYb\NAKz+\SI`\SUB\STXhT?@\26098\1108351\NUL!zX\ETBP\1070153my\28463\NUL\48441\&6\161625Y\134507\FSE\38308+:\1048271\36828%\DLE\188288p\GSO\1098236\DLE\GS\CAN\GS9\NAKE\146763\DLEi\164938T~i\1030944V\ETB\143762H\n\DLE\NAK\1064486\NAK;\1106012\ENQ\148194\149505\f\ESCv\RSMq59{\ETB\127967\&2J\DC3.\DC3P^", newProviderPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\181673\&2\188239qfb\61095\STX\35333\1094613\178860\STXaC\ESC\73945\181776\65893{\CAN\190011w\1111110D\1054736q\DC1!N.]bQ\ETX*\ETB\US\a\1046887q\158066w\152573)p\ACK\996009\1077236\181330){N5\SI'Eb\15220\SYNx\NUL\1073590\988681\60766YB7\1094372co\EOT,[\1011118kDB\1080594qU\tr\51386s\1036440Jv*\32647)TM1\1006104=\17257\US=g\STX\163970\DC4\EOT-5\fr@f\48920\1018635\&80\US\167132\48845ns8WB\f\1093233#C\t\44212_\96106Fl\152020\bK~\ACK\1000313\FS\160850\&9\SYN.\SUB4R\FS#\165301\16648\&2\NUL\ACK\20280\EOT\r\SI\1084824\\\f),\54307kJ\180664\1058240\&2B27\to?G\1933+r\1086717o\DC3\GS(\r\ACKJ:`\tZ\NULKp\RSxK~,E\1058183\DC338\1000895\CAN\1012342\175298ynza\DLEg\1105136fB\SOX\n\1052810\1113899H\1076167\991616b+\1031624IC$\b\1007856\99469}jc\1018185B\ENQ\DEL+\186466\US\31634\SUB\SUB\SUB\ETX|\NAK\1042051Q\1081506\SUB1\998249C\GSA\1048478X\57384_\145168\ETXT;\1014763\60933\b(\143964\SOH\127965\120553\&9no \1013182v\23196b(\ESC48\a\1024742wEL\139151\1089054\FS6ePOm{\DC1\164267,&zj\ACK9f\FSd%z\1105887\1037745\&6\DLE\1039249i,\DLE\GSy,T\41372>z\ACK\12304\EOT{yUcu\21077\SYN\134310gB\45423\at\NULKa>\165013\990540 \182397\1101375~\t\1018709\1080718yzRZW\1008208\SOHT\1041515,\EOTY~F\190089\DC1\STXZ\986110\\8ei$FViM[e2F\SI\1076629RcZ\1044119\NAKN\29972Kr\bo\b\SO;\STXs=\150865v\188844L&r\45009\&3\\\169632\&6\SI\49045\24830\n\1049549\142815\142409\&4\1068154YX\2107\ESC\1034876\24275\151781cFE\f3\1103509QiPb\t\155982\SUB\a9&\f\1023795\ENQ\NUL\STX\SI,M\DC4\DC3~tx0u\69216c6\n\1092523\1059459WA\59326-\10439S\DC1?\989056\158895^\132652u\DC1|#0\63379I\181557\1112693\GSa\174369\70193r\t%\1083075RkwJ:\1082615^\163156\1070110iI\1112809f\rx\33934eN\176533uE\160118\176741\37761];\27612\CAN\FS\155861\34060\rT\b\162737\136891\SUBS|V\1062665&\1059238\DLE:\rYxL[\4648F,c\51911\DC4n>\1070607\EOT;/+\1075294\SUB\161670\NAKt\148103\46608#<\SOV\29818\DC4$\1040961\&5\1000313\1071500\167217eN\DC1\RS\STX\1103111\NAKu3~\\\166939\92376\172159(\1033270[\152712\SIC<\163961|Um\1096297e\57412]J\\\1034954]\NULAc\1107597#\37549\1043607\996535o\EOTS\1034031\39227\&9\a=\DELT\n[WkRO\SYN/\1055333u$`(\1009830\ESCw\DC2\ACK;\1089329B\1093033I\DEL[gi\992857" ) } @@ -368,7 +368,7 @@ testObject_NewProvider_provider_10 = "-uD|3n\1019145+\DEL4+@\1092025Jw~\178453\143260Q\998370\97155r\156084\47609\&2,\150337=i\bN#y\170060/8QhH[\172814\1067147yl\SOs\b\SIW\111157\1029945\100804s?`rr\DLE\1113178\STXj\995436\228q~d}*\DC17\DC1\b+^XL\128422\&0\bG\1065568/*\SYN\137635-!\1012385w@3\CAN\STX\153522z8N \CAN6`\nr$B\rgmp\39211\95604G\SI\1048817\195085w\1072534\"\168231&\1076883\1055456\1094455\64720kZMV\1095414\RS\136637\SI\1091382\DC1\SOH\1104991\152647Q\DC2\NUL}\1008804\1113\182260\168413\1111363\b\DELz;\1083533[\SUB-<\ACK'\990099\997933\1061399\163012\US{3\SI~:X\b\37713!.V6rNo#^M\ACKa`W^@/\1072309\STXKP\bAAT_\EM\GS\amd{Pjw\190755k\1061923\ETX\EM\46993\17352e\43085z5tp\168311\"#\154328 \CAN\127520\CAN\1032856z[ \131518\SOAz\FS:;\7096\tc\1057832\CAN\RSw\10839\1054897\1006182)K\1012194\DC3&\1043568r:eE~\FS\EM\5565;\CANT\1062041\119103TH\STX\1015249b8t\1038814^?24Hb=;G\1023817\ACK\FS\1078\CANXGI:|hBb\25556:kj\DC3\DEL\95488:0\t`E3t\15514\EOT\SYNP\1072656-iw1z\ENQA\DLEu\16461\987644\180849\r3\SOH\1102173O\1070807F\f\1063727\36622\1085302\DC1\145047\byb\5063]t\185443i\1000194-JD\STX\155741I\92474\DLE\v\RS3\SO \1076385\1089483\nh\ENQ)n$1F&1\a\t\EM`\990854\1102491\95178a#\18847T\1029486\169304v*i6\RS-q*VAcXM\61930\155529q\37851N\1036348<\ESCr\ENQ&+\DC2$\FS\DLEhT\51163\1113630i3\31527\1011010\1084149\ETBpl\1054720\SI\SUBR\GS", newProviderPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "po!l\169867\"\EOT\174362\128174z\"mpt\SIf/,\RS^z\92320\1065420Mb>\1038416s\SI\ACK\142342\1036714\1110223\STX\182903\146931\DC2m\178832\b>V?\1091503x\STX\1051440F\996199-ZT_5\1040575\991138'uO\US\48044\1070465\ENQJ\1079452\fM\CANH\1096172YyF/d-)\95139jS\a\CANF\SYN*}\1020007f\1107059\DC37{X\GSaD\55035\NULn\DC2H\DLE\57851\ETBuu/e" ) } @@ -461,7 +461,7 @@ testObject_NewProvider_provider_13 = "W\NAKx;LV\131199t+#\2103.~\1032247Y+\41648\\^(CK@\\\STX|p\NAK{\\&j^\1039047$P^\1011763\1094679\ACK\DLE\1020408t\1021953=\1054407\57413\161407dx\134284\7330\304O\ESCR\52788\DC4\EOT1GM\aS\1006329x$d\"\\jm;\SID\NAKh\1026266qd\SYN\1059481G\t\188836w\1025987\STX#`6\994888\1064038$`B\72206h?5?5!~9\b\1039803A\1095391\&6\173990\\9(>\SIq\21843\CAN\1105449Kd\SYN\DEL\185005\97400\173875)\NULt0F\GSF/\DC4*\1100775v\n\135438%\1098360q\985701Z\13600V'\47204\ETB\EOTFi\RS\1097738\33587\1037250~\134709KR\DLE\v8\ACKA\1048461b\GS\1069935E\1093436\1073421\SO;\1041631I\1059359%~mH\SOH)\DC2,\CAN/9MZ\186665h\RS\ETX,{\ENQ\45529\&4\48843Mi7T]B\1014292\1107980]\vU&<\1062837=k^\140201\ba?)\97208\&8\GS|\a'\13766}@?hL\44203QV/?p\FSd\1072833iv\1062235z\40134\&77Qj \SInhW\tu\SYN%K\171249]\173157q\SYNft\1055800\DC2jS\NUL5q\173146\171547\ENQh\1056381\11907\ACK\195098\&1n{A#XQff\11340\r\1031758\&3\ESC\SYN@z\NUL\ETXP\161082\2121d>|[U\1080516I\4150\1108384\118925\984455KyRA\14537+\FSk\22960\164720\\nQ!\1048917\156304r4\1031804?\bkO\DLEi?JR#V\157527Zp5\NAK\"\t\t\EOT\1081846\vC@\999902\ff\1080966\ETX\f\13177\35834:\EOTf6\DC3\9643\54350\1030754\1007518\&7WnH*\24639z\187301S\96495\EME\ETB\t\DC3TEf\DC3CP\US*\95293`\1036014/\FSa\1030792\"\DC2\1037136\1104871`\173563\FS\SI\\\1067413$\r\DLEB\83072\1020617\168925\f\EOT\94696\&6~\r\1061388\18245@%\1049452\ETX,I\DC2r@\1094492\&1\45509_\46695\1056680\1003378\82953\ESC\f%54pb\DLE\67690\tQ\49471)\1069238~\1043376.\1095857`\41061\1092049G0\995302\1101979uj\DLEw\SO\49009^\DLE,;\\\DC4q\991711m\NAK!j3t\t\1065021\r\ETBn\GS)\1007482mT\EOT\ACKT\EOT\GSh,\24104\EOT\5452\"\DC3[\26881lI\125058\997657\58945\&5\STXy7z", newProviderPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\1012148\996993\983127L\1091820\986234T\100338\32163F_\1074194\NULV\45203\tHSx)\EOTx\1018765i[\13618*\1036707\SYNt]g\1029101\42675;\DC2$U\GSY)\137649\DC2a\bey*_\22406\EOT\986586\EOT\63521_\FS\19309(\DC3\1100285\1018311)`\CAN\tc{*\120605\STX\1035782\ETB(\SYN\f\DLE\"\ACK\177597\DC3\987765{A\DC23\b\"k^\ETB02\169370\1014188<\DEL\RS\SUBd\SYNKh\41799L\140775\SOH\1062973\1017755cG\GS\35290\17766\&2l\1090993\170040>\6743\&7g\1107547\1043309HP8t\NAKA[r\GS$mat-\1044321\DC2\ETX?\171930M\190941!\1008129\1039999\&7\ESC\CAN9HB\70804xL\1106268+\EOTN\9759\58857H=\SYN^\18372O\131331\STX`n,\SUB~+\SYN\CAN\1029939<\EM\DC2\SI>\1017104\&9\SO()J[=\f?MBFQ^}\1106639,\1083228\1091741\a\5309\DC1\134199\ESC \1069328fL+;\176749\USp\ENQs#\4959i\1063701[,\ESC*\n\ETXd\987841#v6HW *T4h\r\DLE\SUB\EM2\1101029\1036281\14006\ENQlJ~j\GSd\EOT\n+n\140173\ACK4\DC1(\ENQ\1001490\1010284\&7y+\DC2q\r0\CANeF\SYN\1037799\136556Y\GS]v\167666\v\7565\1035894cY\SYN\STX\45265R\139637b#-\1068999[P\SUB1\ESC\36885<\999558\ACK\r\CAN<\STX\1073640\v1\SUBkw}\n(\1102782x8N\SYNS\FS!zw\EM\1092262/\b\12914\848o\176997\1014722\1037098\1094468\vwN\FS?\1082514\&7Ml\991879DSr\1083083w\DEL\ENQ\fM\1018937\1034640$I\143124\179763:N\CAN\FS\1067436{ \NAK\DC3Q\DELspHP4F\b\NAK\7459TI\DC2\SO\SIp\160267\984188\&2&w\152534\&6\GS8\16256-\t\rA\17296c\re5\US\SOHD\153968k\150966&\62659O\59796\1075499\bL4J)NSkhAY,v[\1046465wPL\1108537a\EOT\SUB\ETX\RS}\aO6\FS\ACK\161611TkjQ[2\RSY#X4j\SYN+\1048030\34961O\119132S%\"g\ETB-\1094762\98031\1095039\16595\SOH!&\1080355\&3$\SOH\1060654s0g\ETX\DC32\DC3\CANkv\59338\&5\163268fI\189340'\RS5\NUL8\1016814]\22506\DC4\1078481~Qs}IGki8r\EOT\144634\22886\60044\GS6C\150162\43488\1040501\159569\8012DP\SUB+~\147614|m+Wp\131588\ESC;N\1085812@\EM\71307\46826\50528\&1\62468\&1\1006213q\994239'l\DC3\1003448Q\994205\1088573sK.\nx\ETX\bV\1099913\DC3\181734q{\EOTW*l\ACKOx@<\142079._~\SUByJ\DLE#Fw_\1038494(i0\EOTo\31194 \nHy+\ETBpF&}\DC2O4\SO@ZDt\r" ) } @@ -496,7 +496,7 @@ testObject_NewProvider_provider_14 = "<*n\SOH@\30958-\STX\r\DC4Wr\SI\1007828\DC3\RS\1067604|\US\34835\SOHE&\153263\1036536\92409!L\ENQJ\ACK\14244\1031101/>\SIF}5u\US\US9\DC4\174526\&1\n\1030274\1066194I1V\1016260m\ENQ\97064g\ENQx\DEL\998092\"\GS\1108808c\62271\1029396\r-/\SO\21439Tf9\99795\ETXa\1038738\1012785q\58411\GS\1042921\SI1\185051wL\NUL\1005866` o^\139188D\r[!Z\EM]\175566\SOH1\SI`_xlwX\62841~\983117\DLE9\1031057l\NUL^V6`\73045\1075428\EOTD oo5\EOTu\DLEMA\1022305\ENQ\EMi\13900\ESCuc\987332z\63131\&7t\1078035W\134810e\v)9\1047085f\96604Fn\164370\DC1\EOTe\68756\ACK\EM(\t\67712\153632\70287\ETB\128139Eb\1072526\&1\CAND\NAK>mqH\SI\998818\94648:\DLE1_l,\r@O?\1030904\GS\ETB\1005437\f\74188A\1087068)6Y\31566\1086717N\n\SUB(^Z/~o\NULqNdf5\NAK8`D[t_s\176045\nb=*\rQlb\EMh\1029213O\EMRK\61627s\GS\DC2\SO\26774q\18070=J@+\nz\1066073\47758U,\SUBq\1086977\59546\&7>M\vY\1109677h\41719T]\1110540[-+\34138Z'qW7%\1099155j6\\u\DC3\EOT`k\180585!o\1091363\FS\f\RS\149577\&1\182629\&5MwmP;%O\1321]Wp\38640\997816\97169)\DC4\50555\1023202MR\1041904P\ENQ'\SOH\SUB-\DC3d\1089710{\185850'<\SOH\CANRGw\154963\18264s,PwtQ~\v\FSN\EM\DC1@\6151R\157258@\ESC(J\r<\6101\a\EOTf\1026350\44174~t\SI\1076269|D()\987977\&5j\998682\181526\CAN\71219\1075942*\1076536#[0\59233\1009694\FS\132084BCWP\998330g=t\119272\NAK\983872\992436\170639V{q}#((\97889\&6\1017885?:'7F\ETB\13285b\1098369D+j\ENQ\RSB'=\1012088\DC4\1062545\1058232k\1098846\1049476SB\1049144\DC3:b\996836\46172\62008\NUL\60444\ESC\1077185n?\1011254i\78840\&8I\36251[%[\RSv /\21654S\137362\176091?\1110438\CAN\1002877\58915=w\1001772V\1000514\53656\b]\CAN\NAKR%\1099968\1095832}\1063203$\188197\NAK\1064833\&4\\\EOT|\140908Ti\1044678\"\1005661\1046781\ETBEX g\127932 \10082X#q2\1054376<\DLE\34304\1063738H\174496\SUB3W\1059788A\185024Rv\1070771#\154633B2\tF*\141620wibh}+y4c\SYN\155689`4\1047660o0}\1059893\40124EC$-\DC4b\DC2?CF\nK\1076070\1053238\99748Y\STXn7H\68523p\1096385\DC17\1031359N%\DC3O\EM{7\1101262h\95725Pn\162776\a\38956_u\162795\27940\SO\71300;<", newProviderPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\NUL@*\988888\60665O3]]\DC3R5\1100473\fi\n\52295CM\DC1r\GSM\SOH(\7837\1009970''\1009430\CANd\131985\DLE\44680\134922\CAN\52334\DC1\EM\n\120079\ETXz\ENQy.R/SY\184388\1099078q\f\DC3\1084664D\65907yrQ{\STX\"\988717\1101264[\1090754\28097\13928\\\DC486_5#\1112391RVq[\995776,1y!\46391\&6\1098357kPX\td'z4\t?/3\1080232\1027883 #a\990167\"\vyn3\1045904\DC1\39168,\54979~nr]\\\STX\ETX0$\39765\&0nw\b\63655\52345{&2J\DC3\SI\FSY\23022tR\"\aj3W\49447\b\1075793e\995006_&6\35718\1072621\v\DEL\ETB@\999269l:)\1023775\EM\ENQPyp=wb\178898\131275\&6TB\DEL\USe0\"\CANyb\1007062u5G|\1078610\RSrexL7C\DC1\21904\95639\ETBxa30Qr&.\54962\179605\US)S4\STX\SYNL\142486\1086193\ESC\1099834H\994133Bt\1043215op\1010932e'\CAN\174666v\171038\&3/\1003061\1006550P\128908[o\158079\165762_\53184F^\2557\&80K\RSy\CAN;\ENQv\DC2jp\172951,E9x\26530\1004321m\r.\DLE-@\DC4\NAK;P\ESCW(c_\ESC\rl\1079440K\163685%\1008965p|\FShtf\1001856\NUL\DC1=\ETXE\DLE\545fgj\1011740c}*D \r2w\160434\989466\156503\f<\73827he\GS\148162\1057494LWn\1108533#B\1109649\t\1035629;\178184q\\pr2\EM\37512\t+fS/\CAN$\1034463y \EM\RS\146673\NAK\1080618b9\68919\1004054\164153\DLE\185153EJJlgg\GS\170643\995180gO_p\1024807\SOH\1028326\29454\158229\&7I\SUBh\188999qCk7\994783\182522\158185N\1028031\60002\997229c\47190t]|\1061071as\1067993\12397\1104481\&5!w%\183013*\CANX\r\1011619R\DC1\137554\a@#\1042651\183219~\SI\SUBP\RS\1078893\\\36828\&9s>\SO##\RS\NAK\1031948\1084602U\10637g\f{\SOi\985716\v\41790\17576\995331\ETX\GSeo@E\RSn\DLE\SUB\22688\DC3PZc(\95975\1079854\992997V2\1010919z\1112130\SO\24848.\1072164\ACKY_\57589#`&\a\SYN\1113381\&5^\994232\EM]Na\SI\1081598\NUL%\62408\&3$o=\131232\ACK\1030071\1059534\53221%\128097\SUB\CAN\SUB\1101375L\94279Q/S\FS\998041<@a\FS&!6\177851~\1076359\63636\17500\v\144004\177924\b\DC4\EMb\1096546\&1\187997\DEL#d\RS&\r}\99877\129542%t\154521U|D\DC4m\183102\NAK#yC\DC3amKV ?&pR8&4\60835=\1041477\SO3\1037010\43983*t\EOTW\SUB\DLE\1004660\989420$T>*\DEL\DLE\f\13059c\83481\SOH\DELG\vV\28484\"LB9\b\128051+\997889\&7O+\26560\n`q\DEL-8 iu\159483/\b\1022698\DC4\14906E\NUL?c\26013\rK\127979~\v\175845w{\181255\"M)GU\1017157Y_'\DC2A\1018905\1113059\1073846z}\61520UukH:8J{\\M9}\SUB_O\1010643\1002699\1002442A\"\tKA\131151hHA\ETXt\183766\DC3\1109451\a\998436\1111661\DC4\984796\SI,0\ETBPs\162110P\1100762\26332<2Mf\181314\&2\100692MQB\n\FS\SI@\CAN\SOH\DC3/\SO\SUB7z\1058036\1096521\&9(1Dh\984301\61296\41737g\58322\SYN\RSb0-*U\1009736\v\990248i\1085978\170328D\50254Q\1279<)\138421T1F\DC1\1095280$\EOT#\5075\&8\SUB(Y\1096693\DLE>\1083615\50264\68373\96452AC\1016258\&2\180593\&0\r\DLE\1047223{\182103+\NULy45\1016793~\70805Poa\20510T\nj\41413\tf\1035917O\1098693\f]\1069241\ETX\46803&\r\78306yF_\US\DLEASe~[=/\US \EOT\b$\39764\1112829g\r6x\DLEP\RS\DC3\30137OO\1040211r-ARf", newProviderPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "U\DELL\1031997\3965\1044182[\182989ki\1105227\43452\1060867dMzz\139329\DC1\"!Ut!\1028584\131087\1022923u\149780!\156581\1024037K+'!x*e$^4\b\1071937\1069725&\"\1073836\18493P\SIK\1075979\147223\a$\n\DC4'%\151627\SI\14127\1043974\USq?\163384N \994666NAsu_f\16443\DC1S\ETBJ\ag(\142579cee~\1053954W\7128\SYN`-B\1108390\96984Zc\SYN\EOT\174552.\ESCt\16487g\DEL\CAN\94849\nt\vKD\SOH\1034271\98659w\t(\SUB\fd\SUB\1025124J2\ENQ6;\DELe\1044132c\SYN\1001450&+gU`hp\DC1W\991261<\vq\1005716{\1109069vM\1018971\12099'c\150736)\SOH\48889'\bXw\DC2y-eil~\1016478s[\66660\1059951\1007726\42327\DEL\GS\DC1\DC1\NAKCWK\53431tJu7\1083032\f[\41026\FS\991253&\DC4\177728\170128\187772C\EOTdb\DC1T\STXt\bvs\GSc1=`\SUBKmu:3\STX\1056258\39200RMW)\1054845c$Qk\1049257\&2\r+\95965\26879EC\128237R\FSm\SIPG\ENQX\GSN\NAK[Z3aVL?\1094684|\64372\1755\SUB{\SO\177529\1006397\26736p\GS'sp\DC3\134636\1041903Y\1094846\164802KD+X\18730\FS\147280vT\vO\156837\ENQ6KENY\SUB\63088T\996443\&5\1030054\SYN\32468K`g \151686\163874\DC1\GS\DELh]*(w0_\179610=\ETB8\12974b\1031125\&6\50363\142908P\1034807\1091246\41373cI\DLE\13509\DC3\181871\24514\US\158574\"P\DEL\ETX3\993828c\1014804 \a\ETX\2313z\SYN.^e(Wiy\EOT\SO$\n\27897>\1086272\7381r\1043910)^AE;\61438\DC49\184834\SOH\120628\r\140592$\ETB\1071703\ACK\61909\33962\&0\148931\&6\83441\SO\US\34292\n_\ENQ\1041026\ETX\DC4\1100537u\GS\26703Q\154632H\SOH\ACK\a\1106457jiyh=<\ETB)<\US$\1009945i\DEL\173200? I\59667\SUBp\EMf4V\160676xP\64680M7G2\DLE\1063464\1076234\1000247\987230JF^\1039383\1059239\DC43\EM\164684\ACK\a/VMhe\18744Uw\STX6r\1035269\181211P\SYN>]Z1\USe\23971Os*3\1110545\1082930\83509\148700\1112586\65687=\1043119\GStDk?\153927\&9t\DLE8\v\FS\DLE(\10191\146750NCw\DC4<\1103983C+(|\1092605\1062435&).\SOH\a+b\bn'`M\58133k\1052890\DLE\29087\t\98313h?\58108\ENQ{/\f\1017948\&9\NAK\DC2@\50474\984618g\100141\&4vKVT\ETX\SI[O\EOT\SIay\b\987314\\s\f\1094655\ETB\b\1079118<|P\181328\ETB=a\1003480\NULD$\1022014b~$\1019186\154912r\20036\"\59634R\1032464\ETX\DC1\31209M*sC3\74243k\RSo\EM,\NAK\n\174795\&5\ETBg1!\rd5-\994199\189635C\1009049&%zz3\DEL\128716\1048947\194977:`M5B\1003865\2713\bg\ETBQg\1094900Y\184417kZ\1053719\ESC\111238\"\f\n\SUBFJ\STX\b\DC1b=\rE6N\3398\74574Ns\1012069!J\1067627\SOH\vVT\STX\1001063\1108342`2/\21824\SUB\1001304za]\GS)\1069987l\1015781\72979q\1056952\1060194dtj\1029226:\DELg\38261\8352\138598*U\DC2\SOHu\RSI\1048699i\38798'.=\ENQW" ) } @@ -566,7 +566,7 @@ testObject_NewProvider_provider_16 = "X\ESC&%\SIr\1013622tGpJDG\96662D\ETX\149539p<'a\EOTkQus[=\1064920\EM\DC44b\SOHf\41601xx\SYN\1105345c&Y`\8190&\161851\SUB@<0C\b&op\DC2).q\fR?L\1038536&\1077065\CANQ\"k\t\162224\1011923\172199bg\ESC\1006816\GS\DELAK\DC2Y\b\27439j\20485.*WG\1001813K\1051809\FS\USZ#\CAN\SUB\143399\149513_=\171023I\1111437\b\17184r^\ESC\USA~\DC1\r\48470\SUB\"\SO}\148083\v\v\190769\v\1025287@\n0\v\"{e\ENQf\159560D\98342\1081596G\20826\1010345\1034684\29478RB\16962\DC4%upl\\0\npL V@\1017155 #ZI\60205\72996pv\DC2z.+@l;\46388\989303~\DELQQ\DELa\29366\SUB\172927\nk\40337\STX\1011577Bw\39995\DC3J\58482\97623w$\CAN\CANhJ\DC1\1070950\DC1\SOH\10490*\DLEj\SO\ACKT\1031969o\92518\1016845\&1$rQ?oKn\996456t&\1019633~\GS3 R8\172847\96523[$(\988171Z4\1038490a Bp1\141901t\1095317^/M\60283\145389H\DC1\DC1lW\995784\EOT(\1046615)@4\1096597dM\1033217\DC4 0m\STXIkmV\"\\\154503M3_\SOH\99718\1075024\1051272\EOTk^TU\ESC1D\DC1E$\DC3\1066144\EOT2''Y\1062130g#$e\41156<*YU\ESC]\DC2 ],\1047094\ENQ~+@\rZ@Uw&IMz\995436\US-\RS\1007199\EOTArWv-[?\1082462\US\148853\DELw\ESC\FS\DC1\176133\1040362\1103573\184875\ETB5\1064090\1082414lr\NULka&Y\1078886;r)U\STXx\ESC?Uw\165234\DC1\FS\1076798\1095261'\69660\157987\1058027\1001344\SYN\n-\44807]\160004\NAKs\184731<\"[>g\"\SOH\STX\60522\&9\996191dY\1083275\CAN\45431\CAN\1092264$L\SUBIO}\n}0(5?k\132369\993416\1030870\ETX4\43016n\n\49404\146810;\1079380\r\vP^u\1113625\DC1#\14950\GS8\1012920\SUBU\1047742O`cT$o\DC3\1025443\150075\12964psO.|[#\SYNX[\n\ESCM, \1101174&y,\NAK,\61693`d,\CANgMNr\NUL\SUB/\3183E{\11337\&9R-\SO\DC2\1080652E.\n$", newProviderPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "ScV9\b\28227\DC1/\SO\1082245\DEL~\b@\SIN\EOTp,\f\65205\1018180O3\38816cu\vK\95234\180332\999755Sfi\1093574&\1101969`\FSG\nx\189084\&0\CAN\r\9456>\STXxC\29225\SI\1090313\&9\1006724\bH\"\CANIA\DC2R\65943&\133691%\RS\995833Y\12698]*\EMf!g\ENQ;4\1064020M\63310%\1053835\1009127\165604h\131187w\1029436\1060222\32086\GS\ENQ~t&y\66036i\b\1037992`\55290l\DEL6\1076125\1052539+\9788\tj\120921;X\EM)@m\1070590\74001\1028515\&2`\155271\1025681mw=wbEA\SI&\ESC4wE\1063321SN\17555ZQ\1049768)V\NAKQX\FSe\t-I\RSeY{F`h\NULx\1073233\29275\v%\ap)tXiOV;+'6JKh\"\ENQ\DEL\\h0\ETB0\1035905\1071561K;\1034331\&3]\63716H[\1010772O2f\176755\1078605\&9iOa\ETX0]!\1073255\ETB\FSs>&\1018144[\178366O\147558/k\"h\1054719~#'G\69438\39761vmN\143441v\GS&\SYN\62401\ETB\"jK\1015043#\NUL\15819?\1026355emU\t3a~\1018961\187512\155643*u\998993f_8\1074191\1610\988932e\1081085\153783\&2\6584\1022400+c0\182420R\EOT\STX\38370CW_3\187930]\1017105&\173029qU\1010649?7\SOH\50543\DC2gc55u\20745\38427\51756Xt\DC2H\69244;\ESC\1043616\1004496pP\1038981\983553\134052\1079869\ENQN-\SYN)06\1046451\989374`\36070YnY#(\25216t\DC3&\ACK\63160iu^o5y1\194968#\69945=\99282g)\42686\158228\1081922?\1065914\146177\NAK\28022\22688 \1010793\DEL!K5\1062581fk\t;\1041714g\DLEl1\EM\DC4Vgr$" ) } @@ -632,7 +632,7 @@ testObject_NewProvider_provider_18 = "L\STX\SI\r{\182574kn\54948\1112345VXQ\ESC\DC2\168966\1030248\ETX\29056'\1051323\54658\159251\SUB\EOT\984766\ENQ\f\ENQC{@\189125<[a\1062371gIW\158726!R\1085578\73018\1095069]\1032243z\18237\74513\SI\118992N\36162\DC4\SUB%1,T&\1073768*; +Lu\SO\984660nM\1000448\CAN\ESC\SYNX\STXa$\DLEacT@P\1040057&\132164t]k6\50846;7H\3525\132215\DEL\132411MY.\a\NUL\a\1046590B\SOH\61091Gdb=\NUL\DLE\a\nw\DEL\SI\NAK\171550\136285Bci*\ACKA}y\DC4\b\992897\1055635Z=\1010352S\1104802Rs(\EM\DLE\DC4\1006612\"zA;jEcJ\97062\\\1086318_0v\SI#P\b\1054348x\15378sx\1034676\187841\146049\186211$W\1050036S\1006908\1108979|6-d\ETB\a.\ETX\1109068\1088252)\FSu\1101581.\ACK\1009497^l\20011\&0\1061172q\95615\RSY\1045270ag\38508H\7662\1024914\1060305\&4e\25038aX[#*\150544=:z\45265\1008116\1099580\&7\ESC\ESCH\985321dl\48337\1087757\EMO\156474\DEL'\1084047\1014854\&7:]\22790x9JLR\177154\SOH\1111189eGhco\168958\997980\998816\191085\1063191/]\SOHR\v=B\176884K\SOH@Q%i\SO\DLE\ENQ\USzfh\1021329c<(x:_\1025949$\RS|?_\f/\95240bRvD\NAK\1021075\&66\177037jr{ \FS\1042048h?\984080spq%&Rxm!i\DC1@\1042700\145738\&2d\1098910xn\1004958oU \987251\127283f\143815\vZg\FS2\1087485\14445%T\986010\DC1y\35930\NULB[\DC4`\DC10\STX\133655Q\1088929\DC1\1106239_!G*\70084\71862\ACK\1104224R\SOH\SO1RV\1041740\&9<2&34b\DC3D2\1098671Ow.l\RS\SO\rAr\1007207\136610\ra)\129354]!a\CAN\\9Vp6\1035773\1033675M\DLE\"}`8V\1093475\54144ne\ETB&\1061443q\66316\v\21669\EOTDc\42096\&5a4U8\DEL\996381fcm~\2441C\1072107\984736\&4(\1058291Pk\t''M[\33579\148987\&1\162166CBSt)k\NUL\133601\1067835;s\28250~8ml\DC4\580\984312\992382ik&1}L\63264;D]K\184410\SYN\1101524m\1086014\r$\1006862\159405$|\134896M\1089297\1073323}+=\CAN&\1064854\1105965\&1\155415\140958w\ETB\177782\54963\v\164388\1111379\1102914\185569\vt\1077972\&7d\158949e>38R\1095677\1052668\t%i]\CAN\ACK\f\70075^\SYN\ETXt6\8189\145336\1113611\NAKfgC7(\DEL-tv{mb\1096092=\SUB~}L]\NULPpH\FSqW]\nW\132420\"\992416\rgr)n'\SYN\EM\EOTTNk\40031N\1008484Z\1045093\1086526\EM\1025350\162013\13065=F@!`\ETX;\DC4F\SYN0k|8\RSnvY \9231S\n=\53051!l\ESCnUC:[HV\48689\v\132253\DC2N\162194\FSzV\1034180W\GS\1010999\178061\1065310/G\NAKX\155696\CAN bA\182702n\EMtN\135302%%a\1085149\&8`\1021915{_\1053343\98622\NAK\SOHBm\fk'Uz\vIi\EMy\1102591x\CAN\34225\DC2RmF}Ej\CANS;s\US\1104917-\ENQGi.p\1022704Y2\120189\ETX^VUx\18478uA:,T\SI[y\10483\187556/\a\996661\61864\fz\1031813tq=,S\13269\36567-0\1013620i3ld`*\994221\"\FS\FS!\EMLt'W=-MW\1078223\156247\63941\1101759&x!Q\141701\v\1096038\&0IY\1019948\SOHq\1093064\&0G\170326h\ESCH \DC2\70353\1068905\&62\1080445e\DEL\1026847\1027522/_@\155198\SUB\DLE\DC3~(_\RS4H\ETXK\73112}\SO\1079756Qb\1113993Js\f`\FS\1097049\162022(\ff\FS" ) } @@ -662,7 +662,7 @@ testObject_NewProvider_provider_19 = newProviderDescr = unsafeRange "0", newProviderPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\rAc\1042188\1090391n\179482N\f^&\RSHl\1007350\GS(V\SYN\26587\DC4!\DC4P\917878\131600j\156578L}D\SYN#u\DEL\1043116[\ACK6y\1106909d\1024893Qr<\1005754l\21273\&89\1086500.\986065JDWC4\b\95048\RSW~t\DLEx\SUB\1058027H\SUBY\133847ej\1014785\f=\\\"z\63346\DC3NFooo\1017922\1010464\RS;n:<=fq=\161672\STX\992600ZV,<\139419\\\1033183aL\1004843`\ETB.8\1077260\132383\&0\992239w\NUL*\1076012![\135952" ) } @@ -697,7 +697,7 @@ testObject_NewProvider_provider_20 = "*S#b\74541\r\DC4\1072925V-\34350;}\1053334\1057549*h\"&\19378mM\36413PWq%v\ENQ5\40637fk[a/\1013322\100662\"\US\n\1089474\DEL\119911S2/O:TRP\DEL[\10083\1027814)\RSs?\r\ETXP0u)\ACK\SYN\145993S_\152944!I+\tj\54703g\187008\58274\28027Hr\SIAd?\DELJ\SUB\ba\121392\1016486Be~e\60888\994938j\DC4-nu\121370M?\69911.NVdMeOTq\bp-\1070087\1083306!@\"\DC2z6JK\162658yM\DC3r\1057085\r2/\DC4Y\"\SYN\bO\1091905t\1085544\DC4rLS.\STX0?x\45330\1022438[Q\1086255\DEL?\RS7\48888=\DC4@4\9674F\1013333uH\1079345\166828\36384e\996621\&9\ETX|\rL\70661\ACK\18517\SUB\ESC\168536$\142878r\DC19\\_/v\72770U/\FSWF\990960u\30070\\+\DLE\32139\&6L\ACK'bc\58714\1036754\989962\aC2\132227\&6\14310<\DC14\1066102M+\EOTP\fG./9z`]\1059616i(\35512\121389\&2\1095506}RBwMyBE\EOTM\144042E\51844O$Le\DC4A\1036114xj\16172\1018320\atW\1017261xm\36950v~\ESC\t5\NAK\39959?\170917g\DEL\n Y\180069\1041733.dG\STX\DC103\1058041[eN:JO\n<[\164646\1074508c\\hV6@\NULM\139064\US\68657]x\1077972uJk3\58112)\STXu\f\SUB\58392\&69=\a\\!D3\155981\1098230[U\DC1l#A\132142@\DC4_\ETBb\128395\136223ed\\\1007712h%\FS=U(I#\NAK\97056\NAK\SUB\ENQ%J\a\ESC39}j_\49034\NUL\45579|h&i{\DC4(\RS\t\1047106/h\50694\93978}\r=eRH\1107604\&6\a\DC3\1046755T\b\141122jD\1109067&\DLEk\161435\1046844)\165661\1052461E\188599\186366&K%]X\190285^R|+\SIUc\NAK@\STX\fLI\1052555eH", newProviderPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "Wx\SO\"\DC2\"5v\v\151001\DLE}\RS\1039252w\1100059:^\43558\NAK\\Z\1050333J&\f<\"\99267\1093524&daGH\1105983\RS\1002907\1097720~\DLE\ACKo\986821\59705\49549L\1086772\13384b\8051/\44192\179295-#@_\7758\n\129644jdIs@r~k\157114\1021838X\DC2>\184946\1065626\1071906\"\nN5\183352-d#67\ACKF^G;5\185762f\1034214\RS-g\140366eg\1095591J(0<&\\\162255z\b\993604\RS?Y(\SI\180344ORXUf=\USxW5=\181334\&3\1036314N\STX!YK\1001668,C\1033503#p\1013354\50859r\179560,<\154639\&0\1036571\996797E\133725\rh\1042728\SI(\DC214?pJMN\DC4\SOd\DC1\7595\EM\1005908\128235\b\94673/S\134129\44978Pd \DC2fj\SUBLm'\DC3i7\139357)2B\985360\39935r7tLJ\CAN\vO95\NAK\n(\169817\1030028\23965f]_\172078\NUL2FQbNS=6g\1048060P\142526\1063467\985597\1071417M\RS7\SYN~\ETBm\1037229\ACK\SOH\vkgmBp\ETBw\24748\169199f\1023790%Q\EOT\140598kP|G\154373\ETBB-\nJWH(8)4$\989238\DEL<7\186902\FSI\bA\DLE\r?\1019914\b\b5\ACK\1009640d \SYN6\1102454G\CAN\b\t=qG\1060976 D\STXvV\tYpg\1016558\21533q\n \ETBL\1056539-\1111371\DC3\1024221F7Q\1090844]\25539i?\r\DLE\ESC{=\1107323_?e\1079481%\SOR\987580\ESC+\SOf\ETBq:g\\Rk\39309\173918[l\NAK\1087232VK\njwp\EOT3TJ\3983Ej\STXR7d83ON\ETBq\29567\EM\190684N8\n\SI\1030588u:G\42235FZ\FS<\NAK\194749\&7\1086892tH\1047800;hbS{\43951\FSsMs\994770\&9B4\1052158\&35c(~CUc\1016298\\V_XD3\17286\7398\ENQ-\1063783\100891\&7W@\59062)!$%v{\f\n_I6\1088622\52764]r\1105300\61079\STXGi_L\ENQ@tr<\35715\&2Dr\16519\\\v8\49277\DC4\1069631e\b\190386\71324srN\34600\26071Qk+\36197\999209O\\c6\1032813X\1026685\1074390VV\\\999471^\1105556\DC4(P~y\SI(\nrO\1037710U=$\1038971k\1011736\&7.\NAK[dn\1061566\31927_\NUL\997265\vNVd\54706z\1029333pV6\RS\166743#/m\1065646w\NAK\27792u\144303\SIs\DC1\136497^A\95500>\SUB#\EMsC!3#\59953`\159877q\65860\\VrnT\DLE\SYN\1060441\DC4\STX\156538\1003845\DC2d \1028483#\CAN\179878/k\14627X\"I\SIO,`GU+\DC1\DEL\"\n\47090n)\ESC\1059861x\1018430\1097583%\DC2\SIVr\f\1044385H`\128647W\FS\NAK\1050334vii\FS\a\ENQ\1005180&d\GS\146823\991562.\1090052j\1008159$=a_s\DLEQ\1020394\SO\f\ETX\1019724B\ENQ\CANL\STX_ZX\NAK h_sGj)\1047298|\NUL\SI\rlUN)\ACK\DC1`8\f\1018610\999181\b,A\DC1\tt/0lT\1071777\a}\SYNj\SI\az|\ENQ\152944J,26\1022981\ETX9\11179\&0\EMw'\NULO&g\USF0\1001389kg\STX\DC1|Q\1048680\SUBM\131896\1038590vuPgVp\180615)/\1060369\DLE\61681\13692\993364\b9\FSiU\NULz\fb\22561bP\60643f\SO^\\\1008115\NUL\ESC\STXd\DLE>\1040220\1103806\SYNOIc\189228l^l]\1031063JY8J\1036381t\70171AcI\SIi]Bh\989297\ESC\140714R\r\NULz\ACK\1088597' A\DEL\\feuBiG\993059SDoWa\DLE\ACKW'\ESC!\"erY7Y`\\I\4948\"`y)\1045668iN)Z\1012930T5Q\1076971\147595\993658g|)\US\1003237\ETXJ\39701\1106744\NAKY_\a\vJL\1027083\CAN P\ETB\1078145+%.BF\153802Ay9M\98346\1008748\1104393s\25042pI\1005219_\1002925\rM\CAN\39386EZ\58119ZXS\1105928\RS5\DC4b\97371\SOH\1106103\184422b\100457\\X\STX\144554;l\49694xQo\NAK\138070\SOHJ\989399XE0^&\167968\n_1\USS\DEL^\SO?W9~\1099319\DC3=\DC2\1068564@*\155081yLc\ACK9\15796\1059875\RS\98699bV\1105827W\1005933o\1072486\FS\1000032Bmr>\1053735\1030072\157751\1056352m\1072516\142673G\STX'\1013038\&3K\"%Hk]\61616f\178900\RS[\ACK\1112290\DELvq7n\146589}F*m\DC2\ETX\1038640w$R?%\1048405\991130T%\1086216o\DC1\139752G\1070667\SYN|\1085639\111068\1068350B\a\ESC\1063604\1088194T\1019860@\SOH,1\1094852?\DC3\ENQ,)ipt\146004NXe\DELd\1015278\DC2 6\984739\1037131\&14\31204p\f\23571\134629\60442q|\EOTh!\DC4z\GS\NAK\1046271\RS0\120694\ACK\b\DC1\1057519\ENQN\22139P\139372?\",\955a\ETB\f\ESC+\"JpO\39005\1043690\54002\ETB\ENQ\1093734\CAN\1005666\SOH\DLE\DEL\2414I\55278'=I\EOT\62705N \SO{Vtdy\DEL\"\163827\37015hC,\1053062i\NAK!o\SUB\1075233(u&\fz\1049825\ETBA\1045850\175742\RS\US~A\40004:D-\ESC~A:p^\1079564\135140:\151246w\1025133m\61429>\6832:.M\1073891\10252'\CAN\1071280\24496R\1003679P\1024693*\SYNq\GSUX\1072603|\r\US.\1062109\NAK\1109389W~5O\1000925\STX\184929s\1006565\150194\DC1r\SUB\b\1057499ZN\SUB\rf\20741\SOH5\GSJe\66771\1067879b:z\SI!\DLE\ESC\t\NAKd\v@'C/6\DC4h\983790@", cpNewPassword = - PlainTextPassword + plainTextPassword6Unsafe "l\992017jfd]\CANOG\1037448\9679E-FG\DELb\DC3c\168198\179584\r\EM\98126~\tW+\ACK\33446,\1082952M\128295s\1000699M'\DEL\STX\SIz\ESC}\EM\154156\CAN~;k\53216t\ETXx\149224\132305y\96580\ESC\DEL\142174#O\r\bJacD\GS}}M\n\aY\1033126\&01C\EM$E/\NAK-B\175854I\1023972\1070217\6129\1013299\184147\&83$E\1009863\22083\&0\SOn[T\GSB\DLE-\1007136cZ\46079\174945\38508\985022\173232\ESC7\1110907'/2\9477B$SL\NUL^\EM2\NAKG\1093469\&2H\ACK\DLE\DEL\43539\58839XR\1080271h,\78748\174767\1054239\1041868Y?\SOH8DF\EOTCh#;/x\DLE-\EM\bA\DLE\159735\bkX\SO\188157g\94522\172555\NUL:2<\166889\998353\1083925zp\SI#\1022316\48223\NUL\ESC\141660\1089351T\27451\&0bA\7868\v\v$&qMQD\994988\12182}\SOZM\ETX\179973.\ENQ\21913\58375\47428\191199f=j{\47820\1111986\1073477\100913\61587\1073940@\ESC!u\1077743R\7637EJ\1016579\1016763\DLE\1058455\FS_@\1076367V\1005325j1Z\NUL\1004454>t6\1079007\ENQ\1084309$\SO\DC3\98064\137557\47918M\ACK\ETB\172727r\b\1100397)\SOw\ETX3\1010263\DC4\1066921w5>\1035509\96260Ga\USz\1047694n\SUB>\t'\92445\r\148219 \1025075;(4!\1073109,\",\"x\EOTyf \f\aRW'z\133849\1048674\24036\ETXYx%\1080586\42394\1045626n\13335[/'\"2-2jR{=\1012515E;\1026542\SO0+\142703H\987291\24296@\1106752%\SUBNnZVt-k1\DC2\155707bTV\DC1k\1003798a\1071366\DEL(%*h#\1030597\SUBL\STXa" } @@ -35,10 +35,10 @@ testObject_PasswordChange_provider_2 :: PasswordChange testObject_PasswordChange_provider_2 = PasswordChange { cpOldPassword = - PlainTextPassword + plainTextPassword6Unsafe "J6\1063462\\\ETB\1078893\993390\ru.# ?_\DC4=\1064091E\a\1053958-\150000L\1061533\&3\1048772`'\DC2I\SI\31152,0K\NUL\\G}];\187087\RS\176174d\136667\1076870\t\1047442\48335.\152068\15337{\70067a\EM{9W\1063171\SO}'U\SYNB0a\SOHI\EM:^~\DC1\146030]\ETX\1068275E\ENQ\EOT\SUB\GS\38515-,d\1087008\146851:\1009309\DLE3\nn*4U8\f\f/\164529_Jq<\23032j\163500*e0\152734\DC3\58971v{'\57450^3;\EOT&yY\NUL~\194865\94514ct\33770o}\1015771\1008056?Y/<\SOH\1076816iJ\62386V$\\\CANd\CAN?\DC3n}\1001195\t\1017145\139912\1110640\r\EM&C\FS(A!Ke\DLEV]iB\136999\137089]\173378~\995379\&9\NAK/sY\FS5\165215\142850\&3\1074963\ETBX\DC2\92297\nzeKE\\\v3\128136\r\ETX\n\1110227Mr:M:\n-#'L\142407\121103\&7eo\57512\1043763!L$#", cpNewPassword = - PlainTextPassword + plainTextPassword6Unsafe "\STX5{IYj.N\1097942\1034521]}l~\ETB+\DC2XU\51470pB\121102\25755\ACKH)V\ENQ{\DEL}\ACK\SOH\991933\SO~YZ\1000116`vCT\CANU\3236<\NUL\ETB\1097339\SO\SOHgfBz\NUL\DEL\27119V+q\n\53764 \1025256\FSG\145697\184862~+]A\97342\70080N4r3ly$\EOT\1088639Mv\1002336\1050997$\CAN\DC4\23032\ETX\a\SOi+L\1016399Q4\99029c\1019418\EM>z?'\CANO\SOH\77963l2\SI-nZ\\\NAK\187610\&7'VHQ\ACKf\182917\US\1083627H.1\US*\SI\35903\SYNw\1090044\EOTR(Z\165917\1084964t\1085428\ETXV\184675\b\RSbU>\986822\NUL\DLEZ)X\137883\22578*n\1035455a\RS\SIvZ\CAN\ACKe?eB\998053\991381~\CAN(\ESC\a\1007645;\121445q\1042993\1070820{\SYNk\GSA\DLE@8#sG\1095900O\78803oKfrR*\SI)'\22262L\35087I\1030025;LR\988091\STX\917885\93968b\1033563\986558\&3SJ|\61654E\54642(}\985223J+\\ED\187753\182558\987551\&7*6\ETX9V5mm79s\b<5\146014\ETXe\1070748\\=1|>J \DLE\DC1#\1036249\174789\a\119052\DC4\ENQ\7214A\1057521\991526\ETB\1096433\159048=B\DLE\b\EOT\99076mR7\GS\\_{||o\191444)\1077352SJA5" } @@ -46,10 +46,10 @@ testObject_PasswordChange_provider_3 :: PasswordChange testObject_PasswordChange_provider_3 = PasswordChange { cpOldPassword = - PlainTextPassword + plainTextPassword6Unsafe "\1090639\STX\132492\SI/\183791\DEL|9\\\b_\1111257E\18560\1026473\37179O-9\SYN1L\11112\153612SYM6`XP\185928x\1091969BP\SIV\1094629\1099550\&3Dt\1051594 \185390\127986\STX\993509\1072672\\P\182591\ACK5\v6\SIW;\178969\&1\nE:[\182420\96819a>\1066339\17724rv\12096OvI'-\1062972\166884\ETX\SO\SOu{\999818\164786\"_\984751~_u\1023831L\RS?\1032751L\97379m\1012949\b\189539H\DC3\3277Zd8\CAN\173661j\STX\59973\STXU\b\1058747\EOT\39158a(\ESC\NAK\"\ETBc_?95\1048328\r\SYNpa\NAK\b\SI!\DLE;\GSI\b@\20755D\1058888 \DLE}f\1025872\989393*\\\DLE\DC4l4sG#{\175474O~3!n\1070630\1046460`X>n\r%\vR%\12500\GS\SO\b \NAKJ\FSe6y:\DEL)T!\163863\19105(p\STX\ETXS\1035778g.v7\EOTU\\\ESC{9\1029424\&7\1096509\182402 \11429`3\v'\1011010\6241H\SUB\67713`\1102842\SI\ENQ\36671XSL\5184\74631i6*H[`bk\187634DB|\1082864tF\STX\1095470\40232\24100W$\DEL\SYN|AjF\SYN\1060698\ETX\1002438\151035s)$.U++'\n\DC3C\1092666.k\ESCgAo?,\1098414\SUB(sBt\994422X\51349C!\1079241<\1003009)5\147358[\25065\RS\993654\988949~(8:\1099034\94463\67647M\NAK\STXFs\\\162758u|~\DC22@D\39863?L\SOH\EOT\CAN\78252_\95345+\b\1047730)f\DC3\147188r\SYN+=8\17990+\131480k\n\1004620kv\ENQ%zD\1087067Q\EOT\DEL<\DC4U\EOT\98368$\CANw<+\125229*\171804/\az[q[\DLE\ACK\132467d\bv\DC1\DC1{Q]\155471\n\rokp\CAN\DLE\180903\EOTNn\147253.!\63250\65540z&|\983968W\164923\1015875\47406\r%B\NUL0\62411\58998\989796jn\EOTg1\1030731U|\1069001.Z\147615-\DLEUdT\SOHP\ENQr:\993057@J\172264r+\22908f\189795\1008819\12565\1059459?!rR\184591\1059540d\1010396\153681\1087402\ETBms\157686SQ\SO\1013566\159622\"S8aV\t\ETX\69642\NUL\1018708\GSj>0!f\1048850\172491d<\1090475c-!+\v\157251\USB\CAN0u\f\1109289@n\US=\EOT&;\FS\1094127\147561Sc1\tkL\f\ETB.V.m\1102645NZ\1025114\171053=\9900w\SI=p)\983196\110627\n*\EOT\4264S\142455c`h\1064905\CAN\DC1}U\16412\RS(.\ETXBQ\RS\SI|2\DC2\t\58697\26979J\1059222\t\n:1\1076824\DC4\20071\DEL\\?AkiJ&\ax^0\t\RS\1008082\145465'\vEB*\n\133263C;EZh*\1096287\v\172007\&27nR\1030319'7\1067756\ETBV\1096588\SIC\25035\NUL\ACKcl\1015672a\DC2\1054753_X3\1087036\&3\174910mvT&\v`r>\1008758_.\28801\DELo*\USR@!g\5064\DELo6'\61374~\11251\1055297Rv\96087", cpNewPassword = - PlainTextPassword + plainTextPassword6Unsafe "\ACKb\65690.z\DELS\RS\1043455|@ou6\SI)^#Nv\173721\nK (\49735\"+\180317r\168250hgU_\NAK>.~\42075\EM\DC3rNA\1003405\1064814O\1080126f\DEL\170710@Mj\DC1\rd6\157492)<\NULZN\STX\EOT]\164074%Ki\ETX;\1074300\&1lkk:Mk\1032789{\1085268Hq\1095137\991423[\1046704\1069727Y;(\DEL<<\NAKGs *\1064528\SYNGz+\r_f\162493\EME\179877\SOHo\GSA%'+v\145346:lQ\183770\120729\1008624\54333~\46645a\18566h\RS\nEX=*~\b\38738\DELk\47265\26012Y,r=\SOH2\12321\&9\FS\f\61703\146118\1101857\41261_\177617~\150584\1101236rA\1057119JA\\~\t\67201\GSrIk%3\179007%\EOT=%\13237\1079931b\FS\1080016`}\SI\167993`@\NAK\1037573\f\179386{\984491b\DC4\191263\SUB`\tV\ETX$\34881\DC2FH\ETX4E\128495\1074641\155862\f SL\176565{A&\FS@\DC3q~p78<\1029510t\1072174&&\1064641W\62128T\148445gn^K\1032060\DEL\1073914TK-\1032280t\69863*B\NAK\ACK}G3W\119556aj\135982\&0\35872\48740O1\EOT[o>\SYN5w\GS\tEd\ESC.}\27602\&1{'\1023415\1007968a|\am\181403\bu\DELo5$07\tK\1101735*\t\bv@F\ACK\ENQrQx\RS\52315\r\f\18064\60859\1043018\42814\1082068\16599p,*\RS\DLE6\SUBH_\994350\SIi\145754\17091\1001085\NULUq\176242\\\1081511[j\1020996PN1\DC15+!\1067420.\34561+7mLuRk\1036698)N)dlu.^\1109734\EOTzU\1019326#3\1055275V/\SUB6^\DLEq\60060\153909j1\n\ENQ\143934\DLEC\1094908\174654w)\SI\1095019\17787\155204\\y0/@_\1036276\\;\1044245U+\":|\1008444L\\" } @@ -57,10 +57,10 @@ testObject_PasswordChange_provider_4 :: PasswordChange testObject_PasswordChange_provider_4 = PasswordChange { cpOldPassword = - PlainTextPassword + plainTextPassword6Unsafe "\1005314~=N\SI\SOH\a alq\ENQ\ESCj\163982U\133814*f\"\72875\SI\1015050\1072188\52409\177675M@0\\\1060741s\b\131914\&4\NAK{\EOT\125006\ESC7\1042522\n\DC3\ACK\41709\CAN\DEL&\RS\984654\DC2\1039379\GS\NAKbHZNbM,\53068\EOT\SOH\1086193Qw>56\NAKF\"Q6\USp0\ACK_\1071681\22914\GS+\1113265\1033881\&1XL\1057692\r\b,l\1090712UrQ3) \DLE\1020313I{\ENQ\168366\v&\STX\182716k\1077895zs\1099425 \120690G(gf\1060305O\38802\n\1054049\1065585j>|Kg{.V\163868\ENQL\131757[\1096643@;=zg\134436\183794\158027biF\132561\&6ZPA\n\135254\t\1032483\NAK0\CAN\SO\SOHaE\1060005\EOT}Ys )\STX5\172564\SOH\1025776\1058126\1057981Tx#\r\b\DC1Lc5#\1035228Y\1093589/B-Sv\159168\161208\n\1031751S\39534\f\r-\167002Gq:;\168477\&4T\96990\917580,ST\FS\1079935&c\ETB@+\180969\DC1\NULd\167999\143044\CANP\1021550\988126\1020951\1108300z\ACKU\SUB?UV\148371p5\161618D\f\USo\165498#x#_\1054438\991493H\1053912\1101113$m\1108341,$\1028517(\1361\EM\FSr{(\DC2\36604:Hr02Z\CANj\EOT_C\RS\SIt\143715{(-\f\184102\&2q\r\r\155913Z\1042726Ko\t\ENQY\1105826}\a7h\40363\&39\DC1\40241}1P L\nj\GS\123621'\94253Q\1094248%n\144018\"$R'S=W\EOT\nr\v%O_\187746Pz(&v\t8V!\150217O\1087987\1109209G\120191'~q\6433b;\b\33127\&6\6978XNr\ENQ~K\DC1\184383O\STX\43136\186449q,~A\STX\995391f=JwT\f-Z0\DC1\STXJ\135448\SYN)\t\28369\989463oI`'s\aG.ggB\SUB\1089552s\7042Iv\995920f\DC4.NGl\167789\SUB6\ESC\SOfJpG2\ESC\151792J\165772\1060235\RS9\164444@pMICAP\a\STX*_\41597^e(M\EOT\a\GSuYl\1027529\\*\ETB\1000487GSnZO\184505\a\151353Do\185571S,\n\ETX[\1024125\12994%\1048335\158640\b$fm\DELJYdZSTOY\1011389\1000717\22006K-~n\101099Us<\63668\42529r\SOH{\1001552}\1035280\143766-\SI\191007\&0\30696\1067137\1100998y\34996\&3\1012120,d\t{\1060327\1023821H\EOTV.\CANu\1087782A\78628r", cpNewPassword = - PlainTextPassword + plainTextPassword6Unsafe "=:kk\1018981\&8-\\HYms@'?v\ESC\133198\147317\t\1008759.\v\1033547R\DEL\1060763\DC1Z1\95927\CANSmD\143932\1024564\ENQ\FS\"Z\1030825@n?zS\119833\DLE\988733bz\189538\1082413\SYNSv1(\132603\rQ\EM\f\149020'.9\SI^|v`\a\1039850+\DLElBv \RS\23734\35305\1109565\DC2\bJm\1085701\1095232?_rXd\1019687T\137292\STX\147778VK0\134410H?.H3H|\1089668\&5\1020896\RStKw1k\1046876e$A\1046587\66888\1106484w\22514\aRYT\1048339T{|!\1076458\50629;*\1111661\169350\1009115\1100512\&6B^\36743Hbz\DLEK.w\STX]m\1105522\3984`3@\1035182/\113800Y#\1048181{X\74883?^aYz)\STX:$\148676\nd:\"Q\FS1\1083955\DC4}s*}%^CWY\SOH5w<\NULla\65202\1015084\STXa1\1073755L]\GS\ETBV8\SUB\133836\1011042rS\152914\1109488}\SYNl}\1018153p?Yi\1111523\&7v}reh\993180w\DC3\DC1k\US`)\f\181331bSd\184792A6hx\1018142#n<~V-\987055h\143466LT\53862q\f7\SYN\\92\137339\ENQ\r\CANU\1071144%lYedK\vGHs?rN\177663\&9\99852\52785FV\22264\127076\154269`.\DELXv,\1085673\&4\r*\191239\135780\152535\&6/\EOT;V\1045100.Z\DC3\1073857\SYN%\ETBTVv\ACK\5241\&40c\STX\\:_T\49351\&0D=\138839\ESCg\SO2\16967\RS0\1009235b\n\SIe\NUL]=Ir\\[,;\ENQ\1109755E]\b3\1057051\100809c\NULya\1062732\NAK/t\US\r\DELS \1052333QQYH\53161\15320\EOT6\1045701pY-\EOT4\7658L\1039028\155730kB\172820\EM$,8\120808[\1087257DGgn@\ACK\16782+@\169718[\teJf$G/B\8949\\&k)bT<\1074663iE2h`\189858&p2\DC3r4g*\1040011M\SUB\1034202.\14977\25151\994175\1080867\r\156624\&6V\163857?Z\7344(\SOHF(z\1085772\EM@r.\1041715\EMBi_\1023342!I\DLEj\1069951\NULHy?\SYN\1024843R\1061663J4kQ\DC4\SYN`1\SOHNnp\167659\ETB\36188\ACK6p[.q\DLEZBF\\\b\NUL|\f,\989077\161119\150164z1\187508M\DC4!\1041102\DC1\1034211c~\1085907\DC3\1085359V\1024822\SUBJ/#'U4t\119160\1101637\ETXW]\EM\1003131V\FS},\1018388z\28107nOj<%7\SIk^\ETXiZ^*Fz\STX\\g.\np\DLE4o\NAK\DEL\DC1,\1048642\&4L\EOT\DC3]\83046[\DC3y\1111455\RS\ETB0L\US;\\\161083\1038455\165809VOG\97134 R/\1068148\136637J\1008468\SOi\31819Qm9\DC3P\178289\1062875m;\33449\151544\1112165g%\119045\ETX\SOH\9817U\ETB9\63207M\68250\EOT\12530\&3:1\8937s\ETB\t\1073799\160211\1013614\99221Ycje\US\1021337\bC:S\EM\DC3\EM\1080393\&0;Y\1006472unM#\ENQs3(\1012482\ETB\SUB$\1108830\SUB\SO\ESC\1011341\&9xk#\"\1107050o\152443`leDo?\EOT\166427'\178290\1083767KWa\SI\983521\&5N\1062943\ETX*\1069873E\95635\49809B0~\rG\146763Pkj\134528C\994565\1112409\FS\77994\DLE\DLE\1083005G\1079213q\1078280\SO)\SO`\1044446\r,\GS;\141525\13757\USO5\184789\1024674\1052970a\SUB5G\DEL\vN)\1061733\SIi\EOT\1053143`\181217\RS]VL\b\1013194\&0\a\EOT\1039988W\51437c\SO\1065070*av\98257'\170757 ;c\996012.\131792~\DEL\4838\182631\1055951a\135094I\ACK\1052557\f{\1072125\&1\27594Jd\1063195|\8005\SOH\1024659\EM\ESC8\120279\986941,HPp\3786\ETX=\135249\SOHD.\DC4\GSwi\1040647\CAN\SOH;\1021350!|^\DC1\ETX\ACK\1063454~y<8\1011154=|\134143q\f\FSE\149852\bnT\25647I\NAK\STXYB\1111567\1092081Gp)\1057864\STXj\EOTBi\1015288\72231\1105732\vzO\EOT)\1021734IRz\1036141Ty[\SOtj\994518q\ESC\DC1\ENQA\78643`\1033140\SUB\1086534\119199pUM\74032U5\SOE>i\DC4\1026198H\r\f\a\ETX\SYN\EM#\STX0m[\DC4Y\ETX\nY\1041053\1032715\&2xROJP_\1069998\r[\139994o\1038593\1022439-\CAN\FS\1053876\162419\GSI\1114021\1099881\DC2\ENQ=\DC1]\14160\178652ee\NUL3\165586CA$\1096608>,Sc\"\DC24$\98460\US\1104391|\148368vh\EM\b\1110174\SOH\51262EC[7Kh\n\26878\1037105qWk\142931\NULQ7i~gM\RSp\r^\nJy-[\41948\v\1088158!\164120\&4\DC4\41370\1083111\1100437\1027623gln%\r7q2\8168\DC2!\EM\NAK\175295Tl6\1071902\STX\38040+mo`VuL!\n7<*\\\157558>\68039\64688\RS\CAN\37133\ENQ#\DC1zi(3OU\ESC\NAK\ESC%\137946\1049584[8.G\1111459-E\1110194\1084255\1058892\v\1064396\1062440\&9M\99681v\SYNl=\US\15311\1047155&\1053601\ESC\SOH\1047114\1071949\172567$H\n1Y\51322\CANLg\47625\ETX(;\GS\61177lZP|E\ACK-8\GS~\ENQ\v\189891\1107362zv\bQ\USbkv\94908*S\DLEs\1075777B`]\1046292\SO\52998I:\65296D\167913\r\37306\182476P-wN\173628\RS\bD4WD?\63663\SO\1113823\1023204\149429\fI6,6h\b\1004711nP9!G\27578-\DLE.\SYNF[\160877%Q\1097530^\STXH\157909}\v\US:hx{\1038469+\1090842|\1014387M\DEL\\;G\28870\1101783\48530\EM\SO\162503zq\r%\SYNCUS!+%{\127862'w\996607q\1104160^!XSCAa[N'Vm\DC1\DC4~\189916P+w\164548\5708#LI-k\118975V!\121316\1113106md:\SUB\t\FS4\1004433z\1078080Zg:^\NUL\995376Qs\184644o\1095386\SOH\158723\EOT\1021483lPb\nBT@}\2545'&e\NUL\1065941,X0\135225c\tu\CAN|`4\1020041t*wK\DC3\f\25439RD\b\SYNGZ\1006639g0F^n\f\1105456R>f\1100409\1100823\\;`\SUBoh9\ACK\DC3\1071927K\v\11722\"\1060736\DEL\99248\GS\1040422\8236h\194957\23896+T{\52879\1008639\ETB\57964\987068\&6\DC4\998395\&99\SO\1098197\1097876\STXR\1090815\EMQb\165117a|c\150904~\FS\NUL\132829\DC3\15008V\DLE7K\167075\DC16=E\ETX\fTv\1034496<;\rLEGuZY\118839iNm\SYNJ\rI\190474\&2\1095050lI\ENQ\GS\1034351\63865\STXaPo\FS=8DtkQe\SO\147814\&0vQm\153309\1071911x\128401\164053\1008099$o-(t\DC2z\"AQ\1020511e;\SI\70124C\ETBH\29202#\1074721nCh'#\1094035'\1064442\35450Nx5=\37407\177998\94806\49674\\Y\35646Bec\1095406\1051005\DC1r|*\EM\38243}.A~\1079182\1042143\ETB\SI{Pt\1011810\ESCS8\160032\ESC\22627\SI\153862h\998542dZLu A\7299\149281+jc\1513<^\157390\DC4:\1083899\f\1031499]\bl\1036256\128520\38650d\1056973\DC2v\1044284\987395r\ESC#R\1022711Xr\27081\20760R|f\1092090?\1013931+\ACK\1107788M\CAN\1020010;\USJ\DC4\1012811\1028415>\1053853\STXj_)cWt B\18936f\1012599p-\vJ8\51800m7\167922R\NUL\171175\1057562\STXk\1020080(9\DC3%x\34431\DC2}(dMC1\ESC[", cpNewPassword = - PlainTextPassword + plainTextPassword6Unsafe "\131784\138514\157899#Jk\r\1106537K\DC4RP~\ESC\150747\1093719\NUL_\1070253F\EOT*a\RS3we+u\163806\SYN[\183120BlK\n\FSF\DC3\SI\1031201\51899\1091000\1006948y\b;\83374$=\EOT$\100771\tJvs\155623\&2H\1097133<3\49632\18894\&7&L'W\169743\&9\1100463\1016241\DC3\EM1\998556V\DC3Spzv\rZ\98169M\rg\60865d\r}\1017655\6434" } @@ -90,10 +90,10 @@ testObject_PasswordChange_provider_7 :: PasswordChange testObject_PasswordChange_provider_7 = PasswordChange { cpOldPassword = - PlainTextPassword + plainTextPassword6Unsafe "o\194642\1097637\NAK\1096957q;\40241\78060@G7os\ACK&\NUL\rb\1064652\r!\v\EMh\SIf\1009912\FS\1111085\994910/m\1095852zr\21631\&70p\ESCxcNP;>ah\1038533\1088598?s\DC2\161084+l=\SYNtKk\1112851\NAK<%Wy\DC2\rdO\"wl\r\1073877&P[ x\EOT4\1070910\94261k\110789\EOTO\2408CJ\FSh\CAN,j\FS\1051419\&8\145785\EMDM=?\1044107rN'uaGQD\DC3c\DC1\SYN\50022\SYN\SI\191191\23297_\f\f}(lT\1001335-\1102617\1055091an\ETX\",S#\182773\ETXhx\ETX\"S\29957\133313\44208$Gpk\135573\74317\28585]Dx\SOVr]\1106090\15330\EMt][\190740e#B ;#\RSwWZ`'-K\1031198\1079899\149986'G:I<\ESCgN79G5\1076698\134552\1001620\&4k\178721Z\61166\ENQ\DC2A(@ScQ\1036811\RSS.\1083926?&5\59387\&4N/S\162222\1084995\&74w\171315\176694\31631\132086\f0l\vn\1041562\&22.\995582\162439\178300\DLE\CAN\GS?!\SI\19976usZ8sJD\1016223A]ExUW\18012tdEm\160005{7\b{\ETB<\DEL.\187515\26357\1022998!<\\\ESCGPS%b\1101700\13570\EOT_X6\\a~u.E,\145264\151278B,\1110132\178446!\146205}j\DELw\1094539wAOIN\74851suE\1011582LH\50805\1075175.8g,4\b\994127\158463A\STXO\CANYgM_<\134150`2t\5592/\184130\ESC\1025744\a\EOT@\t\n6\SYN\1051619\153044\US\24861\NAKqOwDI;\158935\STX\142163T\153718\EOT#EZVVq\nj*w\1099335*\SI@[5\1010626\n\DC4\\1\DC4\DEL[\CAN}\NULBUTcW\DLE;-D-\GS[\EOT8o/\26515\b\FSU\DC4}\\\140030~\SOH.%r\24914\33259\ESC=aoY\121055z\135293\180565t:\18518NUy\986819o\v%\1031392\EOT_\1056629\990992Vv\96494\1073204>%E@H\t\171158*\1055587W\131453U#p\ACK:\1001700{?.Dv'\US4VZR\140754\138375\14865\ACK=\1010074\a?\DC3\DC1\1104713F\1094183%\NUL\11085\NUL\119900\50952\1091734\1096788Z\131351\1071405K\STX$H\b\25145\SYN\137614}Uu>\1059256QJ}\1092477c\1005961f?\1098417dP\ESC\1112602eA\f3q\EOT\DC3\1085835\SUBP\DEL$\\\1043723\148046x\EM$J^ I[\ACKgl@k9\14259oq8\60943f%\"J\1057317\73689\1041929t\SO\fi\NUL'tD38k\ESC*z\v\DC4'\38081i2}}K\1000483\NAK ]=\GSzA\SI\STX\GS\65784@5\DEL\32084\RSJ'I\1061395\ENQC\132576k\36936Bde\132392S_]e\22951K\STXh\1080765\&9w\1031828\1079907\"\1075875x]\v\"\1004384\1034557\a\13954X+!S\188785k9\1336\STXL*Z\992108E\988071E\157741\1059002\49383l&M\78548\68781\1059405\&1\1024148\98156\&4JY\vb\1009588\CAN\1012372Xqx\DC4\STX\rW\3001RRPX\GS\RS^m\58278\a\1082402\US\990568QD\1066036\EOTAzaVl\NUL0[[\997199\992592SJ\100209Io K\US\SI?'.\100329Zv\26227\183271\&9?C\\\SUB\1055968\&5x{", cpNewPassword = - PlainTextPassword + plainTextPassword6Unsafe "_90\1017274=\175182\\eIq\EM\29319@\US\ESC\USr\GS{\bq\SYNX{k\998765\1113634#>\v\127239\\L4z^\47811+N\1113429\STX?9C!^\1072965\155787\1051243f\n\189595\DLE\a\ENQ\1019894\50928&\rJw\CANZE\178133jc\ETBb%\50684va\11406<\US*\77953\&9?P\f" } @@ -101,10 +101,10 @@ testObject_PasswordChange_provider_8 :: PasswordChange testObject_PasswordChange_provider_8 = PasswordChange { cpOldPassword = - PlainTextPassword + plainTextPassword6Unsafe "\180818[@\EOTO\EOTr\t0h\154811\1097619Ls,\RS\177254\1001237\US\53799!\174182i\33534gi\12980Ul\DC2c\"Q!h\1078688\ETB\32168j<\143648\SYN8\120997\&3z\1109784\140076\8229\EML\DC4)L\1086339hE\FSYD v\60832\&3:\b\127281~\t\DC2|\NUL\STX>\1037988\&9\52889\ETB%2TZ\1057438\1019124\34595Ba\36978\&5\n\f\66575M\DEL'.HktRZK\US\b\1045482\SUBQfa:NA.(\az\DEL\131940Jviu\SYNt\141599$5b];W\SUBPM\1063367\1063525\135883,\17207W2L~_\DC2\6631@DJ;|\DELa\ESC.h\1052121\1098974\1033911p\1087765\EM^\t1X<15#N\1026411\1084279\&8G!R\147770\&2t;\ETB\ESC\1064735d~\v\NUL\1102025\DC4B1T\"\1109782}x;!no\r\1106009\RSt\18334uj\EM#r.W'}@b)\STX\162952i\SYN\167204Y\NAK g.xl\63576L\1084858\&5\186390\182838\990328\th\DC14\26177Np\ENQRLe\1082057\&5k\SOH\997985\1062349D;KY\189276A|\ETX&t:7$\ESC\NAKX\176629&\ETB,S\SI\18829\29542K;\CAN\184587,RWVP\RS6C\24675\a\187635\992522\154218\41884\b\STX\ENQX\9568$\SYNK", cpNewPassword = - PlainTextPassword + plainTextPassword6Unsafe "x\1083148\ETX+:\1080028wSH\SOH\62978\1102729N6\182762a.$\992539\120747GK\158987%\136043\DC2S\1037479Ym\1008949\NUL_Fe\GS\n4\990156\39344\1010528\&9\t\ESCEZ\"\aE\SIO]\1045645\1079319mZv\14455\&4Ie\166474iF$\n5\EM\ACK\1075904\1113583F\SOHG|\STX4g\1002980XHN\989865?\1099099\173477\&3\50673\83417\11655\1099379B\ETB1\r\1013634Tn[\EOT$&}'}\141322\&3_m\1077163W\3968Pt\ESC\1071158\&6f\1068159=\183495:\1036808\DLE/p\1004364:\r\1085290\ETB9`\1080065\984968h\1027137kOs7\NUL(t\10489\1068176,G@C\63384\1024509\186856\190455B\1113082\SUB\CANQjW\1008166~U(\1009483\&5\1109177\20348!\1077035E\1005&Mj.(\DC3y\152919\172701\ETX8p)'1ep\n~&{\62654\167895\DC2*7\990132\DEL\185308<\DEL\SI\1098650)j-abs\CAN\GSL\25312\DC3Pl\51906\&2`$bh\SYN_\1086252+>h\59671#\1056101RE\"{n.wh\n\64038\154124c\1069890\GS\170451\1076325N#I\1062645\nX\n3S0+\tr\CAN\39868\t9\1031811`/\1019167\1036273(N\57792\ACKD5\1096310a5\DC2Tf=}\ETB\1108347_yHue0\n\1092905\1099428H\b'\1091583T+:\183409${\1057811\GSE0\RS\1043155ly\SIobfRk\ENQ\RS\145619h`\\\95626\NUL>rV1\ETBfXk=cyaI\EOT\ETX!_\CAN\26741xR\DC4\1038343<%\1073241,`TXaz@^\\^s\180706Y\NUL\1081447\156985\DC2c\EOT&\n\DC3S\44890\NULE@\13815\&0`B\t\1039059N\CAN\32617\167086\999897\34753B\149257\USp[\SYN\186181\ETX\1040852=;\ENQ+$\a#\1020966\ACKc<\1106724\NAK\ACK\CAN#\41741\66650\DC2^\f\1089620!\ENQ\ACKC\SUB+\NAK\1070506f}\SOH>\bz\184367-{\37662z\128698\191437G\ENQ\n\1036769O\1112827\ENQPs}T'q\1049540\1059171+\ESCW9U\ETB1m\ETX\1044364\1110248\1011325\1077049\\\1070234}z^f\v8p\58049u\DC1Dn@7\SO\178338y'\t\CAN\DELX\138703\44901\111212Mz\1060998\&5\\\"\128701>\EOTNZdWO*\177619\DC3NV\1105635\44906vyM\45692\145400z\CAN\63310J\DC4\RS\ESC-FY]`k\DLE\DLE\GS\US4l\DC3(Ot\SOH1\156591\DC1Daok\131703\1053478u\1047598\RS=ES?\1105503v\119021\1077338\1108555\1105842!\EOTICQ\64082\167240\1027279\ETBu?%\1093608v/\47051e\DEL!M\bLA\SOHN\STXWi\1013467\176220*\aU+\SOAiO-(\n\7942m_\1015104khe^:\rQ\bZS?\1043829k\n8eh\984956\ETBU\146314k#]\ESC\32013\58442,\ETX\DC3.3\SI}\30711=8\NAK\1023884o`TI(\992144-\ENQUR?\152908\DLE\1110035\1106113?VYfS\EM\SUB\1095315\33553\1096655\ETXC\RS8j0\tKC\190493[_&\46172\1060818\SOl}:\r\SI8(x\135429?8\98588\&7)R\985918Q\ESC\ACK(z\SOH.\1107353\&2Y\US\SOH\1035764|\DEL|3&\DC3\94271Q'D{\NUL??\ETX7HDW\184522]`f\bPO\ENQx?\33111A\DC2|hP@;We\35075;\1057215R\ACK8\"$!A3.[\ag\997090\1017693.\STXI\1107916\1089611\19348U\983048Y\1008717)G\RSY\1107954\DC3+u/\DC2\DLE$\STXN\DLE\185464(>[\SIH\12867'KOC\DC4P\38328.\65734M\ns\ETXl\NAKj\DC4M\1046104=v\US\FS\1033829=p\157189s\SYN\DC4T\113713e\SIXhO\v0\SYN\159832\SOH>!\161626\n\RS\999549\135814\49229\1051757.\EOT'(,f8NT+J\1006984O\1062064\RS\187616\309D\152878\b\EMg\v^\22870\SYN\1026918\62565\ACKf", cpNewPassword = - PlainTextPassword + plainTextPassword6Unsafe "\47540\188511\&0\STX\SOHF!v\ENQ\11373 l[\SYN\137894\&4R\15581\&7\1083947rK\15414+\n\135750\1065844\SUB\afb'|\RS\"\995385\1090151\DC4\132765/\SOH\153829P\33605B=\72999\a1\1017925F\1051495k\ETXmF\1017174#\177930\148698b\168141ZG$\1112470dbB\SOH\983969\72724\EOT!\996099\&7\SI\1066289{\ETB\1024612\DC1\NAKSr$o\63124\&4<\163973q\1060394\NULXX\DC1;#%fM[dR\EM\1044817N\62150\139272\US\SI\1073067\985245d\RS\GS\DC2EbQ\191179l\1028785r`Tf\DEL\191144\&0\ACKHJ\1016730T6X\DC1cypE\ACKy \DC3\DC2\25565\SI7Wv\SI\1046192Z\vY\"\v\156204\DEL\1106419\995387/\DLE+D?\f;B\163188(\FSyI\1060531Zi\1051115~\993288XN\13032M\DLEPzB\US6\38727gj&L_\1061368\EMj9\1018863V8*Hf`?25\154173Y\SO!dS\1033424O'\1099719`\GS_T\1012344\48568\NAK\1052118\b 'ss\179793Ug\62366\ENQ\rQ:NVc\46684\ETX\147041$\33117K\97385]rNq\1088791\14261\&5g\1108158>i\1060212t@=Io/nm\ENQE{\1051318\v\181086Yk:\SYN9o\NUL\v\16507 [K%J\97955\fM-\1066437rvqm$^b9mkM\1039402\&2;\ETX2\1043146\SUBU\18461]\SOHD\ESCF\DC3<\CANu:\EM\174389\n\DLE\24984\142121yXK\1034045\52191\FSA\62973\&4(K,\168483\SO%gE/B1D\1107948\DC2\161658C\SYN\EOT\DLE\CAN9O5De\29644seu*9\DELk~B\ESC\DLE-\rT?t@\1000006\151230;6\ETB$\1003656\FS\1041307\637\1085577\1005683u\194601{>6\DC1E\48817kO\1071212\DEL60\183739\&9wk\177129\DC3\156315VX\1016207C\141727\"\155769N\153799\CANT\SI\STX\1049621\985530Rl1Qr\21745eC\GS\SYN2z\RSi\161367D6s1y\1095652F&\1040517lTt\ENQ$p\GSRi\1048949-\58685\SIu\21111v\19578P\1077429l:IZ\181939\17566V9e.\DELp\v_6)|q\1034902{\ENQ\29955MXK\1056306ax\1003137_\1006056#0\US\r\CAN\"\DC4`O\1049859\CAN~w|f\EM\126607Oi\1023015\SO\FS?h/('\DC1^\39065i\185517J`a3\ENQ\v\1060183\11345g\DC3\48063X\29116Ya\"6\r \132135Bb\1062624\DC1&\13220\EM\ENQy8anV7y\134882T\1047562\SUBcn\SUBk\168542\US_a\EM?\1106016\1088608\DC3I.Z\1069178\ETX\SIrX )!p\1067306Y\183358\&07\GS\1086052?\169845#m\EOTuZegqUxW{\100361\34246\33073\36773L\EOTg\154155\998821]\ETB\a\1059432\ENQz-\97879\187856j4\ACK\STXM\STX%4\EOT{\59661s" } @@ -123,10 +123,10 @@ testObject_PasswordChange_provider_10 :: PasswordChange testObject_PasswordChange_provider_10 = PasswordChange { cpOldPassword = - PlainTextPassword + plainTextPassword6Unsafe "1\RS\10044\NULv\987768z\1055172|%\1068184n\150620-g\146786\100842a\132317\EOT%?\97207\1068876AA\"Hmj-\EM\29734\&8\39432,\EOTP\58685>`L\STX:\127298\ACKhOh*\54301md\DC3\STX\144021\1098966LJ+\ACK\"\186180+\1079127\1032187\1090115\SYN9\147876`\63187X9(i\48707\163570\&6\1042913dn~\f>\DC2\1059432\1061679\1009814gq>!\1009228\1047046\63767R&|/\996634fC(D\180586\163947p\\0D$G\23465(J\btdn\1057718u\SYN\NAK:[gvX\50684\NULA\DC3T |@A)\f;\10521R-q?fi\142601\34542#\131181\68251\NULF\1056804\RS\1089058?*\1094737iy \1005023\126576EJ3\153530F\SUB\3963\&2\16235\1015286\SUB\1066520X($}aH\118969!M\1077359\SOH3])\\\ESC\1049797h4sn\FS\10735ztNR\STXxt~:Rb\12611\996694\SI\n\1112987\b\154951C\83302+\\K\1035224\STXs\RSa\47166+d\1073064\&1Z9g\RS)\93030&)\1043446,EIg?K\EOTm\1090815\&9\n\175897\US.u\51778\\\DEL\195063^\DC1|\DC2\SO\SO*LJVVT\1033808WO\USWmOS\1066607\"Z.\SO\1113376C-8\f\DC2miZ\ACK\1084935~C\153854sbIc\"\\-x\10336L\162894\NAK\EOT\1011330\DC4\1065068 \DC1I;\50247;\FS\STX9gU]\151272\154324;\131933v\ESC\n9?QY\SUB\176268\137386Y\78635\1037339Y\DC4\1005665@ll\26187\FS-\v\1059041l\1096164\1084819\SIWrw\ACKU:\135072{\GS\SO\1015883*\f@n.\70686f~\1087845\1045524u0y\SUB\1057096fX\SUB\1018748#e~V\"/[\ESC\CAN\152318\&4\27910_6 q\1092940P<8.MdP\CANV\RS\1046864\991518\NUL\ETXy<\CAN!\US,\RSt`\t\CAN~2E\vs6E\28962\1105957J\vo\1034354O?h7\NULQ>\1091553\&6\"V\138663s.<\"\1088335Myg\"\1103252}>O8\\N\FS\EOTu\DC3\SOF/\RS\GSO\27243?t\177484p$<\1089949VrW+\148070\"Ss\CAN``\v\DC3j@\NAKkeV\bE\164215\11921\FS\NAK1\1061345~aQ\f\RS\SO\1083547~\RS\1064714\GS/t:\DC2\tH\1019658\176743'2\77831\ETX\SO\CAND\SUB\19747ux\DC2\1085285\b@b#\US\17662\NULS>\ENQp\DC1\EM2\NUL\145191\"\"Z\133654\&1, Isn\f\160554\EMR-\58719RsaR\FSu\b\1055113>O\1004908\n\59796\136706\DC2F\1085126y\SOZ\93820\ESC\158354\DC2 \46585\n\1106215J!\STXg\ETX\988927\1065461rba\NUL_\42383\STX\STX\ETB#\aAqacXF1b.$\fFvU\173641\ESCAa:\154419\67985", cpNewPassword = - PlainTextPassword + plainTextPassword6Unsafe "\1903\59455\FS2\DELpfHg\1002321@hB\1104441\136229ae\"@\178717\DC1[;F?\1059559{\r.~Q8q^^\1032632p~\54629A\SO8epx\v\1091069\36029\&9Q#`B>\\\STX\ESC]\62056a\aH\GS\1017283=\133882\DLE\US|K\1089241SU\"`TOG)E\1107458\EM\ENQ\1100349V\DC3\SUB\131700\35766\&11eL\136082.\1077925\f\30081\139237\ETX\1018509\&33\3857\1020021@\\56/RC\9618VomQo\189775\1090226\54508|b5\1008980\t\38541\148813W\1052824u\172429\19279\1097359\&2\SIHs\1009437'\\\bt)A\DC4|\vh\141292\US9K\1006653\1113093wY\SO\1070366\40254\17618\&4u\997723'\1071258Wtl,\1028539\180872c\SYN\169621p5<\989014\SOd\1098426(P\SYN41.!\1051130(\DC4L{m\SUB\1109045dYlO\ENQ\180749l\27773r\1015113tS\DC1d\1103375\RS\150389\\U\137134)\1068287\EOTRc\DC4N\ETB\SYNrzEm\763#q39\1045616lY\CANHr\156951\26672:\877w\135480\DC2\r:CA\29971\110984\1082925\n\DC3VE,od{J@]\21278P\29049\FS\139994\1100241\1102005!\136294\1017333H\23052\&5\\\DC1\148824%\181207\165938-p\bN{Ky(N\52156B\a\990465u\119232\")\14788Q\1031053.\183810J\160516a\18817\EM`c\DLEh3\150349\SI>\59607\58218\987987L7II@C%\170472b>\NAKgl*\EOTzI4Vc\78060\22721+)\ETX,\DC3:\42649\&5\1054588\SO[UL\SYNCQ\41627(\12611\ACK-;O|\120383\SOH\25185;VBv[\fj\n$jq\"\NULuYx\EOT\1042364\&9:F\94542\1103197omPG6\fX$\DC1\ETX\SO\EOT-\137576Yk\147970M`\DC4K0\v?\1041183t\ESCT\1068218\30904Z\ta\1045178\SOH\EM0tf\4343U\NULz \98491~jq\1078216\ETXV\174194=\47181vn\143157oly\DC2i\n~_R$8;fbOK\NULz-?CM*\STX5!\1105218\181223\98689i~\189811!\DC3\134655\DEL+\1100972\1088541>\US\1083023\988420\59101'75K\FSf\CANY\SOzC^b2w-uD\1106649p" } @@ -134,10 +134,10 @@ testObject_PasswordChange_provider_11 :: PasswordChange testObject_PasswordChange_provider_11 = PasswordChange { cpOldPassword = - PlainTextPassword + plainTextPassword6Unsafe "\1020927\1032547/~G!t3\ETBpT(lfd\987373Uv\US\1047331n/\1037165\v\1000590\&4P\SI\1060450gP\1107120\&3\132634\147692\&0o\57446~\1081825\&4.p\155579xg\169376\1084103yGDY\1068206\EMx^\151320\141047u*R\t21=|\\2\7811\ESC\11201W\147769\STX&\USGu\1097263+#&\ACK]m\159187>\94801\186556\&6W\ESC;w\1017208b\\\ENQqz\\|\95815o`\\\184139\&1\ENQ&@\SUBF\DELC]G[G\164490$z\1112723\1032192\ESC\1044057\EM\1048741\NUL>d\1033451\1015039\1073811\n\166720\180329P&s/!qt\162927@\vP\DC1p\n.n_N\1112206\DLEV~\1101479$h\138762Is\1004025`!\22537$\996552\r\RS\1098882@\16250Rd87\16682\69640\1041864Zo_Ps?\58225\ETB\DEL\25967wU\r [t\174359/\GS.\ETB\178764}UyN\DEL{w\NUL\1036907:u\\\US\SYN\179040K\1072719\6945J>%.\147824'\19885\184555Z\DEL?\4231\STXjg\145989G\DC4\SO\37110\ACK\43793\SIv\134991\NUL\174407ei\\]\n\998741f/a y\1002649F\SIlm\995784\1000687\141212\ESC\RS\US$|\1035150\&1\CAN;6\150884\&6rxN\135999RVa\STX(\GS\ENQ*pSX2-\GS\53557\DC1x\b\".\38402\ETBM\"\1082676\178592\DC4g\r\1100889)\DLEk\190056t\1043965^0\1008489\DC36\US\t\52881m\SUB\DEL;OV\1030445p]-p\DLE\152006D\37776\19566>d\DC34@/\994339\141918c\SI+dol\r{\RS\DC1\1017180\1064450\SUB1>\FSy&#\169442>\DEL?x[~^dAc\f\DLEW\SI1\995822\&0S\ETX_\1047048#9\175054wfVhc\1042539\1020713L#J7GBX(\4705\1048737\989333/W5\SOH\1112863@gv\STX\162785Z\ACKw^\CANk\986491HyU@I#}Xc?\1028091\28123\DC3\DC2)5+\1059942h\DC1\NAK[>\58609\ENQ#\RS\997513\nG\135550\&3\CAN\FS2:M\49436\ENQ\1074694\1023446\1073068\1089664>pne\178174s\SOd\1091829\138029\&1\24380_\1036947PM)\EMGb\986632$z\46384\ETXv'\re\CAN2\12453NA\n\1033330H:\148549y\ETB4\ETBm\132498\185138\DC3\1010645c\at\184247^\b\ETB\1097131*\SUB\1075368Q}\1107305r%\984574gdAS\EMX\ETB\ENQ\NUL\ETB3\NUL\DC1\99185k\fJ.\1031366\48850A\DC2\185849i5V\1044560\996851:)*\tO\DLE", cpNewPassword = - PlainTextPassword + plainTextPassword6Unsafe "P7\8027gJ\1025598\1050728\tb\1046936s\179130\FS*\r\163897~yj\67377qm)Y\EM\1082698k]SS\990645~;\rp\ENQ\SUBm\1109081QAT\SYNe:\1037799\3798\a=nU\DC2n2\97679c\1105464|\SOH\NAK\EOTL\STX\1014848>\n\SON\US\990037\SI\GS`JOJ\991087v\USY\43061\NULp(S\1006785<\1009666\DC2j\ETB\1074651\1111117s\49393\167041J.,OGU\1015263\1026361.\1082540\&71U\1093020\&8/\1053449\1043583T\EOT\GS c=\53746t}RiOL\RSUsY*h\120237_| \1025261\991499\63211\27137s2redy\1033031Vg\6578\EOT\155956\62493bdQ\EM\1060795-\1079855\1078796\EM\ETB\18365\170958\5129\1084739(Qw!\SOHh\1045601N\52593\STXb\43616d1\1081703{\1034023\FSBE+\r\a\1042611I\988095Rbat=\DEL\57734\v\1087456\139092\1002353oA$\ETX\EOT{o|\DC1Y6\SIleK\984319c\DC2\NAK\9960H\SI\97430\&5\988979\&8'\142119\SOH/\12561\bKo6+Iw\179708\18608\v\SYNN\1061814\SILuF\164362J\1037455\&1\1050949\&0\168187\12201/i\SO\990270\996257\&6g\DC2eR;\ACK\159365\1095404\83044\1057125\SOH\42192_=\1021400" } @@ -145,10 +145,10 @@ testObject_PasswordChange_provider_12 :: PasswordChange testObject_PasswordChange_provider_12 = PasswordChange { cpOldPassword = - PlainTextPassword + plainTextPassword6Unsafe "t\176015z\63099\RS\1019165\72134\1094758\1087142Z\NAKB\1067933+\ETXXj`3.Q\1032039\SUBK\1109183r\1017216\141033\EOT\1016663\52892f=\t\b\CAN\SOH\40431\ETB\a1\187032J4n2eUq\165606\STX\1002769\95144\n\DC1\v'P K\ENQC\49957\&6\995828\abB\\\SYN$E\rCp(\ENQ\997577Z\r\5958\1064586\6052\169035(2\DC3\nwz\EM\1057822`[,\a(\168052\ETBn\1024741\170909\&3\166910-\NAKTp26!\EM%\1067691\983629\1105810X\1084137Ww\160494.S\SUB\RSs5\tS/\188321\STX\179825\14815E\143720\5499::Y%\SI\151589iV\139252BU}]*\GSp'\51146\77903\40692\1032384\EM}\14702k\EOT\1048493a\1024981\&8E\26598\f\1016469\"1uT\US\97604\1086313hC$ND\99182\ENQJ\"S\vCyu\SO\DLE9\137054b\44966\DC3Pe\21040l\44235u\1093696\2097VSM\a\n\1107484\&9\DC4(\a\177489\150593>-\NAK\1030177Mr\1050563\&8\DC3\194810\141213PiK9\EMm\ACKW\SOHvIFX\984936\ac4<\SUBU'\ENQ\SYN\155860x\9048Y>@\1041311\STXp5C\r\163993!z$\1015059\GS\ESC?# UW\1052214\&0&w\ETX\1102267H\190128>-[9z\29338\1008713\SUB@\EOTiA\1113779n1S\136784\tG\DC3\DC4RT*", cpNewPassword = - PlainTextPassword + plainTextPassword6Unsafe "\1029941\182208*\50406\144479mqK=\SYN\NULg\3986a^U !\164592\1000979F\1091010J\137728\43445\ENQTB\152909=N[/5\187278\SYNT\SOH\NULp\1045227\&8\155213q\ETB\v\ESCA!w=x\DC1\RS\29768Z\EM\vpx\US\b(\143914mvW6~'\DC2n&\RS\ETB^]\1092638\b|\1066156d\SOHv-\1092522T\b\ETB\158149x\46579 Mo3)\FS_5\182825Mf\156710VFG\STXOEC\142253(\ESC<1bYVMM7h.:n+\ESCXB~juz{<\GS1\23218\1073013v\1085890Ge\SYN9X\STX\1027702<\ENQx\61722>\ACK^\1108099\1049394\140867P\ETXuh\ESC0\15060?R\SO\CANcT\1025381\1026223C\DC2\"8BY*8\121167XO1\v?z\US9RR\139465\NAKdG\160065\DC4/k\1101568\1097562\46923\ENQk\70387HNx\139984#\997549 \apU\24875\161412E~p\DELe\1024027\32616%(\EOT/\165473Oa\1068906:9'b\b\48968\63083bSw\1089284\DC2pQ\DC4\SO\997853G\143790(A+!\SI\SOH9O\1021380\rWZ\ETBHVe\1038354\SO\SYN>\1084362\SOH}Bcj\1040105\SODC8\180947F\t\1078880yrR\126464|\1002350\CAN\9877e\160489\SYN\fo\n#M\140761\1050789u:j\DC4\"\39095\SOH\1091919I\64643`tpdU\SOH@\ETX5\1015281\169940\&8\1084283C\EMI\70725\EM\DC2M\172398\1098890\&20\17785\ENQzq\v=KLK\1026521\160303\191191\FS\1022904&\1044176\EOT;e\DEL\1092828,59~\DC4\1095506s04[C\74207YbmD\f\40061\\\ETX\EM\153974\169857Z\f~:.\r^?#e\1054794du_I\ETBHy\NAK\DC3C@]Q\167956\65170a\v\1065540<\1097822zRr\vA\1005063\1042686%]\US!\99727.dfV\STXR\54637\1083007CJ3\t\1100221d=\DC4b0\96187\ETB\STXc\NUL\32051B(\RSsx\DLE\FSlYr#@\1048685\145684\1032535\995750\NUL7Py\176787aL?7\SOH'\1032303\1026443\166147;\ENQ\1101976\178862\1064385CP\GSy\NAK2@h4D\74062\98439\98790;\991778+%\142285\1032969V\DLE+Af\ETXr*}v\74561\EOT\b\SYN\DLE\SOH\CAN\NUL\DLElI\1103471Kof\59742\DC4\ESC\185307O\1025693fr/\STXq\62224{\SYN@\1075147p\1092437\DELmvx:fk\1096766\r4\1079176\9458\ETX\t\fP\1068111%c:\\4\403\1072385A\SI\1107936\188641\154662?1\US-\EM5j[([\40806\aQWI1\1012550\1013491H3Xf+A#YF\SUB_lIo\169072\997652\998922X\13580X\1093433\SOH\1032578\989439)P\172195\&0\1014194d\a\RS|\174744$Eu \SOH\ETXF8 a\b\1022530\1066904/]\US{3E9Sf\138114\a_:V\ACK\1111019QWU3\1098773\3051OS XdA[\183160\28570.\31939(b\nL#\1107788[\STX7l*C\1033328\984136\GS1.Z\998716\EOT\46839H\n\1071926\1079240\SOH(l\RS\143037\v\38887>\1090554:-\NULj?%F\1084391I[6v\170273\22502\100077\49274~7G\166097\4519\EM!'\ETX\38398Q?,\GSUu@%(H(I\ETX~>\DC4K\DC3>\72246\EM5\1022086 \1090756O\f\1111314\&2\DC3\25931\994780G9\999039\990590*%\1000152\NAK-\983159\SYN\\\US;\152905JP0$\1106049\37822]\DLE`\1036169:0K\997106hkB\144462\RSH\1102093\33454\RS\989572\1107706:A#]R\SIXFW\"8\STXHW0\986050\45557j\37948\995320\1100585A\1035623}\61155l\49913\1091660\ETXg&?\SO\&H\1054389\1089082\NAKL\98924RG\1101738\&76\5639\1081113@\EOT\SOv:\29442*\DC2xph\136453\"F\n3lu\61648\STXuf\DC1A2M\154097\1091702\ACK\DELtXj\DLE\1096523fT\168155\160774\62409Q\ETX\1073552ah:\1045093ctrC\1008972&:\ENQ1\984019\DEL\72254_,}\1059043Y9`:\DLE\DC3d\1103970\1096003\1102898*Z!\187234\148461\v\52269\&7\63614\SOH'\\TOs!%?\SOH\1093845Z>\DC4\1204t^P\v\a\34585`\147989_\SOE*\1011406\SI\162221<\DC4\USW{\83494[\1054677\&1\1049205duWR\25182\1059779\&9l\FS\a\US\ETB\r\1036646J$Ea\1052569\173473\SUBLpR2\27762A\167459\b\SOUJao\1025597@\17412h`\SO\163155\DC4\1066350E\157076o\1110972dZPjbt\54921\985661k{\1102674\&0|\ETB\50568Q\SOH\152060\&5\rfAj\1062496T\983117U N\31082u\1075887\DC1\157116\DC1IP>\995210'\rz\1046533A\1066921\"\181434\&8\164987|\63500wXC\NUL\1064912?,X\1019667m\US\EOT\96637N\185883_:d\USU\167304A\1106870*'`w|6\1045529\&9+\166106mjC:v\1053515\39282\10936388!Acu\a\ETB6k\SYN>\EM-\22513g\149536S\DEL\RS\1023314\1096302jyZ\1066742\1070063U1G\SYN(\180738\US\1006809\SOH\1114037M\172262\a$CT\CAN;iezO\150819!\1105298t<\1055348\149076oogs\SI\ESCO\a~\DC1\a\DC3\1018432\159829t\15910\37325|\DEL\CAN%\1010165\&9i\156087\&5\144925k\1050355\49336\11211\174004\1051581\DC1K:\RSE\ETX\5991:f\1098856\19403\rN\49047:t\SI\1053824\1038465z\149922V^?D\v\vki\SI\ETB\1001377?u\"4L\1021111&\987357\21606B2H\173390\35107\&5F\34214\GS\DC3\\\1059063\&4" } @@ -167,10 +167,10 @@ testObject_PasswordChange_provider_14 :: PasswordChange testObject_PasswordChange_provider_14 = PasswordChange { cpOldPassword = - PlainTextPassword + plainTextPassword6Unsafe ")\1036280s\137216'`\59330\STX_\CAN\180010D\t\ENQzJ\1063390\1045233`D^\111264>\132440\NAK\DC3\176932\49323l\SOH\99704\1013404\&8\1098260\DC3&>\43426\30743x\173643.o\158967=6\1633\1098022P1M\162604\"RG\SUB\1038025\FS\STX\CAN:\DC4\ETBu\CANx\1089236\1061187@(E\1002850\&4~\ETX\NUL\51238\&4X\NULH\DC2pll\EOT\DC2\177807\1104201\DLE\17185[K|W\DLE;\144266z,C\USD\983816O\NUL\riV\">=^oX#&L\1049388Kq\25975YW\1033425t\1055427\22674\nPF~\1082938;42%b.;t \1040882\1039127\165132\DC1\1064926PV\26969z_xZ\SOH)Bz\t1f\DLE9/\FS7\1093628,J\33998\72145&Q\NULe=\FSK+\SI\25383\1028788\1022136n\97202\SO\173088\&8p\DLE\ETX\111158|,\STX\1033460\1104436d\1050868o`C\SO\132042\f?Hy&\36586A\46227\141006e\NAK\DC1wJ[\fc\b\1070422l\141230\DC4\64151W\DC4R\1045214t$\136334v\61852~r\1060898f\1071586\&3\SI\1019583\\D\ETB>\164308b\EM\133344w)\1053343_(\1058134[ra5U\SYN\178080\72861fC\141152\&6\1011495\STXy\100396Ii\1109445\f\184085z0\164727~\78749D\rhqu'\DLE\SOH\DC4\145824V\\\SOH+Mu\1041477S;w\141810Z\1041792\EOT\NAK5mo\USZ\1079915\172082\1069321\1090200/\ETB]\aD'\NULx+\STX\ENQI\NUL\DEL8\RS\1055834gcU7\1066759\NUL\7502\RS\995972T-", cpNewPassword = - PlainTextPassword + plainTextPassword6Unsafe "\DC2\t!' czxpt\186414}\1036760Z|\1105851\&3\1098742\&9cE9;qlI\128862\&0d\1041894\1071062\ETB\SUB\13291C(q\CAN\DC4\92178r\992045\n\37621\SOH*\26079\&3xLD\97230\SI/AS\CANX\DC40\NULC\134548\DLE^\1113807P\SYN6U\"2N\STXv%\1078605} \17042\4719\ACKB\v\t\989972s\138208\&5\DLE#\53263\1088280\STX13H\173756#@t)\188467BEV\ACKLCX\SYN]\DC2P\181437\FSN\149514\186718?\22604(\1090926\15413\1065637\EMO\27585\\r\FS\\\SI\1035346\18565\1013435vU\at#\1062175\"\EOT\SOH{pE\12478xJ\DC2g]\ETX\DEL\DC3c\SOH\fPX|\1066931wEAe!\993681R\ACKu\1113614..$\1068793\27316\&9(\SYN\58010c\1002603\RSw!\SOi\1030926>qPl\ACK\STXXmG-\v\120608`\1088763B\FS%gW\ENQ\DC4\n\STX(\1074703\b\DC2N\1109769\&7H\"k\EMty\ETB\187962A\1020329.:\ESCj\re\GS\161991\1034321-*q\ETX\1093441Y%A\46791hf\n\141128\DC3J\157117\SUB}4\EOT\161237\v 8$d%\b\28128n\989856K\DELf\t\NUL|;X\ACK\142667*\CAN\SYN\24303\v\4878\1760Yj\vyyk\1026116'i\1080447?U]\ACKb\ETX\48209&\"\29031\1039525}\63031P\13226%\ESC\t\1047966\1098216\ENQ\ACK\NUL@\ENQ\1106062%D\41968\94208C\NAK~p\SI;\GSz&\SOj\DC1\"7\66276\1081689\EOT\DEL\990793\bdiX@[%}\1072552\1098336\&6K]S\ENQ\1012057T\990893\154290\&24g\USe\STXpS\82979E\FS\ACK\71075\1087888\DELo\29366'\t\SIv\995645\&7,\ETX\SOH!i\1031533\1081283\&43XW\t4\FS7\1101016\1108161\31300?\1083887@\1048301g\DLE\DC3\1067632Mn\GS;\1044539*k\147748\&1\DLEW\1112391\1103992*\vB6\158062\f\68308=P!\1112874\45394|9Qv\DC4q\1063868\1105018\1004357Z\DC4\1097524;9J\160053+\fLa\DC4\1010823\&2\1049908<\1055415\EM\1053244M\ACKDO7NUa\34227\1041181[ \181881\a1\US\vc.\1012405\ACKhi)\162677\35949nG\27679;\132913\1026232f\182327#t\1027272B=\152450`\180605\&4\NUL\1006343*\1075473\b\\*\ESC\177216\CAN;\ao%pAH|\n\EM\tC\143291ZrZ\151170IF\ETBO6=bh\STXzV\1013862P\1064457\\\159157:,U)\58595\DC4\1013245\1075630\GSg\SUByv\110788l\\\v\b^\31887Ii\ENQ[\65195y\1088707\&8a\CAN\t\vDeb\1007440i{t;oR]", cpNewPassword = - PlainTextPassword + plainTextPassword6Unsafe "\152852s\1068988Df{\1109851>\DLEN\1096871:,\1104054 \134810\3245;L\133604uN\989402-\"A9t\1089099f\CAN;q\1046261\36961R<&3\ESC\DEL\72254\NULq\n9e\SUB[d\1062509^\v\CAN\61899U\18913BM)\DC1r\SOHevfz\EOT\118837k\152950\&8zn![nE\40274\a\148239\146181\1034404\DLE\NAK#\1021834\\\7383\f)We\135919\ENQ\1003933\1042129Y\1083464W\184760\1043368\&3\DLE\\\b/\1058055<JXk3\78374\987565\151899\147262\27256\ENQLe\138865V3>\142078\32373\&4\1065316\35039F\\$k1\f\DC2f \v\121169\FS_\1066161b-\6727&\995927'X?Jb,\12990\158842\9204-\ACK/\983518\1100707`\ETX*\9732!\DELf2&/\SO\78519\ENQ\1006374\ESCU\1108463\993917\USJq\155123R\133864\&2Q@\ETXq\30171sM\"\DC2{b\v}i=\118851\DC4p98\DELsa\1110153 NiV\1016779f@\162996\1062077F+Z\154196\DC1\1093124Zd\66026gh^\SOK%\142282I" } @@ -189,10 +189,10 @@ testObject_PasswordChange_provider_16 :: PasswordChange testObject_PasswordChange_provider_16 = PasswordChange { cpOldPassword = - PlainTextPassword + plainTextPassword6Unsafe "gB[\SOa\136597\&4I_\1070522$\US-E\f\bD\184839l\53618j\145196\1063189e\96537*\1053243g(hJ+E\\\STX\100410/\ENQb.\7738\1113147\\\27214\9423\EMu)-\DEL\FSRh\DC1G>\DLE\ENQ#\162187\94536\v&\DLEd[5\DEL\DEL7U+wZ~e\1065056RT\1015376~\47206\1049409\SUBL~\ESC]\175086KpHWu_\1093704uN=\b\1029782\EM\US\1041680\DC3\DLEd47\1016259\1094650\SYNZOiX\61511M\DLE\DC14\1023510\55250d3h>Jw<\SUB\1105863$*)\173139\&5\t~\36451U\v\1007351\&8nb\DLEXH\137571\DC1Q7kP\186382L<\1078705$+\1081663#\ESC\38858*K\180009%\154955\65738\f2\v~A\1011551\f}\1100334}(%\SUB\997989PA\NUL\1081198o\1064382\SYNV\NULv\159271\ETX\54311\1064618os-\1091683\NULE\STXcl0\137068f\1050864\186833\174746\EM\25158sQ\1071799\60428\5196k^=_l\1066392f;O|\1063397YO\"P\NUL\69230\\\35862\"\ACK\"~\141584X\1038172\ETB$\95964\CAN\27381%UQ\NAKy\1029066\1073585-W\1020228z~\GS\22450\1113567J~\DC4-?:\1110536\rf\1065914\a\187988\1098168^.\26197T!*\1037028~\1073514\SOHF\am\27257\158078\175061\\\SI\147288Vk\99196w\1092949\186929_D\DLEq\1086094r\131393\NAKy2\ETX\ACK{Pq)\1037265\99424\993708t\169393e\NUL\US\988887`\159377\EM\1002749\STXY!\50906\fY\ENQ\1078545ERU\990479>\NUL:ZT\1035772.3\CAN\1096695J\SUBN\ETBZ\153481\a\16088\DC2m\ENQ\ACKDzhd(<\SIF9-N^|\983096;3\993521%\164480|\SOH\1080654\32149L\DELs\72704/G\161452\&6\1001045T<\US\SO\176234\b\132812}\1056421\7504k\19220\NAK[t\DC2U\SOHIX(\1069119E?9*|-/)3U\ETX\ESCo\SO\1099860\SO\RS\1009682E\te#\DC4m;\DLEfx\SIm\1046538\a\97848\187828\&1\137971Kd\CAN\1042040%\ENQ\173735;\1027791:4kcX\37202!>j\153067hT\DC4\163467~\1060082\995785\1005593\190617\1113881\199\DEL(6r\DC3\145739\EM\ENQx\GS9E\SOH,6\1064646\984090\&5Zv\US\70154)dssy(\NULco.#\"\NAKXW\119053gS_\31565O*\142194\"\1067622&Xt\DC2\r{Fi\48778 ]ZZq\994122:\177062:\1052139U\ESC$zN\SYN9\"\10761\&10b}X'`\190793C\1043334}\r\1111369\1021511*\r\SIk\1062958`E?=`\46022\78512l\1068151*j\36518M\1020065\37308;\159311j&\DC4?#\191316\ACKs[iF:\n\16090\991139\1059764v!b\1112922\1068230\SO\985232\141755\1112084j\DC4A\SYNT\NULNY1\1078882\a\136436\1088153blf_n\DC3\GSB\ETB\11380\50340q\991037-B/B\16269j\178019\ACK\1079155rr7x.x~\151350\DLEcIIK l~\vt8z!)/\24279~?yP1BY\ESC\1044'#7\\(p\159717Xx}\150971\145409x\15522X}8\SIw$\1067337Dy\68912SR\7036\54589\1086756R'\EOT\1016478 \1086591>\1072777\&65\DLE+\EOTm\1097089*vMO,4\24600R\1049889W\991833\FS\EM\154481\":H\SYN_K\v1(\23407\&5g\189510=\ESCY\v||]A3\1090464\&8fI\f\1089249d\27681dw:\53053:e\FSX\140812&\26383\58555\1020960\153568\&6\ETX7?Z8\DC3i\10727\32848U\987253'D>\ACK\1099263\167302n\1084348\SUBe\5445\DC4=F\SI\GS,!jSM\1037019_\n", cpNewPassword = - PlainTextPassword + plainTextPassword6Unsafe "s/\DC3\a\1019919;\186152\DC1\ACKL\NAK\SUB\123639\&0\DLE5\988934\1085942\ENQ]y\n\1088660\EOT%#o\181283k'N\5859\164247\1012481.U+D\ACK\US7^\1106302\DC4&\73089\US46zmN\1078548EX\1100831&hV\147473\&2#B,EY\1062234`\DLE\986448\25566\EOT\EOTv\1060131\1011335\6586\8606[H89oS\141447\v(Isb\DLE\1009984\999533Dv>S\ENQ8\NAK\1013739\31809CBi9C\f\ENQ<\1060837\EM\1002394\RS,/g\1021216\&6S$\CAN\CANTc\145792+~\CAN\38440;P\EOTh\SOH\990223\GS3}j {p\1006877\169025\158467D`\1014376mN-\\V\STX\1106691{\SI\ETB \NAK1#6p\994323~\992677\1043440\&6\1059978Q\\8\ENQ\STX\1011902]ynrf~>n&\6380\94374 m_`\1080547D@\DC2k1,>\1098067v\132299\&6?\ACK\1012654\DC1\SOHjL\16576\&6\USld\1008037\189738\&5\7878w\62207$l\f57\NUL\64524\1073108sg\"Mf\ETBuKPsa\190219zLTtY\136016\&2\"R\18939\66650\FSw\b\167281\US\1065135\50725W/\126507\94452}S\164388;X\EMe`\94080\NULZ-\995401\43858\n\\\EOTy\187923f?\1090882!\SI3\n\ETX0\1046274S\39868T\189058\ENQ\SOHB_{&\FS:\FS\CAN\988362\9506k\1054019N\GSWR_>J?,(\1101196r\1035425\1088638\1003569\43364)\997661\1029696q4U{\21915uqQA/\1036564\1028269 ]4L\100553\29035\25204m\ETX\179784b\64823\1106448\GS\151115\DC4F\1004969G0@6\1077837\DLE\137744BV\1081634l\1081851C\1065998Q*<\SUB4\ENQ\99901\DC1dh\1085582\1100640V\128022\FS\1012025oZ6>\24971I\1046586\&2@I^\DC4B\ETB\ACKmD.\137741`sB\19558\183636L2\DC32;\GS1L;\DEL\141722p/\bCj\47458G\27338SXe\97073Z\ENQf\43154\&2_\6385\SYNr\DC4/C1R\DC2" } @@ -200,10 +200,10 @@ testObject_PasswordChange_provider_17 :: PasswordChange testObject_PasswordChange_provider_17 = PasswordChange { cpOldPassword = - PlainTextPassword + plainTextPassword6Unsafe "I{A\1081115\1040244WH\t^\1024680\STX\DEL]~D\n9:\NAK\ESCy\1040393$%7?\1048769>bm\DC26V\1065346\16705\ESCQMle,\1015761\21766\1105355\NUL\1020694\GS\v@K\1043881aC\1094072GI\37368\1075187\1079031@W\1038319\1001363\997424\157615\EMa}\SOH\12756\1081169RP\1087906\SImNi\98040.6\14300\35116\STXe2S:X\\y\1027289R1\163603}\1012190\1110662\119901d\146377Xi_\1058064z \185806\47296\28402f#\ACK\b\1018085\fDK>\1092488AV=6\b\DLE\GSi\DC1\SYN\1108215=W$M\1102069\ESC \DC4u\990358Q\FS\78744u)\1056471Qf\US\SOH-\STX,\DELnCr\\[\25698\CAN\ENQ-G\SO7\9176m\RSs5\CAN\NAK\NAK\1081901`-fY\1028198\ETBL\63765\ETBX2[X$B\FS\ESC|\n\SOc\1093611X\NAKW\1010429q\1046880^`CD\DC3SMJ\STX/wR\NAKmB(\EM-\1034880xI\DC4A\1078737\1103535\1083873yw\fn\1057985E\1101283cP1\189927\141738\1011422j\1037710_\NULb>&^\EOT\58470<\r\b\rr#hu.LY,\tS}\ETX=Jz\1113866\78095B[\f(yR\997282\SUB3\140630\DELl\1016964\SYNIa^\50526*\988502\&3z\11825%<\ESCx\48956=H&u=\FSs5_X/\1021976Q\1059092dJ\172609ghu\44563\ESCE>3\1072325\189630Wls=)'NG\1094331r\SYN\DC1%}8%&\154657\&8:\ETBKQ@?>k\1027270\NULb\9895+Xmu\1011506\n\33550-\f\49393\51542\DEL6\1055009\CANw70*;Q\1072772}y#\ACK}\ENQu+2\163564\171133\vTF\v.Pq\DC2`M\174005\96741\&1\SO\njL^sg7\1081135C*\1102887o\1088885{y\52998\ETX|\1023709\EMF\135291vxdQ\137699:\SYN]e\DC3^\SOrv\SUBf[5\NUL6x1E\1005855\1048944\1080103\v\993780\v\DC1\SO\988187\"p\35477k]^/=+\1067771\n\1074075\SUB}A\DEL_8\RS62\ESC\DC2Bk\29783C~S\EM-`\n\186446\EM\NAK]J!p\SUB!Y\SI8m(cS,\176277<\168390\1057601\1082774'\1092313z\989733\ETBwe\1013727\aa\n\59433Fb>A\1088804|\SYN-\STXXz&/'~(cy)hIF\181057\DLE-/XyR\147207\1035531\99846\ENQr[\r\1052642Q\1054366u/r\995938\NUL\ETX\r\tTj\190394\155541d\1046953\DELm\vb'<\1059546\45701@v\53022T5dlF\f\157714gRJ\1037469\995950\1090231\US\62219j\bF\\\v\f]}4[\1091004\&1J5\1101864fM\1002697P}\1109954]xd\44742rn\USi\127844zuK2\64008qwmc\57449\SYN\SUB\r55w5q|\DEL\1004339\DC4\1000580\NAK\\;\97824(d\STX\1069352\126106#\SIeP\1014578pc\b\1006213\78367C\119203\b\DC4#C\183272M_\DC4 EY \140657\STXg\US\rq.\99689._\44617E\ENQ\STX\21293\994346.\1010642\165378s 7p?I\\\1016448NG\1016271\vH.\1011097\&4Hz\DC3a\1112965\1099391LS\956\NULnx\ESCK]\v\1014996\ESC\1001785\DC1<\1059607M)\1103444M\52771o\DLE\FS)6\52537\78102YfQ@F\140229W\NAK+\158031\f_b\999514_p\1026159N\vF\RS\"f0\FS\DEL?#v{\1062480\GS\ETX_p\STX[\170629Qb\37443\&5\1079682b\b\1113122O\987041\r\30653z\GS\1034134I`\ENQ\1074991\ENQ4\NUL|\GS\rt-\FSY\ETXea\164217'\ETX|\25860qY\137837\EOT9Gyz$ME\1012376HF8\1101936\DC4\1040797\&4\DC3d\38807w\EM\1087666PY6$aKV\RSkHNh\ENQ7\b\154234;I-J\1010947rSDa\51431C\157687\SUB\US\SOHV%\ETXB\DC2N\DELGq#Q\173949>\1049166\ETB&\SUB\1027480\&6c{:\NUL\1026276{\tq3\96886\1014266n\DC2C\993425\17816nLR<2bXS\ESCe[l\1097388\fjjZ\1004264w,a\143819\r\SYNL\1049703K\EOT\FS\v(x\141566\1002452\49875l{\986046cp\\\GS>DA*\186399\189082v1`[I\1087573f\160956\&8j\SO\144181n\60434\EOT7jx \v\DC4\SOH-\"\1051346+`\STXF{y56\186936T\17962D\111297^C:F%5B\1113608\183649eU" } @@ -211,10 +211,10 @@ testObject_PasswordChange_provider_18 :: PasswordChange testObject_PasswordChange_provider_18 = PasswordChange { cpOldPassword = - PlainTextPassword + plainTextPassword6Unsafe "\ENQ>$;GNS\NAKq\178874\58968$-\68366|\151635\70868GG\1069152+N\994629igAv\68290\n\1040872\DC2Tl\ETX\CAN\173661\ETX\161735N\42419v\149562\98179\&9$v$R/\69765U,\a\21766\CAN\ETB\1040043-O\DC1HY\fK\1067449aS6V\NAK\1040221\996914\850D\54816Ke\ACKj!|!\ETBo]Xg)X8P=\ETX\ENQ\RS.b\"xp\1105843\&3i?kTDu\1098220T'.\1071583x%0\DC1+GA$A\45313\a\ETB\ESCa\1028493\1096329+\1019183\1111460\SO\n\SOH\ESC\65042\155997\STX(]\1046910GL\DC2a\151097F\a\168528\fdB\ETB'\165547+\vTf\992507I\1045958\&4r\175158%\RS\999749Xq_*g\994465q\RS\SYN.\1013841 \SYNzQ\NAK\986554:\181318(d0i\1025168Z\1057887\nn\988266sa\61001\SYNx\DEL\170759A\1027650?LZ\CANO\1039286\57543\&8\RS\EOT\992522M:unvs\ACKco/\1103889\61376:$\1017208O0l:\4771j\1071859\NULB\DC4\165947e|\1104789\119138`BCH\92363\&42\59532\999574%Y6\1036574\b|\119002n\1008926\NAK\FSb\EOTso`\DELy7H\1144\SI0\12299B`!\n\EOT#L\51662\1103006|\ESCa\1038787\DLE\r\1067701\&0Y\135747#NKj\1106283\42365\SO,9\ETXh\DLEw\128979R\"(\EOTV\r\14540\&1\989621\DC1yq-e\f\1035089r1YU2\1087695\171952\DC2\SO\1105513\1062941[\1019605_Za\52679\DC2\ENQ\v\1046141\\#\1076425\a\FS\16658b5C&>dt~y)\66452\ETXg\141548@\DC3\182735\ETB*$\DLE\1313UnKs:vVS\1095798m\997335\15618X5f)}@{ha\DC2\f\1041518\ETB-\1054867\t@\STX,\1099907\990571\STXD\1087623\SOH Cm\1004594sDY#YL\1090422;F\156423\v\GS n\a\v\157412c6+,\1004205\EOTH\1063061\33351\vf\1065194-ZS\ACKB\SIFd\buy)\128544\1074733i\1092468/\SOvZw`JB>\RSu}\1107475\1036872\35763%\185556n5\CAN\60703js\1039874\1078173\135796uvW\"\28047h\1043723P\DLE\1097958S-<\187968\34464\186710/wb(\26583\&3\n*kt+\180850\SUB\1021642\1068226x\983171@\DC3\SI\US\\0\NULRA?\SO\RS\189447LuW:Xh1\SOHT\DLE\94916 VOG\DEL\RSO\189514k\3015\1025016;l`g?q_ \1011679\92993\984171~\ny\US\1045482\52577N\1029913\EOT\SIA/j\STX\1084693\DEL\176033\136608m\GS#z(\127161jW[\1038238\1073630\a\1060787\&6\nnn\145137\188550\DC1\1068174\989085t(l;\1017830Sm`tO\r\57362\NUL\1101579|`x\1071012t\153686\EM\163617/\DC2yuB\1072146s_\183212UrcO\99677\1081043\n\41654b\1797\GS^\132600\142485}Q\1109755\DC3G!\1035773\&9L\195083y\2453*\SIU\vk?T\1064435;\f\\Q\RSk\1085726\172950\188191|\6976\1114033Z\155291\tl\NULb%WA\49679r}u2\1059498I\DC3V'i0\158983/Icz\74116\1054934\DLE5L2K.\ESCKLr\DC4\1046820\1052056\STX\\\SYN+\DC4G>\132569>N%R\DEL\vR\\L\1082431*D\SI4u7\DC3AW\STXG\144748e2%y\bw\US\SUBZ4x,\ESC\67889\f\47421\DC2r\1004074r]%ws\DC4]y0\1014309I\993296n\1010689.l\NAKLAv\EOTJ3z@d\27165\42869\t" } @@ -222,10 +222,10 @@ testObject_PasswordChange_provider_19 :: PasswordChange testObject_PasswordChange_provider_19 = PasswordChange { cpOldPassword = - PlainTextPassword + plainTextPassword6Unsafe ";\93029\64850k{\EM\ETB\183122'\ACK`{2\16834\ETB\DC17\b\aQ}P%O!_^d Tf\STX\177895sQDd\DC1?}\b\NAK\64801!7T\180836L\ENQ\30743\STX\DC3\GS\a\US\1011186\DC2\DEL1EEV(x'\DC1\SUB\1061407\t\GS\ri\1100649\&4*\1060200Fs<3\FS>}Y:\DC2)8+4+\STX\985320\1012240\134405\vl\NUL$\v]rZ~Dy\1045002MF\CANlw\995758x\1076847-I*bE\1065762A\189306\&5\50057c:R^f\\G)\44960\&9\DC4$|\STX\"y\1020665\39604AW =\20322\1091813\&1\1108618'\1051166x\39102\&6U_\997336\ENQ\50873<\1066165*\ENQ\1029616z\186193U\DC1:4a\148141\32437@\135977\177323\&5p\1110836\NAK$[\US\74077.\987765\&7\NUL_\988982\&8\ETX\r\bg+'R\1027482\1029411~E\rP\1034583\r\181175\46544S\\&.^\60125L\1104199L\a\144365G:Wiws\NAKQc\r\164901\b\141361;\999696|Q\23549\&4\1036417\72875y\29622\f_&fGgH[\1029620u\1052069\23938$\ACK\STX}M2y\99985Z&\189182\f\1110805nzK&\1066016w\n\NUL!-U\SOH\ESC7\ETX6\1026958so$\991139\t\138455\DC1A\27842M1\DC3\SO3.Q[Uy\1006799e\1005623\fl\146202\171029W\1104958C\SIk\71104p@$+\ENQr5\1029753\nB(X)Y\62054>\149953%'\180534H\187026\135153s\11937A2MqW/\18450$#I]\137728\&8sv\49908\ESC\1061880t\1103799sB\988567\&5\"\a\128637{t\23482oJA($\RS;\1067956:\SO\98842\128224\v\141160c\992280\"\1037303\95310oQ>\STXyD\186030\1035343\186166g@&\EOT\95865{x0R6\989091tn\12077x\1106050d}\1016609m\DC4bHo{\f\rM\184517\t\137817\147706<\NAK\179286;dz\EOTC9.\CAN\15836V\SUB;\992386\RS\44724Ho\DC2\93805/\1100285\141534\RS\t\1016167c\STX\1023078\1034365\1046848DavSJ\SOH\STXk\NAK.\FS{Z4\1035470\&0j\1013919{\161435\59533{\148113\EMIW\143598\147178_[", cpNewPassword = - PlainTextPassword + plainTextPassword6Unsafe "4)N`WV1t\171789cC\1064787\SUBi\r\181549K.\1026553\EOTpO\USm\172246\&4JS\119270\NUL)bL>\ENQDBd!\SYNc@\SI,Y\30028`M\47712M\SO\8923{\1025087\ETXk.\30014\EOT\1066825`\120441Zu\169464;>\1079442\CAN\1106555\1111972\ETB\51402%\1109382,Zp+\rM~Q\r)Hv#l\168927\FSDHS\174628\999635j\987945\163692eWG\156827\\7\RS\1052535\&9\\\t\DC1t\1112332\1073530`\143677N\1010456h&\188126\1030547b\6757\1027169\1067336\183902[U\136396\991560>\CAN%\nO\78186\24509\b\1052137\&3Za.u\160812T\US\1033241\CAN6VR>P\188486\1062205\1040066!n(\1023327sWSekN@\24878t\985533V\145005\1095963\r+\1017241B*K\ACK\1074664]Ani&\tsuX/a|,,\74249^\129600\SOHty\ESCf\146951\NAK\150050\DC3E\fS\1032730\SOH\3856\SUB:7=\bgux(Q}s\20372]\DLE\r\44488(\1014999\148549\&2E`yqv;=\DC1\985264#{\EOT=l4PE5\181488D\1035492\138684:_a.{\"F[\n#~\1063106\&50K>\bTp\US\DC4K\1084112/zT\186543\991672\&7\150007\1111071X)\DC1\\,\22596%\SUB1\SIA\STX\RS\10664l\135150\1044470:e\SYN\179418a\17938v\34834\ENQj\NAK[\1095674f\177286W\159061\19064\180331\\]\162954&\EOT,6\GS\DLE\v&g\1039892&36\120707\1051886[IO\1066559\ESC\DC4sS\1010996_a><\f\1020047(.Y}%7z\DC2Q\NUL\CAN\1050803v\1028497\US\DC3J\EM\DC1dEh'Em\133130\999811\SIH\FS\120427E\v\1050653,{\190522iQ\1097210\165473k\FS\1112852\&3}6#\1003388 \94215\1112185\n{\vY\ETB\70801\1019457\v|~W{\1035159\8185\1045959'\DLEO!\SO[\DLEUk\23328)\1101657*\1069854B7#\988898`\992584$C\r\SI\1055353\1075772i\121086\1096003e{N\a\US\SYN\7098\1049584\DC1\SYN\164973\1005568^\US\1074252\1056920\FS\180867^6\SYNWPe\nx\1022949:^\SUB[\EOT~u\26460\&7\RS \44431\DC2\23328O\ENQ}zi\1081683d d\166200t\996444\fk\172000r0\1044826\50464*\994866\&1)$j*<\51631\1008420v|F~\1020301\NAKm#^l\EOT\148762\139353\US\GSW\15496^j\DC3\1080990\aA\\.=3J\995313>\95982}.u\STX\989998:sk\ESC\1107636E\ACKBR2o)\STX\7667S(V\"\n\50026dvp\997353f\DC1\DC3\2034\ACK~KH4K\92412'\58542h\1050852k\1045053i&\a~\20933W\1033711{\1058407mp%\1091729H\1011114i\DLEdZ\1104024W\NAKYb%\bXwgAa\1084701/\1060643\ACK\DC4W+h\1089169S~\EOTQzZ~\SO\1094174E\1061848\t*\100572v\STX#\\K\RS\1045046\111171\EM\149568O'\DC2!4]\DC2\163000\ACK\ESC:\59217\NAK>Y|\31158\t\149865\NULuv\1087835\aV\tb\STX7\1095030~#\184376\165077\41742\1035571\US\DLE\13715\v\1079101\NUL\EOT\1064658\136028\ESC`\1005448V?\1069622{x\SO)\DC24n*\997306nsI L\DC2\CANvo\EM\ap_P\tJ-Pk#ui\1049155\159822b[\1067444Yd\180222\35890\42013V|\49216q\1097565\r\CAN(yW?#+E\SO?Y\29463\63850\DC3\DLE3Q$\a'1\1110820%\r\153831@W\26659-\DC4\52370v\DC1\1013997a\r\SUB\NUL\1009250Jz\983893n\986832\133570\5867\163028O\t\ENQ\999543\&8\DLE\38142A\\$\5442\n)\1058130t\178355\166333CE\163128rpj \DC4O|}\72274\150000&1\1012087)\98960\138897\133873\53513", cpNewPassword = - PlainTextPassword + plainTextPassword6Unsafe "\GSATD~\GS\EMK$\1042815h\993958PO\rPp6}vL}Q\SIO?\SO\SYN2!%\140960\98456\14965IQ\SYN\DC3T\DEL\EMb\\n.\EM\v6G)j\NAKo*G\vn\DC1q\DLE4\1057182\190279\FSG\a\1078359\CAN\STX5\US\r\SO&JH\v\v\GS\SOy\FSZ\1087012*\1054369C36Z\100291I\1006927d\167128\136845X\NAK}\vX\13655\ETBE@\1047002}S\DC1\44983:nS\1033701y\1032307\&6\a1\RSF\151742;\78820|\NAK0\1046714\ACK-\DC3'\1052372\t\DC1J\1031338\t\60374\ACK\1100306&\EOT\USfx\1078008\998032ev\t\17415^p\ESC\41380=1de]\988835zK\ETXt\168935\EM_\133793\SI\128297A\GS" } diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/PasswordChange_user.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/PasswordChange_user.hs index b7296b5393..f376b73704 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/PasswordChange_user.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/PasswordChange_user.hs @@ -17,7 +17,7 @@ module Test.Wire.API.Golden.Generated.PasswordChange_user where -import Data.Misc (PlainTextPassword (PlainTextPassword)) +import Data.Misc (plainTextPassword6Unsafe, plainTextPassword8Unsafe) import Imports (Maybe (Just, Nothing)) import Wire.API.User (PasswordChange (..)) @@ -26,7 +26,7 @@ testObject_PasswordChange_user_1 = PasswordChange { cpOldPassword = Nothing, cpNewPassword = - PlainTextPassword + plainTextPassword8Unsafe "\SOHf;0B+CKY\1040633W\ENQ\178683\66681\1079258\1036336f\SOH&\166643\1050584\1022602\1091853\145882\tX\190821\FShl\1020866^A\153646=\151238\EM_ow?\\h4\155435\68388\SYN\11851\&4O\NAKnJT\EOTr\CAN~\ACK\ETBR'\992244sX\1001766mlCHvg\1112425ba\1003664R\\\1034092o\989312\1056334*k\r)H\180403\1051096\n#\14366~\\9Q|\v;\USd.\1066580\&0SHP%\1019462\22215'!\1044148N\SUB!Kz@\NUL\74079\1087771\SUBVp\1100111\38836\STX3#\DEL\DC4}}\1094237N\120442`\169346\&7\1036101\DLE\154725^\STX{`i:\rUT!\DC3\1111700\152543\NAKWK\NUL\1098445\1102182eA\140938\ETX\172001\1034473t@?\1014650\SOHJ\1074486\&7\RSg{\78258\&5R_\DC3u\SI\153435\1082441`}\DEL\66836X\DC1\175200D\25079b\176836\&6T\141840\167124p*7\n\\'\vO#\FS\174827(H\NAKn\178850\1015713}2s\143401\&8GA&\1004513\CAN\1068132d\9056\SUB\1059104t @\1056816I/\175842\30192\DC35\28889c\EOT\1046281\22594Uk\SYN\DLE\1099103\&8\GS\1034138\94316R-x\999901\1007697\1008634\DLEO,Z\ETX\1073959\63275f*\f^>\EOTD\r\SI_AQPO33\96451/F\RS\185177y\77854|Fn\1010492E<\1047147\&9\ETX[y`e\168776\65402L\SUB@4i/*\1011887\1102541\9070Ih\SIC1\1031432\t%?kFt\ACK\DLE\US\GSN\171039\f\1094027:\aV\ETXj\18014\SYN\SYN\150071\EMK\1083674\162115\40502Uez)\1080936\FS)8vT;\GS\21613ay\ETX\SI\GS{C=\EM,\SOH\ETXO\162859\ETX&\SOH2%<2s\f\SYN\r6ivo{\1028087WN\1053937R\1039894\1030129\995717\98891[ :\USu\180666^f\1087790\CAN\137895\183333N\SI\145270\EM@pK\1078668\&9\r@Ze\152611f\DC2x\59319M\30205;j\SYN\29669K_~:v&Dpx~_\STX:b;bv\DC3=\14812\&6\SUB\41242\ESCy0Ho.B\"u*{\1018548Vw\SUBW\138263\173995rbY\51982I{q\1041374\ENQ&_Pt\182926'\917559\&5\v\150891\35898\35323Ue@YM\164633)\n\EM\GSn\EOTZ\SO\DEL\\\f`f3T+_\RS@\a\RS\186662}" } @@ -35,11 +35,11 @@ testObject_PasswordChange_user_2 = PasswordChange { cpOldPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "7\16927K>\97741\186669m\vG9\tO]kp\63012\SUBVQs\t\984613\1108746\ENQ\1021022!O\998098\EOT=\abrgK_D\1033730<\SYN_\1100470\1086629\ETXH\SO#w9" ), cpNewPassword = - PlainTextPassword + plainTextPassword8Unsafe "\r]Gy3T\1026217\EM\1020078|R\\@\1056800Xq\155479t\EM\NAK\45450\1031406GU+p\1028583\1037856G46\1111047\a\145730u\EM\SI)@\2452\nk7\989251\22005D\11178\1075520\1105369\&7,h\154963\r\1014527\&3\a\13276ki\SIuUB3=X$\138590]\1046903\bSaAr8*t\DLEX:\1023144KA\SYNu$^rK~`\1062546)\174565MJ\1062282\1020633\SOk\SI\EOTF]\DC2\997860\b\CAN\f=p\1041758&S`\b^\179839;S\\\DC4N:\SO\f\NUL\1076187\&5f\127761~K/\ESC\137715*:\1033030\ENQB5\158024\NUL~m\DLE2\12820\1079647\NUL%\DC1{H" } @@ -48,11 +48,11 @@ testObject_PasswordChange_user_3 = PasswordChange { cpOldPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\DC1\1008131\"iQg2?.V\nR\ad#NZu\SI\154091\"B\USm\1066170\DC4?_-%\SO\DEL,\b\SYN\78542\1070480%U\RS\95262%1\2330\STX#\SUBV-\163363J\142686O\ETXV\DC2Ga\DC3,\1094317O3J\1098970n0\1052934j|\23339cF?\1019037x-\1069855\1094636\&160jp^\179153\FS>\\&\ENQ\EOTg\62450]\1073387\RS\169810\US%\990256\1042714$\985984R\1044140'-^I\1083467 kT\bZ\999047F\t\1084750F8R7\SIYN\EOT:N\SYN\SI\vd\57930Uo\1017473\1052974\vi/KA/\1004923\1051639\DC1e-\47612E\SOH\SO\v\SUB\1057038c\1090019\1003618Z#\991058e'\RS\120431\"\CAN\EOT\SYN?wO\1084580\DELI\2368\1005674\1041651gYJ\147444\&9p\CAN\187441Rn1\187124A\GS1x)\146547k\23622\DC1%S\1016329C>\134586\19597G6\1003504\RS\97878~\996492avKH\GS<\1082858sNVe\7956\152082\DLE\188847\f\ETBmpc'Xi&\150774E|V\1073099}\"\NAK5\96146&\t\f\DC4l|p\a\1024356\1036737UOM%a/9\r\n\1095590\1055708P*K\1073690%\NAKyXE\165112\987387L\DLE$f\ETX\DC1L\\l\11245\49768\\Q\SI\1002707()\58946w.\172820\SI.&\31267nk\vF\143976:\1038638\2606\1016120\SO\RS^\ENQ\DC4\b\1035479\1045289\RS\EOT\EME\1072274I\"W\1104244l\ESC\131418fB\23703+R\1113063\59494?\1061998^TD\46012{k\181947n\60196[|g;\71853\1095649\18432\173156\16164` \9356\1082477\174851dT\1015692p_\13046tN._\1042851\\\52588T%\98330\DLEC\96142\1019008\9148D\NAKrPk\170211\SOwLe\1032698\20202\1022050Jj" ), cpNewPassword = - PlainTextPassword + plainTextPassword8Unsafe "\EM\GSjg\rcpq\ENQydjzBvM\EM-\181560x\62219qO[p\RSF\1043280\&2\DC4\160364\1019066\EM'L+z\SYN-K\94385\&0\ENQ\US\ETXP\DLE\ESC&h8\141548\1084128c\143493\1009984 \a~=|hx\1031253)V\152928J\991022!@;*_\US]\ESCd\FSI_\nK[\DC1\b/dS\1020193v1(h\ETX\152908-UL(U\nm\1062628\\\1049985\t>\FS\EM\190594~*$\1056230\31211\148228\991805$ch\ACKyCFOIo\DLEvHeF%\168128\&8w3I-\77839(\177181\161298r.\998529s>\155909@\ACKb\EOTa\DLEf\68669_;[-.\1058443q\GS9\SI\145931U\1085428\CAN\ETX\SYNbfMq3]N\160390of?\987479\&2QU#cY\DC2\ETB\a\134728\&8c`\DC4-\1035600\&0_,\61186\DELd^\DELM\1082727\&3\NUL\SYN\DC2`\DC2(z\1073614R\1073511\158846Vqn\94033\CAN\186179Ap,\68655~:>9\SOH\986818L|\26590\984726\&6\1020946c\31513^\1077430\NUL#\68875\7357SD\t0\GS^P\nmg,oVnT%\1074906#\1079052\185568f\32331\FSG\NUL\aPl_\EOT \1071732x\DLEZ\EM\SUB\DLE\1082444\CAN\9126\NAKnSq^lw" } @@ -61,11 +61,11 @@ testObject_PasswordChange_user_4 = PasswordChange { cpOldPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\996042\1013508\137442y8z\188910cH@ge\96750\bJ\ETB\RS|Z5j\f\USJ\DC3\27867#\5822 =\ETX[[#ch1T\SUB\1062971\159509Fr#\CAN\1067286=Q\1110054\162733\n\RSL-X%\1070501h\1065080\1089630xDM'\12493aC@n \SYN\RS6\"e\985673\1062591\ENQE'H\DC3\131230o%5\996218\DEL{\1010852/KU\STX[\153798\1110733/#\1111047H\DC2_bNy\1099854\vH\RS]\986900wdg\1006378\ENQ4|\991191%\GSzgvb?QC3U\vOL\1090175\1009217\171249Us\985275\1007556\1056022y\ETX\1006666a\1089443\1064461IQ\NAK\1102475/\1025821[\146525Y\1110273\&0hg\NUL~:x\DELd\fZDQU\SO;\a9m\\~\f\167899=*|0\1089233\40380R\FS^\70516B2\DC2\1019556y\a\985058/\129335@>Vh\40618\1019580\DC1h4\n;Q&P\DC2A,f(B\SOH\1028143 \138873\1052427\f\140570$3\158205\t\t\DELs\133507Vp\SUBnDA\nsv\151492!'\1098710\144726X\r2\139117r\186851!@\51165\DC2\1073571%\1026015o\"\bi\1075769tV*\1089261\1000193\SYN\52519\1026058i\"+jB-g\40752\RSL\v'\1089204Faf\988489^\997807\69921E\fo\1041666\1032996_\1042556'\1071888\&9 ,F\95367d\121251\161394\DELY\7850)\n\RS(^\"L\GS\993283\1028777.@\DEL0\DC2,w\136018\ENQ:U\US3\1074021(\26102B>\SUBLh}8\36317\1071795\&1\DC3\FS,\NUL\1036218\164959\ENQ\1101169:\1105205J\1060042\n\NUL f0O\1023842m\36567\ETX\b\STXg>bl\1028623\44691p\SOH\45834\ACKE\NUL:fQC!K\1013456\32733(Va]\FST&B\EOT\b_#`\1041118o\DC1\165469\CAN\DC1>\138365H\1018054^\983454\SO\1088879\1112501H_a\1019703M\1094145taIx!c\64005\ACK\GS$i[\147426r=\ETB\30388Dbpc\GSt\96715\51391\25397\1098750H\1008635U..\160586\136531K\131733M`u|V\1083030#s\7110v\EMP\1008700h:H\ni=\150174\49091\ACKK\63386A\SI3b\EMd\EOTk.t\FS]f\132877\ETB\22782\DLE\f\1013087}6\17773l\\\1063285D3\DC3\USa?FR sHm\ETX\1105953\b[\DC2\STX\1091150\78391O|#\STX\GS+\145799\1109990Tf\6422\1036975`\SYNNL\RS\144764\&9\SYN\97231\988154\EM\1019553\ENQ\989472.DKMf\991253}c0\US\rFZ\1025650.\1068209SK\DC3Isq$>\128748\149897^+\1101484\1014800\n\ACK\"^\177274N2Uo|\GSM\27950|nZ\1078716G\tQ\41315\1068764zzGp\FS(y\22194\13258Wg\1110206\15989\ETB\a\142998\83001K\1041605\140118\138647\1044203G\1017800" ), cpNewPassword = - PlainTextPassword + plainTextPassword8Unsafe "6%CdZ\NAK:]a\160757G\1100807\aHT_\ETX\184817=\1094974H\NAKV\n\188284\43568\SI`\GS4},m5C,)pOU-z,m)G\1085731:\13371\68388\58925\NAKVBlt:'\SYNr\161012\&9\SYNZ~\53239\r\131378\"\b`}l\ENQg\49807x\EMsO\SUB\r\ACK\ETB\1012862*\119158\ACK'\NAK0u\1063315+.N\155568\&5\DLE\996563\1059464\1095806OE[\1066634cJJZ.>#mY=\DEL \SO\1020809\161961Fi~8JT\RS\t\DC2[E\70439\SYNO\SI@\1012929J\STX=\DLE\SYN.\1056562#da\1101967\SO\FSrCR\SOH_!\ENQ-Wtm\140222u\STX\1093627\&1\SYN<\1086071Y\99519F\1092290\174518\165124,H\152431 \1016376\1086967\65320\1078045\100936\161880\64562\n\RS07e\DC4.\1017260\USl-W\1036127\169524_\rSidOZT%46Me]\bf3X\SUB\30968\r]E%uW\1037702\120955c`\SI\1084987 a`/\DC3\1066414}\EOT5\EOTCMY\SUBY\1018010l>xH\RS\169677\26707}vy\SYN\DC1 A\FS\15039\&59N\29728\1000117\SUB@\1007505G\187702!xi\59210\&2JK\ESC\a^YMk\CANQ\t0:\DLEzo\NUL\DLEx1gU\SI\1005915\&8\142146P3V&\146215l)A\168185>\SI\tz\40878R\171716,\rLb\187682\CAN\983254_G\1019834\1008637EY;\20022\DELNvs9fmb#\1103912\46381g\1086578\54419\986014fJ\60290\v\1003578\180699SFA\STXA$\188361\135582\NUL\RSA\1069366E\SUB~\997873t%.P\t\SOHN\23780=\1058283\ENQ*\42808Fm\987705^gW\STXBN?\1062464`AUpn\SO\58276i\ETB}\NAK\35802\&8GN\71264FAxE\\&q^al$\1099577\DC2`z\67120\131492f\ETXnux\149811q\FS\CANy\ACKb\1075992\61816\nWZ\24019oFZ{to\EOT\a\58806\b.\141033\1061510/'\\bL\ETB{fp\983623\1076286O\46626\GS8\1055057\1088721;Z|< \153326\1088059\1111453\&4aC\SOH\161524\SUB.*K\"\"\129454Y\167276v\986403c($`\SOHK4d\125249\FS\122897L\992931\EM\1063797/AnK\163512\&4\44876:\FS\1071653\1048482$\DC3/Ug\143227iyBpz\CAN\ESC\50988M\153299\t)p?\160170[{K\1064379C\187515/i\129567\1015971k\SOVyO\EM\1027000_\SYN\1092978\137534\37394\ENQ{+\150519\CANp\EM\120158\DC3\1039610}\ETX\ACK\rpf&:\SI\EM{[\47214\141578Pj\DC2\1042947\175183;\tz\13562\&57*\ETX\149429\a\1099670`\rM\b\1065597\a\1061713W5\146248v\61801 \63453>Z\127207\177364t\SOH\99385 \24048@Vd\1098979'6`/\RSv\ENQ<\EM\1046071:74s[\SI^rcI55&\DC4(\1044403}5\1072105\t\SUB\1019144g\1055613\ETX[\1049131\1027231\v[i\1106618\ESC$\574\31775#\bq\1086447T@8\183810\1018524\1080923\DC4.o`\f_27^6>\1018938\20504s\175505q[\161155aeG\1042361HB[\FSs\92188t\RS2)[Qc?-\1006821/z\993159-\US.k\32238\DC4Bc\72192c \b1\SOHCE\DC47\171040\ESCw2.{\1014032~|,\EOT^\1106499}x\1099466\ENQL>>P\168482,.T\1049248`\1106998b9u.(z=@&b|\1039337\DC2\21581\ETX\SI~\vz\159863E`\FSe\US\15482=\"QwNN\129353lzq\190036eiq\SYN-d\137123.-n\SOH?B( T=wf\995467\RS\DC2\179872\SYN*|\147417DM\37567*:\STX\189754AS\t7o\64289(\994294O~kP\68006z\f\ESCe\a\987232I\DC4\DC3!EL+G[\r[_\1091777\9539W<\131337\1098445iA\168912'F\RS(SE\SI^\14294l\1054709\US\DC2\SOH\n\41372j\DC3\156318c\17177DN%\140618&\1004034\ACK~^\35003\58010\EM\991741&D\156963\NAKP\SO,\SIk#\bb\33222\t\33164W\54708\b\169426li\155619my\GS\133694f\US\58936z7fFkx\181089=\96578\ETX\1045516;pk\1103897\1096717.\SOH9j\1106500$\1083366\DC1oc" } @@ -74,11 +74,11 @@ testObject_PasswordChange_user_5 = PasswordChange { cpOldPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe ".vT\149065\158692){\74556g\1110206\1091301s\1012653\157052\ENQ,~\ENQ\132963\ETX\SYN\EOT\1103480\155219=1\DELo\989094\ENQ{}S;\DC1 \NAK\987299Y\129547Z\GSqPuPV=I\FS\ETX\1043494\60432\SOmRV` '-#\RSGcv]\47869>~ \ETBN\DLE\155012\1109063\181243\1002700\NAKF\CANu\US\43300\78315\DC3\1075822>z\\.k\SYN[?\\\100534\&3TJWN\1033469\\\23429=PJh1\991408\1081195\47549\DC1\1021540\1100099\36799\99980yyf\SI{%a\CANK\165733?Wl%\185431;Y\"\177839~\SYN\124986n2*F\983249\&0\63886Z\ETX\SOnp\t\1008554rz=x\DC3N\50460*8\tj\1085763\1069586\1021364R>\1094815\ACK\1052450Q\US\1016757`kE [\94447;\94463c\bK\f\1003111-WW\SOHr\SOHndW=s\1064135\FS\SIO\176630\142291\1022975l\14890<\SOGx'Z\41402\26364\1054258\STXW\1047089\1022246hS\144850\EM\134018k`mW\58467\25020\&9\DLEE\995366XON+\DLE`]\SO~g\1044869,9\t;\DC3\1050886\3363pD6s\157184\ENQeem\1045132\SOH\24377Lo\1082536ctA\DLE\1113917`B\EM\94062[1<:}7]&\v\44512R\177157a\RS\63093\&6\FS\10794f\ESC\1076238\52233v!t\DLEG\1015620\\f\t^\SOHB\SOH\180364_N+\v\ETXux\NUL.d\2283\STX%{\120714\1085733\134796\1048671uO\1061770\n\EM\r\a\r\36309\DLE4\1043749Hp\1091440q\1079376bJT<\STXVw\985328I+\1034709C\t\27376\SOh.,\1103086:\917965\9480{\ETB\995773zqY\STXE\GS\51683\&8vF}\170082X\42566\983317U\NULWGiN*v\173195\162226\154581\fR?i\1049259\DC3\a\DC3O(\187320xa\NUL.\133821\1058197\1098767j`\"\64700V\176930\69639M+m\STXJx\FS\GSrVs\SYN\GSJ8Q>l\tJj\EOTDGHj\CAN$X\RS\119922D%\DELV\EM\SO8\988454Z%ah\1074629\2919KB\1036581\ETX\SIP\1041071B\142456\ACKe\1093894Re2\1077169}Q:\1006282C\ENQ\1034308<\170708yS=qL\SUBd0{a\2279s\1075662R\1019777\133916NS7\SYNG\1052457N6Z\1026683\1010570\36133mP\DELO\RS 3\1004867G\96938:,\991792\US\1040258\ETXpNgH\"i\190411\169538\CAN50H\RS\51809@jiHF\18488\1089326S=#\EOT\24653&M\186999\ENQ\188436sB\EM\NULVuJ%wk\US\USJY\US\SYN\DC4@[\133710\2562\1102116\170261N|\25196L/Fs\EOT\b~khlJ\\*\1083562tv3\STXsg\ENQ\DC2c>\48829X\985867\1024387\nRg\NAK ;\51240'{~\\\1070452oSr3\DC1P\998414K,\1058087zN\r8\27838\\\165356\SUB@\DC1\SYN\vF\DC2V\ENQU\1077217\ENQy\1105981alR_\73963W\SYN-V\DC3\1058513|\RS\NUL\14311\1069223\DELV0\aHa\162915?PXj\SYN\DEL\985879\49021&t\"V\57972hA\42234\t8\NULk.\189070 \1112762\ETB\59270\185654U\GSQ\1063565\44619p\1061081\GS\DC3\1108003\NAKKR`\57737\40884\&6\r\101067io@o\ACKkrla\174009,\1070019\&0A6#\CAN=\DC2\DC2\161497&\RSan\149845\&6y\SYND\22050a\f\149068h\162218\&3\ETB\178246#O*\ESCy\168142a\5632b\DLEL:\axng6\59689\&1\1040365\181996\65902\ETB%\164339\ENQtfJq\1045673\&2T\SO\DEL\126474q\NULq#\1012957\1002852\\\r\DC2P\1024058(C\1050472Ph\GSBthwz+'\EM" ), cpNewPassword = - PlainTextPassword + plainTextPassword8Unsafe "S\1034541[2\119932\&0h!\1064848.\987603*P:\NAK(\GS\ETB\24253$X\179274K\NUL9\b]\170369\1020647\1051557K0\138808\DLE94_\26880\SUB\994542a\176494((\998954TXm\DC1_}\994198m\\o\120794yJ\ETX#\b.K,\151241\126477\r:bj\158134.\14517[Dg\49015\152214BH mH\181369\990387[QnJ\EOTo\\\98736*r\CAN\984313m\146285s\SUBD\17341{A\78451p[\131098U\RS\ETX\"%Y\1089637\v%\21671\1105935e|\67637\DC2\ACK,}\176528u&\v\1067595\US@8+\917796?shAqmaA\DC4I\NAK\988836\SI\SUBl\at\1097599\14469vd\187527s\ETB\SI1,\1026043\1092581\68088\10003U#\NAK\NUL8\993973\SOH\165172\178585\&25L7l4K\ETB\ETB\US\183298(\141108\CAN\SYN`!=d\16001\DEL\37607\990640g \1007747\"\DC1\1035551)\ENQG\1075268JZ3\29025/\147766\RS\ACK\28620\DC2\DC2i\DC3\NAK)\DEL\ACK\NULXs`\15691MUmZ!y3\1107617\188523`n\USs<)n9\1030989\GSB\1029508g\1055800\DLEz\ETB\110827T3F\140208\&3\1088347\SOHnx\a\57612\&07\DC4;H%\SI\SYNsQS{%\tj\46313czj\DEL&\DEL\r\1044089\165481\190596,\12670L" } @@ -87,11 +87,11 @@ testObject_PasswordChange_user_6 = PasswordChange { cpOldPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "&\1107610,\EM\DC1\SO\GS\NUL\\Q\143835?&%e\751K\991656g\142735]6\1072568XDu\989822.N-Y\SYN\118870\NAK\177961\1082599Z\1067051\41571\"IIzef\172747\157154\1100946\US+h\1063035\156268@T\n\DC3e+ FG\1040063}\1007879p[\1019675~s\58897W\1002225\131986[h\163754$\1014199d\1001302\135635\1083326\r\15121\&9\1054919\SUB\1033452\48331B\146637\1071032g\RS\34856\SIpN(\n_\DC3\CAN\ETB\994340u\"\1055984cq\148292%\168571@_vDc\1073055CW\ENQP\136867\STXHfp;\nM\1110028\154200)mg\1000362}l\1072450h\t\ETX\14968Q\1021295Tj\\b4\FSK!J\\\996951\1037918\ETX\16997[$\1006298f\US\FS\97025h`f#cq0t=4\DC3\v(n\CANb\SUB\CAN\FS\RS\US\157568\1112545W\ETB|\DC4\26469\SOH`\152656O*E_\1014509_4Lrc\1067039\68473\FSE[\GS\95227vbvn\121463\176466WFW^\1109674\&2\1092465\1101465l=\191025\1020663R\1107046p\189999+T\36798\vy$\EOT\184549LpY}\EOTZc\118805zLS\1099150\\\119989\&9Gzc\120792\1050858_\DC2H!#\169248\DC2d\177928!229\NAK\ACK(\1096427c'\142061\"{\b7\tM\63131,#IRi\1091628\n\994326\155033`\DLE \ENQT|!\1097357p\CAN\FS\138789\STX,\94330\r2\1082495s\1097275\\|\35843\ESC\1078746o\DC2i\b\11053gkx\994356kd\1066993\EMi|\13736\65150\160960\ESC/\1010989\&8\1069363:j\1028017\"RM!\96723`()\63658\&2\135558.\1049513B\171714E\1017316\1070909\1028371\RSR\NAKJn\1032860[VZQ\127514W\NULiz`Ie\1058604I\DELMY#)R6\64879\178752\&2b6\tX\r\1048312\1069402N\171772W=\STXAS;q\123203\1083930w\a\SYNptS\NULT\fj\143164\194759T\fSp\68448\ACKR*In\DEL=X\NUL\66188\vM7\121298s\1024216v!\1084042?\1022676I\1082108hQ\1062292.\SO\\\151754j\147624\a\1077885\ETX\1074145bE\1091072\ETX'\1023670\48208\SUBK\GSP\DC3\1081278\DC1\1085046\159684\a\139723n&\1108740Z\ESC\179659 wA>\141155\NUL-\NUL#\"|\165468A\t\ETB\1041615_u)\165061\143580Le'%*\1107600Q\SI\RS\111344\"2\vc3yVbV\1042395\\e\168551\STX\1090925esJYso\163169o\FS$I\1091068oz !\DC3\119098r\RSzt7X\8274%$-\1046768?\SUB\ACKA\SUBkZk3E\t\1067050\ETB\1019523\&1D-G}\1056157\&2Y\DLE\a\at\GS\7200\nQ\182489\1094286et\USK\DLEv\tN@?}>\CANz\987816j>w\DELc@\EMw" ), cpNewPassword = - PlainTextPassword + plainTextPassword8Unsafe "b\170229n\1018971\SUB\985694C{_\70741&\vMo6gl\STX\"8\1028959\GS\165216\SI(\138759\ETB\DC4\152692\&1\996992I]\v\RS#\"\1112677&aGfR\ENQ\1018847\&1:\ENQ\183934\1047759\&4J}\GStZ\DC31b\by._\26020\165646bK\SIE\1034932\149906dq\128297\"\"\67990L\94037\SOH&\DC3AIx\1009451z\11657z_\68118<\1083603=>FA5Q\1025568]\SUBS\1067075\t\1027792b!\1011202PY\1058512\SYN\1097813\aCK\RS\151398>\1637;u\ACK\ENQ+n}NzAP\69805RXe3Q4+!5-{\43538xEx\173125\USU\ENQd\187890,\DC3]c\164824\&9YkkCUR\1052545zz\a_\US\1052913\FSh\bX:\1097581\EOTs0DC5.UEt\1082356.~\51232\188301#\993299\DC4LwU\27171\1014215q(r_\SIAq\7019\ESCg \1034226G(\SUB\b/)K\1022799\1006348z\166521\&6\1073570M(mHu\1072369B\141057}^Xz@\27397da]VDH=xZ`E|\SYNEr$P\b\SOH.\30581;8/E\1056666\156080G+\USF\1046048\189590\1079895^\1072919/\DC3\SUB\SUB\STXy\ETXl\1012320<\f\159886\DEL\EOT\47816\&5\1010161 .u\ESC\f\1084279a5\US\100760h\988443\20830_\1112230y#]R$a/\SUBg-^:,\134242t\NUL\DLEFd(\SI_r=6&R\39368t#/\30862\1083006\55251\DELd\139094\f\bLL\SYNrl(\95410_jgp^B\148359pZf\131184!\1100088\1079773\191219/S\60206\157985\SIP!\SI\1030276We\DC1Q]\DELMi\SOH2\164247s\64188\59175\179637\&6_fIJ0-E\51588\39286 \b#\99545\52587\GS\1063696\57533\1094025C\1039590\STX[m|O-)\54684\132598\189752\FS\FS\31494\v^r,^PBK\175477r,U;p&~O\1003644\154009\DC1*/f'L)\146351b1mmbu\1070260\DC4X\ACK|q\ETB\186400i$\998123Q\170080\DC1:\NULa\179425v\1057890\ESC\1046601O?\144872\1001618\&9 m&\185419G\NUL'*k7>y\185109)c\1026066\DLE\t,}I\SIzT!R\1051585\&9u\EM\DC42ixf\ETBh\1093277\45899_\ETX&\t\9508\57743F\1054634K\151449\t\ESC8\n{2\1060622\59202IL\SUB\1114011dp}_9\67990:Xd\66188\134097v\ETXjk\52228\&0x\SYNi7y_\DC4\94598La\tK\SO.\SO:#\158037ZsIp6\DC3\tG\21697j@\SO\140605V\171781\1004444\1095580\n-0x3\1070457JsH\49717\&0.g}vU\985649\175749\t\1108868txWw$p-)NErg\DC1\RS\v\61996S\97223eK~\18154\1087578\&7\139648]:\SOW\f+C\ESC\1052448\131579\1070786pH\1082515g\ENQ\DC2W77\35594Il\SO!<\1029111\ETX\42368p)}`\47291}\143330\&9\US=$SXcjkM\186140Fp.8h\1047276['Ta)Zq\175154\4734\fW\51765C\1027418\1103868\1101167\1052280u\nc/\1112595\156385\1015057c0I2]\SOH]g\161033*g)\th\1024978G\1053938\GSv\USXf5!U\54369=#\vG\DC3\ETX?\SOq%]\147834\&7\SYNW+6\t&\1113103\1100216-l&\FSv\129474\DC4\1062308\1053188\ACK\1016990a\1024054m\1046519\NUL@~\1087194RfG>\154171q\CAN\ENQ\68901\DC4`o\r\DLEt\DC3\92906\1069460\1048347\53182\STXf\1094150\1068082i\1014049\1037453u\\\f(h,`\1047778m6\DC4f\SOx\157831" } @@ -100,7 +100,7 @@ testObject_PasswordChange_user_7 = PasswordChange { cpOldPassword = Nothing, cpNewPassword = - PlainTextPassword + plainTextPassword8Unsafe "\1078147\&7\65218\RSA\999884k\v\ESC\NAKg&:\NUL6K\NULn>\SUB\119974$\ESC8B'z\"Yc\1032069\&6\ESCU)\NUL\DLE\1101164Zj\44385\83195bJ-\US\"\131804L\a\1067731y\DLEs-}\141826}\GS\CAN\ACK\rR}R\CANL\FSqkZ\n\t\189000\&1|\f5\984053L\ETB8gL\18292\&5\10771cf\\V~\DC1\11412\EM\120833\990084\&1n\"\60837*\ETB\SIaTxU\DELcZ\r#5/\bk\v[\a`\1106514\NULR;(\CAN\rFMN>\995764\ESCt\ACK{'(\141540\ETX\NULT\1057079m\f6\63805B\n\987874" } @@ -109,11 +109,11 @@ testObject_PasswordChange_user_8 = PasswordChange { cpOldPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "b\US\1102445X\1020217\EOT.m\"\DC4\DC4\ETX:1c8\1003324>QU]\ETXRe\1032621\SOe\SI_~\t\EM\182748^BGb\991684\SUB\1032747,4Ux\44572\nA\19832\1062925\fo\CAN\1001285#+@\14237J\SYN>\SOH[Q\31322P-\ESC\DC3\1017636\64940\NAKQmD]\f\" \23277\26752\DC3F\1087665W2\GS\FS_\145580S\34366/\SOH@\1008116u\US \DC4x\989165\138589\&5\60472\47193@\995028u\DC3\127467\169198g!\174297HZM\178770\1109385j_;\39894v_~\1020633U\nzDxZ1RN\2467mdb}\152593b;s\ae|&\"\18230l\SO\1075495\73747\165342\418'\DELj\993281UB[\14633\NAK\"F\1071892\71739\&9I\1086187\SUB,+\169163\EOT\EMBtZ}\SUBZRdm\DELA\ahEj\NAK\164812\1045403\49808!i4\160130,,-4T\39327E#\FS\129309\SIKj<\1109332\1019724`R>\111006\139594\DC2Vq4\DLE\131391\&1\51249\ENQM\42303av\181926\STX\1016985\NUL5\164635!&]\22190p\v`k\67413\&6(GB\1042616\DC2L\996758a'7L\1096604[\ETBR\1022507|\1020702bZ\1060760;6\GS`\NAK\1055957\SOn\128679\1080437\1000675L\70839Vn\189246nw\EM!*bw\r\1102406\ACK3\25917J\1100924\DLE\1079071\RS;9\ETB\51636Ts\n?l\171848'y*{ G?>y\166331\&4\1028518\143808M\ENQ@\1106697\&19\62848:\GSfI%%;p\1057791j\STX\52156{7\1045649mR\170180 \1045874`b+\189602\1095783\29108<\997493b'\1113133G\1113924\187365\1018965\DLE\t)\ETB_\STX\188043\ETBq\ETBD\14549\178567\&8\GS9S%t-;~A{\1098493\1009689t\RS\997797\rD\SOH\"\1036045\1080223R\r\r\SO;b\1079046\RS\96789\64328/*m?~G\1005579Z\1029293)\141393\134174\1004939lL=\1066280O,,j;x\SOH\74911\a\n2.+-\16525&bd\142521.\NUL\1105545\DC1\61097d\1016348q\ACK\v\"\155055\1051009\1111466c\DEL\a(./\STX\10580E\1095607P;\"\1100473h\15195{\21638\1108997\1001215Wme=ny\DC2\997396(\153889\990739 \rC\DC3?>ZD\SI}j\SUB\SI\GS\177033\1081156 )V>\1073618\1110301)*l^ip&^\EOT\991196&Y\SO(L&\FSs\1025953\SUBm\194690OR*\1083553\984637UQ9a\173357\RS\"\170635pt\DC3hxbnb\144388\SI\1096629M\36441\183861fJ?}t\1042071T\21290\1041177?\GSg&V\1107865D\NAK\58427M\1083184\&0U\b\53742\1049758\1019549hjT\"\1047744\EOT^\GS#\NAK\tv+t\FS\USL{\1011965\126503x,\1024988\DC4\1026933<7\1074268\ETBw\rz\159720\985242\NAK\SYNq\65888\1019932$\1038698|:\v\SUBd\n3]0P~Q\FS+&@[\v\92526M\v\136444\DLE\US\a_S#H\ESC\1000365\178961\66613\\\"\b}8\USF\t\ENQ2|,\t\US\36910\996072\DLE+\166272D:\148639\&7 \GSe\1085048#'\DC4MpE_n\95537~\1018210@\1059219&=1\1058223\1085407)R\1035591\SOHt\39215?\94500\32949E\171322\EOTzX\1061392/mZ7\39206\ETX`m\1055925!$\v\DC1Z\SI \DC3,\14510\STXil\DEL\DLE\7164\1027803A~EU;:\ri\1083540l\35399vc$\SUB\DEL_\1081553EP4\1103837l~|\a\1051360\ENQ\SYN\1096952+\1074553J\19836\t\GS\aA\EOT\NUL4g!\62787\150045\22255\183201\STXN\ETBrrpN5ks\bZM\ACK*z\DC4(Pl\DC1\"\70701\1067954K[\1008837RH\1032341\CAN[x.\1048119ac9\1111224\59370$\19588\ACK\f\74197Jy!\29122" ), cpNewPassword = - PlainTextPassword + plainTextPassword8Unsafe "\f\CANj?\ENQ\100674\27294`\v\60820S\aUZ<\190604\v\n\98721\SYN8,\SO`\ETB!\1023917\SUB\988878 \149166~\25356\&7k:\SOHo,w\US`d\1095991E\170702w\ACKl\FSOUl\DC1r\DC1\4696\1005535\177324Y\NUL:*\SO\51294x?>O@mGy+Z,\SI\31196&\EOTZ\14202.w\\\GS\CAN\DEL\1061426\FS#!\100667NFM/\b\168841 T\183374\1014354K\nG\95679\1071981G\1014345!]\GSN}\1071684m\CANH\1032944Nk?\61487K\EM\131256ov\48786V\r\184775p\61887L\1085562e\175014\ESC\173236\USr\135299\NAK\SYN\NAK\DC33r^E\43094Z\bP<\135606\138117\1066630\178853\27013\1078589\ACK\SUB\NULEa'*W\177921\163435\176746\EOTLV4\26629\"-/\118982\1108171\&3\19533\GS\SOH/xiy\1004921\31236\DELZm7s8l\1032610BT({b\59819n\DC1\154649vD\59996\95915r\66886_#d\34706~\53775;BKF\993228] 6f\126105iN^\132202\CANeN\1050181X:\"+\STX\1000519C\DC4*\164663j5\1087078=\49843\163443\&8\46178\1005505\1086358\67354\&9}SK\132067\\0\120968#v~J mt3Xx\RS\US\1053047\v3\997095\SI\DC2Tc\996715\DC4m,\SIn!b\26969'ac\29011oV\18582W\100115\1029633\t\SOH\149270\SO\1052983\&3%hmTnE+\\%C\24956\137609\&3\986293$\1010528\983647\&04|En(\17123\SO\174091`Xy\1069572\984775 \132546~\1054660]\DC3\167285\&9Y\166240\&3T/\1057195\58265\tBS3;A]\DC3\2765P;.\1046618\US\DC3\NAK\GS2[\25411\1061324 \13123\165595\"Q^0\STXT6\18123yB\DC1?\NULv\1081840xd\1060136Y\DLE\1094984\RS\168967\a\SOH4/R\113820\1029185V\DC2A6\1016176(T\\O\USTu\152631\ENQ)\22634R\ETX\vR{\r\STXm\1044646\146582\SUB\154494\32280\1053397\EM\SI\n]&<\NAK-=\1052283|S)\165884\43834Rq5e/wH\SOTh\145516tjJ\1032777g\CAN\DLE\ae\SIV3\94033`\1100278s[KR|Y\CAN\1079014!\ETBM\1091893\157876U\n\f}!K-\ACK\ACKmp\DC28:\GS\EOT\r\1063024\1023444)(\ETX\1017315\983729\60554^\1097871\101098+e\30627\t\160747N\EMi\nfN\98956\149454\&4Q\n\r.3\31727|H\ESC\990983\998110\159749" } @@ -122,11 +122,11 @@ testObject_PasswordChange_user_9 = PasswordChange { cpOldPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\121027il\134081)R{5\ACKXm\1041631\\T\f\DLEp tq8\1070074\&5Q*\"\1087921@\DC4\182306\CANN,\162026\1112739;3.p\STX\fLA-\149790j\bON\ENQn\984593U:\EOT7@\GS\1102637ky)\74361eri5CF_\1086719Y\25273^Q\SYN\NAKQ\DC3[\31615\ESCaY\180511~\t\100087'\DC4M^\EMI\994154\&4\96550\1036840b\DC4r\1078078r\RS\140751\1084467\SIuHc\rg\DC1\ENQd3\SOH=p%ry\1003698\&3mhoP\1106864\v\162715yXMw<\59204\&1\f\1016334\ESC\3501lL\69237\GS$\a\1039285w\985184{)\NAKw82K)\DC36\155645v:\SOHC9i\1039062\17926R\1072663/w\99462\15991\185843e30R@T\121319_\NAK\ESC\\\1092892\n_\1069021\aiinb2I\RS\1098801\138282\SOH\992030Zep>H\1079810uUC%\tS_fH \SO\1084851OH\a\ETXx\a/\ACK\ACK\993315\165332\72410\183658b`%\31687\DC3:UIj\1021763\ETX\1053142\NAK\STXS\DEL[\1028930\&1Z\SUB\US\1088016h%\ACKw\EOT[yh\ag\RS\994622N^{g\1003836\993133\DC2\a-\1061684\rrJ\1036806}~;8\ACK0\1021569\SI0\ACK\ETXfXG\SO}H4\DC2zC\SUBz\DC2\DLEz\DC1\149550\61518H25*Q\1083850\10672\NUL$h\SO\134582\43597$,\65487\61824O)^\\nvK<\989262\&1R ,k\985467k&\1012054\1072126V\98741\170921E\ETBEP\RSR[7F(H\1006507\EOT0\\\CANk\DC1\1085575ql\150344,\DC2\NULv\SO\DLEp\SYNIqpU:$\1051572|+\DC1\SIT\1043680v:\54535\133122\SI\167063\190640\r\NUL\1080625--\NUL\1000447J\158492G\1043941\ENQ7\NUL'o8\1055620\ESC5wuH" ), cpNewPassword = - PlainTextPassword + plainTextPassword8Unsafe "<>f:;-RJq\6050x\161753QT\100505;X5.\1024124;1\fP={7\136758\151452v\DC4b\ETX\1041908\NAKV/R\SO\1003791!\SO\ENQv\SOC\v\FS[Q\ETBh\CANX-\66455\22025\1087386:BVj\1104389\ENQ\41001\97464\DC2\DC2zRZE\"\DC2\EOT0\164473+i\USE\52704R\GS\1015386\FS\EOT\154897\185499\USew\178235\vR_=\1105788\78173\ETBkB\172309\995208\&7;!?'ZN(n)\1003220|\151096\DC3\n\DC1\ESC\1047644\EM\14646wov\ENQ;\1082140]\118864\1041829\131956\&4\1085467h\42033?iD\DC2V\96442t\78699 %\177867t}%\168450\1068330N\EM\b\7477(\6702\&9~\168927*A\ETB\35836\1087213\ESC\EM7`s\988223G\CAN\171597Q\1032850\"\EMi|\ACK\151936\&4\49571\&1 ^\1034297\38608\1080861BBO\ETB\188460sz\GS\1113432\20959_PsX\152878-)\1013286\f\11345ZAT\ETX^\1103065$\1002688\1102176M\DC3\1106060\1083723\31676\135940\1010227}+p)H\EOT]\61870(fiL\74358\STX]m(c\1099516\1058859wN\135817Zs;&;\1101239\STXc?\nP\164370R\1073337\8218\DEL$\62817\1035797'(v\94886\RS\131427z\USV\DC1\995931+J:\1044870\28567<\161564\EOT\t\SI;Ll\31033Bl\1035926g]\EOT4yZ\143213)Qs\127306b] \149682\NAK{\\\\]\48933\1040819b{\1049468F\182947\&1\"O\NAK*\24604\US\989988\52604\&35>>|\CANH\GSZ_4}6\1056732Ty{?\CAN\"\1061905\1004010dM\a\21624.@\120724\DC3\984067hX\DC299hs}\ESC\n$\ENQ\1044696o+\3801Z\66465h7\172119fa$gT\t\1000627\1111076\1075382X9!\40902\a\EOTH\100163\1019832[\"\SO\1061034|};\1001901\55166D([N\SO\1037726g\180696\22235\142179Bxq\r\DEL\1109671\"o\4735\165730\ACK\20074T\38821do\au\151559\39351\SOH\SOo" } @@ -135,11 +135,11 @@ testObject_PasswordChange_user_10 = PasswordChange { cpOldPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "^g\165188\183325!D\1071796\&8I\rv4MC%k\\X\NAK@]\990411\EOTkKg\46739h\v-\EM\1003941d\r\n\46818\164542\DC1Yf\1031947\100661SN\t_q\34663\1024752nn,9\1002494\134950;vL\1005623?\vL#\1004806\159319\1005755Y\49264k\185970\EOTt\54951\&8\SYN\GS\DC1W\ACKxK\100106Dc\1028596k:\1088790\DC4\1024192\&2D?pHS@i\1055560\&5\"A!\127257\DC4\57827M+CS\EOTK\DLE\DEL?y\1098181\162494\47866<\41529y\FSk\SYN${\r\1005146\190068/\DLEY\20575\b\1039849A&\STX\b\998416E\a\1032363J2\120490\1018750\GSa+\nL\ETBE\DEL\NAKnAd\ETB>\ENQO&\b\1011115@:I~-a\SYNk\DEL}\USE)C\DLE)ts\SOH&\128490f\1031578O)8\83270e\59254\&9\58057O\v\r \bt\188311\n\1013246\46070l\SIWGb\1008559@\1059413\22227u\1026214B\1029435t\1109601Sx4bL\f\171190^:6\ETB\EOTz5x\FS\RS_\ESC\1105088\DC3\1111332|(w\1030422\ENQ:\45632\72881k\1036191\RSwC\186931E\1106146\RS\NAKJ\1043833)\120159\1023499a\1068709S\DLE-p\142797Y8~\ACK\f\SYNq\SYN\191139\1061750Vq\SOWh\EM\6136\EOTA\"}P^M1 \150446;\ENQ[\83011\78574cG]o\EM):r\185345\1099699bCeS\1095638E\SYN\39700\42082<\ESC\31948\83108\142987\&0(`\SOH\rr%Y\ACK+\1082430\142137\RSK\38850\1042506\&2ZK\EMc\EM\NAK\US\69438\51321\tyI~b\983734\&4\\t\CANu\1070201W\SYN\53093O\SIwT/\1054638Q\1005484\157400\&0\a\1040610a?\95679\NAK\SIpZi\2699o\RS}R\bm?\137670}\70000\42449\32037\ACK`\NUL\ETB\36412zO$\CANo\tX,gCWQ\FS\137405X\aiI\22269\1099880\SI\1056230" ), cpNewPassword = - PlainTextPassword + plainTextPassword8Unsafe ";\STXW\3658\&9e\61104\1010281u\1054825;z\1102201.\CANm\GS8HiR[\1090721\113679h\30385\1099071P\13786ux\EOT=\EOT@-\NAKU~\30622nm\181728\v\SI\"87xzsd\t/\SIABdv\SYN\1079721\38516ov\r\61169\DELC=|EJ.I1ZH\r\1109850A\157023I\DC3h,\150284\157340\40343\DC1\1101518\SIvxV<\100481\CAN\992513k79\6439\&7\1105382\29451\51856\ACKw(d.\r\986761tHWFI\b\2063|c\139209.ZR\ESC\1043464XOH\b\1065603\185045\DELKC\SOe\ENQb\STXtwM;\1063586Qb\EM+C\a*\991991\SI\68430#DEv\70295\&5p\EOT\1017741~&\92197\6498?\DC3A%4\111178\"v\NULg\tmD\991529\DEL?\148659\v\\7\n\n\US\1052346EbL\NAK!` \996371\a]\1050364:\99420o\98763\1038145\r\US}nre\190462=\RS!\1026220\RS_\n\r^+\1112053\155114j\144557h\164197\EOT\1456\1080248\&4jpQ\ETBv\1058697\&6XK\DLE{X\182304|\FS\30623c?\ACKW[\FS)_~,\187940c\133750Ihm\68052\GS\NULJ\"#\b\41024\&0Zh\147884\1013140F&\ETBJ`O\191380H\917827&'Ox!4\ESC\DC1\1038348+DgC\95526d\r'\US{\1062664\47822?z\DC2\17591~\96360\155417\1068401l\14806r\NULN6<\57679\1055613\141513]Hp8\1063393\&0WrR(\25161\19762'gc#_^\997158\31893\23078\179623E$zk\t\NAK4g\STX6)\993627]\DC3\187429\34110p\1012714C\1078346Z2\DC4M\FSJ68E\37649H6\EM&\131250/\DC3ovwm\14557&m\152064M\1027607F\146051>\f\21330\NULG\994703l1*bd\SUB.\ETBWy\154255yo\1081513 zbzr\1034720\32843\EOT\1000002\16121A\133186\btwr(t\ETX\NUL\1038295?9\CAN~j\1018782E\176705$\EM(&\DC4f#eP\EM~\CANCT0i\1007344e\1041535.\1023395u=\DELu1\173333\DEL=\NAKYL" ), cpNewPassword = - PlainTextPassword + plainTextPassword8Unsafe "\\2~\tLsG\1010318N\73893\19920\SIN\bA\1084446\149836\n\96723\&3\1005158%\53931F\SOH?2\985088\1043578H\184226\tWe(B\26887\SUBU\ENQ\v\188995\"|\53505U\1043137QJ\FSN\1098083\1056930eN\DC1pc\SUBY)\STXR,w\1068893q_\SYN\ETXM\179588\ETB5\19176*\182041\aig2\n-\au" } @@ -161,11 +161,11 @@ testObject_PasswordChange_user_12 = PasswordChange { cpOldPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "cA\1013876}1\986971`\11039gQ(6O\148533\ACK\ENQ\144020\1097898S\b\1026784\185340\1076893\1038191.tZ" ), cpNewPassword = - PlainTextPassword + plainTextPassword8Unsafe "*~\EM\SI\1035297\65924\ETXSi\917783ua\DLE\DC4\SOHgPRF\50551}J\SOH?.\35757\26118S\99244Wb\US{\191213\DC3PD6z;bU\SOc\168740{\NAKC|!\134012w\24191_\15393'XFa>Ds\1032722\&6&\160998BY\1105510\987553u/\146729\187725\NUL\DC2>A.>\1026064\&4~\1066860\983616\1065813\194763\41670n\NAKe4`v#\54249\154796+L\au[\119894H\1003679^\1052741PIo\EM/R\RS\DC3\150872>\a\1011374\&8}ahwE3\1009691k\19141\1096715\23648\131344\SOH>BwWU#\135314} \b\SYNK\1035065\157575\27111\&98-\995803\167979Y\SYNRkE~MQr.\47537\a\EOT\100066FE\SI2re&eE1\SUB\CAN3\30141eKHFCl6\185160;/k\r\SUB\t\41097@&\159450 \47257\vM\\r\SYN$\NAK\40078n\983737\1100061u?D>vA\95624CnF\989092(\71111\176451\62790RhM[\nB>*A\SUBgDpp\NUL\137828\1044414\DC3mQ\ACK\1088526e\164816r\NUL\129550\3404\62114\GS-lk\140775/\1031265>\RS\ETB\DC3\128406LFAs\176184*M\1085893\NULpt\b\100127JN4QU{Jev\EOT\STX\1108765E\t\153121\1033318I4K\\\163474L )RYPBk ~\v\RSn\1013852Xxv6#\1111946|\1067819\f\134655\987741\ESCY\31029z\NUL\ac%i< \n,\128826l\RS\1004102\119256\94776\986312P\1051689V-p\twc\CANV\185920;4\41787\1020199.\b_\1035216)\DC4K\EM,IX\DC13\NUL\1035747\&9h\US:\SYN\ETB81\160334\&1\36963\SI\RSC`\141966\fn\f\100236(\164834\180065-\SI\DC1$g\1046824x\DC1\99084~\181210ADm\NUL\1033535\40647z\999919Q[\STX\v\188766)q\DC14\134546j\DC1$\1038869\178209\1020722\ACKi\995076\&6by\986338E\DEL5\31674\1053862=I\ETB\FSD@C0`\n\1108426JR\97512j\STX\1011610\132328q9\12587\1110037%\ENQ\DC1)\SI \133259S\EOT\an?L\17808\ENQi#6:\39370\984528$VZ\ETB.{m\1105413\ETB\1096254\n\1029048yP?CQN\58229\DC2c\1016719\28430\37793\1021922\1037171b@<\FS0T\SUBs\NUL:\EOT+\NAKe\EMv{u\35899aS5ztr\1095275T\1076768\1067480\1055258\t\138199p\24191!\63947\57751\184259W'/t'\998026Zf?Kaa cX[\DLE@K\rP\DLE\SI6F.\\\1105071\EM$\ETB\185348\995728\1111114\1056306{\CAN\131737\SOF\"+J3g\14443W|\1025079gsE\b\RS\146118\1044328eZJ\rN3\v\r\\\RSTp?\1047550l?|\1052685H\STXI\1041763f\119524>)\150862\1004663v\997187\DC4\62145E\r\NUL:\DELi<\ETB\NAK\1034255\SOHyq\189759\39702\99276\1069813`\SOHt\DC2\1027302\1081467 ^I8\DC4\SUB\t\47100>\DLE\1007609-\154421\US;&\996462jK\\%Bg\170965:\a%\1030923QF*0\173510\1106863HW\52749\ETXs\1026228aAS\1916NbLWp\1051310^{T\29700\179151YL+~\DC2Zv,}\1111746\167987\59316\t\38658v\60679\176523\ETXM\52344,\DC3\1111272~\RS?_y_\DC4u\a\30652\SIOiiFo\1051777\DLEO\DC3{\40043o\1047361\ETB\27903\DC37S'\ENQ;|\1046730Eq!\1073488\160026\1006141It\175865Z\1065806\DC1h#\DEL\138456\7911'\ACK\169193ei\STXs\1104017\1064860sf-\57677\1006904\992638\RS8\ACK\SOH\CAN\146274GJ\141048\GSHp\61180\GS\EOT\186295z\EOT\tp\STX\FS\SUB\96038:)\1029325\42428\SYN\SIg$\139209M\1014224\DC4\ACK8^\NAKt\9753e\1093154\1043578\1079420[\NAKi\66187Q\48349\&3IV3\171979\v\DC3\SUBxDHJ}MT\r\SO\179759\145196\f**Z\64475\1056846]7R\ETB2Ez\DC2$\176439\1000728BM'9\ETXEa\SI\1065849I\1098032\98480" } @@ -174,7 +174,7 @@ testObject_PasswordChange_user_13 = PasswordChange { cpOldPassword = Nothing, cpNewPassword = - PlainTextPassword + plainTextPassword8Unsafe "\tY6b\1071064\CANJ0\RS\DC3\fg]\8556^\DC2\1029089S<\ETXn\DLE\161054\NUL\1009413\"\NUL\135103\SIU\1028385K;la\SO\132923`\50089$0#\19913\f~\RS\1050195kyBx\a\ENQ<\DELbY%\1106346{\144787\ENQ\1006226\SUB\n\1111798=\1082031\nl\1027190\49972\t:Z\\\45927\&7,\ETB\\\1043520\1040129m^\ENQ+\ETXepC\CANVO2:!\155826\n\CAN6g}\1100418q6\1056075\&6<\171664\SOH%yP\175359\STX\ACKo\179550g\1071640p\1006475T\1018644\ENQ\SO0'F#^IVd\n\157140\141227A,@\1053337C-\181395g``\166195\NUL\7801\1049487\138364\&5}q\50268\SYN\1089481\134438u(P\33463\SOP\47384e\n\DC1\164033\&6\1083698\SYNM\168121\&7\1027817\DC49\1039185O9 ,Op\983226UR\DC3\NUL\48061\1049901>(\1025638\EM_Nn8\FSb\DEL\92741r[p\1113723za1\DC2w\DC1\24935\SUB\169669\FS_\CAN\EOTN6Vyi&)\1012450^\135732\EOT\CAN\41126BuJ\DC2`\78370)qq^@$*\SYN\119136P\v\7875\ACKg\134713Mg}\ESC\EM\993564\1036198w\983924-e\31379p&\SOd\1022808\74004a\15280\1040139\1056286\RS\143232\1056072'E\181014\98120\&9\DC4\DC4A$\180660h/A`\DC1l]3Qv\14807MR3W\FSsn] a\NUL:3`\95284{`\32597\n\US\DC2.\172218:?y`\DC4\1085202_%S\155378:\NUL\171483\EMk\"\fWYu8-jr)\184?D\12340c\1107469\1096889\1089369^x\SOH{b\DEL3Sl:&0xgT321\180495FU\1068409N\1113930P*L\145663\64596i:\48860\SYN\164807\&0#\ACK\48791\&0v\1049613n\SO\159015P:" } @@ -183,7 +183,7 @@ testObject_PasswordChange_user_14 = PasswordChange { cpOldPassword = Nothing, cpNewPassword = - PlainTextPassword + plainTextPassword8Unsafe "6go<\1060200f\41213Y\1084615*KE\1038629])9\1028527\1090910K\2404\1065550\&1\99810>qb2`\NAK7*al\v3I*\156801\SO\1090154\f}\DLE\139358~\129615@jjd\SOHO\SUB\SOH\94999lv\16578B\b3\1066265?Ih1bv\v\SUBz\SOH\FS\128520\&0.\ENQb\50990\FSR<\1111211\EOT\DC1l\"E\125002X6\NAKS\1011858?\ESC\EOTEK\210h\54053\16688\f6\917624\59462>\v_Zd n<\DC2k\1086856s\1069883~\SOH\1011269\CANr\DLEG\998802\NUL`.)lj\DLERr/\149432\\\176664\999860\187741\59007\96806kI\1040467\&9\\.!\STXpj&X?=r\1072676A5\169615\18716\NULex`\SOH{ \121420\ETX\999279^\98959D\DEL\1051244\163196\132146m\58414\35040\&61JU\NULtn@pCF3\fM\155170ZrHM\1024580i\136496zhn\1010172\983207\ENQ\ESC\v\US\ETX\1078490\1027708\DC4lU\DEL\GS\10612[\DC1B\EOT Y\162831&cnLev\20431awe\n\175441+O\69646N\1039476\986854\59235=x[7\"B\RS\DC3\DC1\bQy\DLENq~\1100372G*\1040946C\191033\DELHo[\96055\&9'\1018134\&6\186449e#\ETBK\49381W\SO\1108069&kH7MQ\ETX\EM,v2\SOHN/\1044045\tO\169061\SO\1010256b\185510\1081515\148501q\1037709\1091186ww}=$D\ETXw\DLEb\1069094\EM~\142428T\ESC\CAN\74821Y~{\f{p\138353*w\1062006Juo\n0\150906sYXHT\USK\a\1009732\ENQ\n%q iF\95870\ESC\GS.c\GS\1014409\1066933\USW\EM%x\1003810\NAK>\DC3\1058760" } @@ -192,11 +192,11 @@ testObject_PasswordChange_user_15 = PasswordChange { cpOldPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "n\DLE\SOH1\ESC\1030226%\1069394\RS\182899Guy\1039539+\1113955\1023913@\NAK\5561U-dZ'\fB\1055523\30303\SI\US\CAN;V\SYN=\FSz\"\1085023#`\USt]KYs{\v\45407\"\8592\1064953\1006367PP\n\t\31925\1041417\44390\&5u\60622O\29903\SOy\SOH\1051143\184117j2\60717\1030594\14253K\100794\SUB]A\DEL#VL\RS[\\\1044640F\b$UXg5](A\f#b%\1086075\NUL\1041235?\45258\1073954\SOf\1095011\GS\140034b\1052232\&1\998007\996181>\49135rnE \ETB\f\f\FSM\984153DQ\GS" ), cpNewPassword = - PlainTextPassword + plainTextPassword8Unsafe "\137821\"AZ~;\1071515sK>\1003094\32681rdU\SYNZ\FS\1013170|~\68860{W=X9*\1094479^]h\v\1037179\SI\1105700\ETXy\b\121173\EOTB\RS\SUB\ETB\1087347$\1086752\&4V\DLE*,JI\EM3d\169902\&8`0&\182876r\61161*T\ETX\151630_\"\SUBT\EOT\SYNyw\43410\100742\nP\1111807\vJ\GS;'\a\1019800b}\USu\15085\b\SOH\DC1>f\ESC\185821\SYN\ESC\29398\&6B\FS?*HjHc\1072255\1058427}\1624h\n:\154398\"\1051991\1099837\&9d\128882AXf.Y\139982\&2>3\985533~\DEL\RSaL\DC2!\n}\DEL\DLE\aV\b5\1004615w\44033\ENQ\STX\986416oe\47326\&9\"\1012652\EM\\Z\31242\\\1054641\SOu\1042537\CAN-\GS`7&aJP\1066356\999545_Di1\ap \SUB\STXv2;\170127Fmg\SOHX\1102996;d%\ETXx\99896\EMOr\EOT\DC2\162508\GSvo\1035769\ETX\51961\&8M\NUL~bI\1096210L{p\aq\1026887W{PVOSq\132165\96511!n\t\16523U\t\ESC\1014032!\EM-IFP\1096087\97677G\1056015+l\a\EM.\1051294\f\1031336H\1049728\ESC$f\CAN\NUL*\bnr\1049928\1075881(\189737^\DC3\98799B^\170344[F\999872\ETX\NAKJ9;y\ACK\SYN\66458!51w\64275?Y\EM\DC4-1(x1?(:\SO\181899P\59702_\27711\163618Vx\ETXuN\v\1055926r\CAN\FS\SUB\SIu{\1026849\136958{ \CAN. \1004662\1081463\ENQ\SOHD\DEL@\179223J\ACK:\167491Wy\14989\147498j0(\ENQ\1102373\1014240\&7\SOHTI\RS.\118990#\"\162391/tg ,\1019276\1069245\&1m\186668i4\67826Di:\SOU3M\144745\1112930\1006102\&7A1B\47962\159987\ESC[O}\1028140\1033214\1061595\1000273G?~\SO\1105814\n\23793\CAN\132894K\1109537\157688\&4\\C'\171760t\1092105\1069028^\154207\NUL&aW9OlY%1\t\163491bT\n\133769_DO\70287\&7\EM(z_B\14519\153806Fg7\SYN\EME\1096879' \1105838\ESC2\fm(WW\1091836we?\1088332\26513\CAN\155517LZJ\NUL\DEL\FSYIk}\120430\EOT\4637+kZ\SI\156899x\SUB_\DC4%\177759\1057446VC\1097314\1074153\1072386Oqn\a\RS\1056654\"\18164%8a\28468\132645Tb\"C3\1103957vG\1089945LF#{\96210\998246\160936i\STX :\163339\61888i|\DC1\1011444T\t2\RS2?IHPdw@fhLKXq\61905\1046908qz\1038449z(\189299,\b\SOV\\8C\DC4\5575\&1>\SO\ETX!\131673&O%}XKML\43288j6\NAK\1080490\SO9t9Ku\141219\154727\RS:\1022834df\ETB\996821s\16300\US\173093\986989\ETBL2\"\64028\1047440\ta\bZ\185810,u\1054582\1022464\991444\n\SI\1090918\ESC.B\1024218B$\SOH\SOHv\23330yD\1082294UZ\996426Zy\1031823oX\EOT\SOH\174801'\a\125038ub" } @@ -204,9 +204,9 @@ testObject_PasswordChange_user_16 :: PasswordChange testObject_PasswordChange_user_16 = PasswordChange { cpOldPassword = - Just (PlainTextPassword "S+OT\38751b\DLE/B[\100483\&3\47760\GS\180067O#o\25466\&5T,8M~\GST#\987895U{y"), + Just (plainTextPassword6Unsafe "S+OT\38751b\DLE/B[\100483\&3\47760\GS\180067O#o\25466\&5T,8M~\GST#\987895U{y"), cpNewPassword = - PlainTextPassword + plainTextPassword8Unsafe "5\1103104\NAK\1014216?M\ETBj`-\30597\181188\1026387Z\1094596[\1092626\te=\991832E\DC1'RlS\DLEZJ&L\1107431~G\CAN$\vd6 5\US\1006596\ENQd\r\b\SO\1100302\1110521i)jc@S\156632\1002333\v7\24501tU\a\1049077\\hD\1110213\v\DEL;P0\SYN\\q:\\I\990426Ty\1097835wk\154857z\DELC\36957\GS\3138]Z\16454\SO\US)\133053g\DLE\DLE\GS\ETB|\44640-\STX~\1024260\a\1000452{\ETBK\DC3 ~.Z3\SUBC\986330f(_/\1110859\1055634\1003279\&7\183j-\171356zX\STX+;zT@?\amp\SUB!\36089h\r\992554\SO\SIt?\b\54803/ y\NUL\95035\1077028%\1099069\NUL\1063994\DC2\DC2!|!\DEL\t;D\ESCD\1041733OT\1061393UJWf\1113505\178024\ESC\154767\1050223\FSX\1026016\1020780\CAN|ix\1091727jZ\187257b\NAK\SO\1030980r\DC1)\1053891:\163447\45030\&9<{e\1079093\30596L\NUL\STX\1019960;~\985116}\1052410?+&\NULz\144674\1086689\&7\1030068%x\FS\1036306~\120570\RS\US*\ETXp\1034462\&1\149891\13986\1055542\STX@7yY+\ETX\NUL\1062210$J\1067009T:\EOTzl,!\SUB\DC1%O\DC3\SOHX\FS]\1013399$\152121\1104444\\\139341PX40\CAN\v_,yU^R%\DC3e+-g\172222\SI\DEL%f+h&W\ESC9,Jg,'x|\51952/{Y\r\NAK\1057765\DC2[\1038364\SI\28850Nl\46666\1885\NAK\NAK'\DC2/H<\180011]\ACK\1090504@9\127306Y\150151(\US8\53321\993078c\n'8]\SO\186951q7RH5L\1028090\165@\8885\30083NB2x-\1014943\985470{7o\94409)\1031807#\ETX\42922rid_^Wy7\1029256i\1062709\SYN\99669\n=\21963\&5\8639\1035935\1067300\53855\NUL\vO<\175839\&6\67816\ESC\ETB\EMJpG\ETX}\nM\177929\96385\ENQ\NUL[\1007534\US\1085889\"bl6\\`6\EOT\RS\",6?>Jk\1044669\160533\993117!?4\FS>,\DC1%\61901?y_\ETX\1016387\v\FS\DLEUQ\27172\187044\73101\24011\SOH\169041\NAK\1044569aK\r)f\SUB~N\1020859\DC3k\1012707$B\DEL\SO\ETB\ACK\EM\73084\58832~tVD\RS\SI\n\RS\1037043G<\52368\1007888o\ESCftC\186158\n\36317\b,~\ESCM!\ESC\174873\134091`\1046265\998677b}k\67343\1077779`]1^-\NULO\1013355r\24494\149416\36343`\127285\ACKW\1097424\996658\&3.tS=\983895\ACKs6p[\989667\SOb\180485\1076744W\CANdO\128541da\1063827=\1113561n;\180045\&8Wn=\SI)\1025924%\EOTj\1043094\NUL~D#W%\NAK\b \64862\DC2jr\27380tb\GS\1014983r\bD\ACK\175197oH\4243lJ\51936\1017192\59111\1024329L<=\v\78854*\54478 97q\1013840nS'{->/t'\1065169Xq\917836tmel\1025953\1010549\1013101\SO\DC4\"$\US\SUB\1098531\18016\r\DC3\140813\95239s\28689omb\SOH\1102241P_&\67318X\f*lfw~n!\SYN\ACK\a\60339\1012508U\1104365Y-d\126581\1068676\NAK5\DC1\SYNO\1060779z:\RS)\188550\NAK\1026997\59211\5670n\CANh\1072150F\9559\a\133215\165806\NAK*C/\44946.)\SYN\aP\1107161\1043226\DC4\1087020\515\67972\DLEL\n\180263y5a\146153\54746Iy\11497a(\SIv\SO!GW#g4\EOTb\SI$(\ACK\niKxu\DLEQ>\1038539wGc_NKl\r\13222x\83063z\DC37\RS\1096948\\\NULB\vC\141810\GS\169437C?&q\1009432)+PhcHd\186025\DLEA/F\1035548Y\47461\14070J\1012685jIQ>y\2014\1058904N\98611y\SO~\26014@e\1061608&x\189240\1080205\"Yh%g\SYN$\1069145\1046629|\EOTT\EMP\1011180\1084918v\RS-.e8\SI" } @@ -215,7 +215,7 @@ testObject_PasswordChange_user_17 = PasswordChange { cpOldPassword = Nothing, cpNewPassword = - PlainTextPassword + plainTextPassword8Unsafe "\1046443X2cZf\tI\DLE.3\27153\41641\987805\SO\167150\31997\157768U\23766\159716\DC2\993933xy\1103378DB\1095912\USP!\8776\&3\46231\f\14600.1\1020378\1043279ji\135553C\95086\16967\37206\19099\US\NUL~BYB\RSUYc\ETX\1091112e\127528\187472\4411\"cN&\t.9\1098365?\GS^\v\SOH\b\CAN\41627&\13579\1108825a\1014432)z\62357\&6Z\179494\1092724\ESCX\SI\13823xJ\SYN\1428\EM-\DC1I\nB=\1040975n!l\131479~U\1069398;\113684X\187497\59277\EOT\159297\1023481uY\40199z\1054394D\1020153\fFbZtt\CAN:\CANYQ\SOHh\1006361W\1110330\DLE\168743!1}k\\\1055615z+\NAK\1106543\SOH\1094136%\17474?v\1108035h\fN\f\DC2\NUL\SUB\189591\996341P\GSbP\DEL\1107736>ie\1100530\7924i\168174-\30280]4i-\STX/\GSA\b&\v\1043901<\1102709\1106671M\\\991694-pG\FS\169333\DLEHEJO\a|\t(\9209D=x<\ETB aV\1012721O\999045n|mdg\1043448@\1110847f\a\1025181W\190988\19816\DLEh\166909\1092096\ETB~\10652K~\1072426\ESC|\rdi\GS\64637\94773\1081217;\1026647\&8e^\142140\DELT/D\US\NAK\983847hTTe|N8\1077575\&1\1092491tJR\a\155288iJ2\998006}\36187\28713\25201\SO\1109108\&0\2753!y\SOH1W\USzX\SOH0\991532s\119987h\78486\135733#\1074355\138222SR\988575,V\180455v\NAK\164938\&1g\SUB\ESC\97713\1081062\STXQ):\US/E'\131476\DLEz!\SO@\1020670Hy5*R\1010303\SUB\990422\1044281\1014588\1063943\178348\1062043`\49558\DC2.M\1113770W\171312\ACK\1024710Imf#dHF\ETBc\194659\DC3QcG\1070916B\NULW<\RSC\1059704\988425\1022019L7" } @@ -224,7 +224,7 @@ testObject_PasswordChange_user_18 = PasswordChange { cpOldPassword = Nothing, cpNewPassword = - PlainTextPassword + plainTextPassword8Unsafe "$\1043357izIh\65323E\152268b\fi\165052v=:\t9\1029608\r\10484!\1051779:\1003340&/#\1091275G\188407$W\990383>\EMDo`F\nY\EML\EOT\t\NUL5\996488bC8\5233Bq\1018037$p\NUL\v\9478R^\SYNGF&\1012032+]\156711]\22754\38792;:\131701\155917w\1065591\NAK\DC4\SYN\1060773\1015476gi\SI\"\vq\6329vV\1040593\DLEYya\1102677};,K3\DLEn\ETX\ACK\DC2+\184693\142191\SO^q\DC34+Iby-\ENQ\1053606\162697_" } @@ -233,11 +233,11 @@ testObject_PasswordChange_user_19 = PasswordChange { cpOldPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "k\ENQ\SIW\142801|YQ\999097H)\EMa\35968gXC|&\fE`\176817UQ\1096875\GS\1042874\ACKj\94562\142093\ENQc\t\1015620\SYN/8\SOHL\986768\&6\132434\1071731\34028\SOHy& \ETB\52652\SIf\1005119\&5\t\1060616K6A\a FxP\26949i\35802rc\18038\186543\172362\151462H\149276h[GU\nuX\SI%~I\184399Sv\r\DC494\DC3\SOH\989634E~q\DC2\990048\120529\tR\SI1$\NAK\ETX1\165481\1009573#\nD{\1034729@\1045950q\1036461J\97887\au\SUBB#4\EOT\8381\1087000\161668g\1011547q6(=\SUB\58393\n\13236\58038g%\SO\1066841l\1003446\1011686\997871\153172\NAK\f\CAN~\1051732qs\155291I0|\62022\SUB\161505\1084819\\Dq\SUB{z=\CANKL\53422\GS\DC4\1095233G7ewkJ1\35446J8 O\152777\96173V(\n\SOHuT\184493\142630\&4-\988150\&0\v#\1008772$qO-\SOH/T1\NUL@\53323\1012898\n2s8Bfh\"{vy\EOTG\28934\ETB\DC2g\NAKx\40967$\1111313:\1096564z\984205\r\1113615\50569\1016459\1089112z\1059587\62507U\992158ksD\DC2W,%\STX}\SYNY\1063541\EM\148916\1026506\SYNu\1068118a\DLExoH\b\96516ro^\ESC|\14524\137137\174774\&2\1015701!ReL.)\GS\995824a\134494\111281\38182\ETX\1055512\DLE\53907\DLE5?\DC3\988857Y:\1077940\t)\96370\48426\147806*\158714\1042527`\STX\NAK\FS\GSg\t\1084955*fM\994607\1029549,\ESCTL\STX\NUL\986074\1096953:\a_9i\524\168231\986631Wxh%\1104374`t\1062137i\139608mD\30436\ESC\18940\RSzJ\1014566" ), cpNewPassword = - PlainTextPassword + plainTextPassword8Unsafe "L`\EOT,Xp\US\SUB\DC39\SOH\986402+\ACKQ\1011739\163475\SYN(\126117S8c\EOT\SOH=d\152742\FSL\34501\EOT\US]\94933~6\DLE:\1038349\46131\RS\DELU}dYj\vm\DC33\ESC\EOT\ESC\STX\SOho\n[P)\EOT\"r\148842s\132918\&6\1013939\1054104X%g^\1111091MDA\GS=\131957I8\r\1059039\DC4-\1093354\95894\992259\155020\".\146604~-\24057]\bBLv\ACK-u5\1099612^st\26172;\SUBq\FS\ESCd\998793\&3\GSII\STXS\177535\DC20\SUBHy\1108265\18293>\DC1e4';\ESCv\f\SYNxF\RSWD\40069\NUL\15936WB\FS\145512/v\1094497\&9\SUB[a\1031802\t\n\187075y%\1065833\&5B,hyc\"!b#h\1092617XC\GS7\995391mZ\NULECj:O:\v/J\SO\1102347\996658\&9\EMs\DC4\a\1059269d>HEz\FS\171554/n\NULeC\1004734\CAN\65713\&2\181341\STX\ACK\1013277v\1000956\94105\986760E`xtrZWt7\164746wMA9r<\1021337\15097Ovo{\1112295\v#f\1040937\991008\SOH\63011j\FSb\r\1011414\v\FS9e\136229?\1019925q\1021008\f\172280/X\24799\STX\ENQl\FS\v\74972\131088RC>Y\ENQ\1073582\&2v\GS\ETB\US-\\,\1041777e\nf\1021970\GSA\DC1\DC4y\1007481\1102343q8\SYN#\NAK\984437\43846j.\n6Is\SUB\1049642\t\1020034tL\1049999\DELT{\173861\1059180Sz\68055\988553\EM\US[\DLE\48766\r<6CnyQ\DLE\RS146\1059541J\DC4\1059543ceb\NULr6(P\917894(\1072768ic\34855/\ENQ\50857\18315\&7\DC2^b\CAN\1000777\f\US#\15234r8-\154704u\r\1016712\SYN\NAKH\SO\985948\27600\1011459\"'\46452\ESC'=\SOH\19188b_\DC1\186563\SUB\174895x`\a\1041293\140522c\EM|\984810\aA\rQV?\1058487\fZ\f\ESC\SI0D\SOHQI\ETX\990028prt\163629\94675\36885\171880\1096809\&9\46899u\EM\1102387\n\13498\DC3}h\1032138o\DLE\1063962tFT\1095317%Az\1086440\&3\US\SOH\EM\38682'\DC3^1\14526(>E\DLE(\n\1066401z[Wg\1100054ad\1007846Mnv\8290\1091875|e\190345\\g\DC4\51159jIsn\DC2\16061\178290\&0\DLE>Lr*Q:\ETX\"\183845\DLE\98183\STXq\DEL" } @@ -246,10 +246,10 @@ testObject_PasswordChange_user_20 = PasswordChange { cpOldPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "{dNa\GSEIDDNi\"&P\"Dx~\96634s \NAK&\ESC\SOHe\917580,/}}@\1024844G\USRi\177540\vG\EOT\1068093\"dIcX\128456?\53433h4\RS8}E\b\fAI+\138835\DLEN\ETBg'l\DC4\DEL1lg}\1002968\GS9u\t=\186263)\1048038A)\ETBBD\ENQ\1741%\CAN3\b+,\151430G)0%\74936\78333t8\1105056\CAN\988091oU\DC2N\NUL\DC1U~\1100670[\138598\1110439\&6##\151597<\a\SYN\986482V6\vb>\NULh\NAKq\f\176602<5dHa\tg\DEL\24672\66025\&2=tZ\1050161L9M\a2k\1001329\987951vOkA\r)\r\60697O \63131lNli\34835\\\"b`G\52957\1039861\161828n\DLEP\1077887i0k\1015841w\1040786?\\\ENQg\1005909\RS[Z\SOHN\SOH\CAN\186595:\FS\185811\40960_kBD\"C\DLEB4]w\DEL/JF\NUL?L9V\\9\1096654W\1104044'\FS={e\153126>\1098415\139415D\1112130A\a7G\ETXb\983698Crt*Y\nhD\150279\&5\151537)F\NUL\ETB*\1035725yCu\ETX\SUB%\SIbZ; G\1079499)\SO\1012440_\NAK\DC3,~\175703\SO\153562d\1101051\1084728\&4\1018181R\1059397\19127\1099372\1004409^\161681\32886\&1\DC3\USn\1102891*!\FS|\r=\166562[ql\189334S\NAKr\ESC'\SUBE(\SYN\f{\1112073 |\b\50511\42582\155138\1009867E\NAK\139848\&4\151681\t\68617X\1000541\EOT\1104748.Z\1085819\177246\176778\DC4t\CAN\DC3\23081\&5HV\ESC:$e,L\STX\992003\&7R\1012763.wq\62951\24985:\60845\SOH\SOH\a\67714\8047\&0*.\1022795\1087787\120217P\r\b\167713\1096692\&5\147092\121232\149850\DC1\bsc-\1082366i\DC1\2721\183884\154420A\NAK$\190574jNR\917908\SI\120778\16684\989256;\5681\1057323\SYNRd\STXI;\EM\aK\20933\59636,\EM%\1073632\ENQ\1089709J\1061355nR$Spf\1093436Lsp\1046367[\\\1105079\97069y\t\SYNbC7}|\DLE\SUBg5@]2\1017800S4E\be[\1054254\&6\RS4\146792z\DC2d\nm\83369/JqK\SOHQf\1081923\1079670\&0\95005\SOHHa\1014928&8\111343\61186)~m\101024\RS\vG\SYNz(p\EOT\1052203V\f4\"^+?A|\1037820\n\340r\USF&\CANt\1037756r\tP\SYNDW\DLE2\DC1E|PgQ/\1055897\1034173P\rNH\SYNS\30936\1050463\29463" ), cpNewPassword = - PlainTextPassword + plainTextPassword8Unsafe "\tNYm7k;\985171|w~ue]St\52529[\GS\983717e\DC21\EM\SI\"C\1059834\&0\1003638!\995247|xiw\1027219~YT\57860+\ESC'\185609\&6\1010421(;\ETX!\b\1071987&Y9tW\984137\72988\GS\ACK\1083519\1086906\1107857&a,\NAK\31149\1088114y/d\1080408\SUB\169799\150046wOS\atp\1000950B\181672\ETBi\DC2\1090827\1080180\DELek=r\138679\60557\ESCrf\3126UkFh\ENQ9YA\t]\NULUJS@1?o_-P\ETBxW\171817\139732\48291<\1060487\133433&\DC4\SO6na\1000867!Z}'H\1052135w\r0W*\24217J\SYNIwk\9238AZ\1023004\30337\1013798w\1015506\ETX>\1080073S\158446\1061588o\190641\175249\1070034J\EOTu\STX:(\1066396\172284\1054181@\1030039\n\DC3xMJ\30746\147879Oxj@Np\1066698\1000349\1087808x\SI\ACK\US\988847T\v_w,)w7j\ACK\1046770\1038846\US'h\31697\&4\NAK\138144V\37643g\f\1099746\&3\129560\ETXR\SIPdc{a\STX\191154\DLE\ETX((\CANf\EOT\f\188879e~[+\RSg=g#&MQ\DC2%4\r\r\ETX\65235<\170329#\1109142\&5\36874\USv\bpt\DC3'\EMF\"2\1113106\SUBe\1087311$\1010352 \1068376bK>m>\f.\1052106m\64101MvQ\1065915Q\70336\177129)/\1056483<\CANy\995545J!\DEL#1\v\aq\DC2\1102215\DLE\CAN\1089020D\ACK7W\EMw\"\151987\&3\STXv\21304\126082\ETXxW\189371\1054427<^~\993642\r:WGlhl-!|W.\3598;n\1077840`<\CAN\1109050;NJi\DC3\53248\t ]\DLEH\100145_Z\996436\24307\"\185147\1002533\71437\24999a\DEL\US\1084155\132179\&4U\1017349v\1098626S\166457S.\36067i(\ENQB|VD\43028gW\"->N4\153954R\190825\992013\DC1\NAK\59376\20565%\160113[`\120495@B\168437qjKW\DLEm z\1034188\167428j\1029865P%\SI\98769._\r\DC1(N\990561\DC3\b\DC1\1072625e\41522'olW\ACK>\SYNp\988282H\RSe{\"RN\51331\ETB\DC2\">\1007951Q\DLEYoj+~\FSSMU\"ubD\142953KtW\FST\99243\20978\SOHQm:\RS8)g\1040404\ayZ\156789\1022349E\99162j n83Hf\163774\DC3\47323/2C\DC4\FS]A8-\1067911vp`XJ#\rA[\1063999\NAK6r\119313\DELA\DC3\DC3\65757\1003687p/\1081952\twP\1071823\RSCq[\DC4\62257(\1002708P]OL\191214\NUL\SI?\CAN\FS\DEL\62658\1067853O?*\133393\"~\95514\NAK5\DC23\993032\1062731GC \a:T\1086654|$r\1024226Q*US\119666\7973\990723\1092776\1012647\&2\SIXp\DC1l,{\53831$\1091822\SYNw\RS\1014066p\159782$6\1003029\17252\SYN\178493\&7\1094964]\141621\SIi\1073342%\n\SO9i\DC1\ETBI#\ETX\ACKz\"LJ+\f\EOTU\f!nSGq\1041642\1079338\b\n\SYN\58961\1100402\1107153vkoE\\>L\1071747\992957\&2\14662\61032V\USfCJJ\1041994\f\183187\DC1\141258\37968S=\1107082v\994620/jdg\1002901U\1025416s\tO\ESCD\DC2\"\1059656;\162790`C#\DC2\1073802^j^q\133762;`\1044114\1037819\DLE\986390\&0Q\1039253\188705\136022\CAN\1097897\&99\58156\v\132926\1080381\"\1015895\1101268\97449JW\DC3n\1048086\SUB'\ETB!\a;\CANF\1008408:\SI\CAND\61480Nhu\ETBvWC]i\1023609]>mM\96616\989899ISK+\97925\SOHm\"h8\30835\STX\DC43F/A\142221\1002286\98732M/\44462\1041696`\ENQ\1053777\22262k\FS+\t\1010757\DC1,6\a\68820D\1045784.:$'P\20749\1018853\1083057\166962w6V\"I~\f`\9746N2\DC3\SO\DC1S\1111933/\55133ZfjtU\GS\1022578u%\ETB[k\SYN\1038646s+G\EMh6\ETBk\1042066\STX\NAK\SOHi\1024430P\994456\DC1\999049O\a\r;;\72866\988084\DC1y\DC2\t`\ETX{n\CAN#fb\156098z\1089529@\148590-\134697\DLE_[)' r\SOh\ESC\1005694\&4\SIi\t\173183\1062912\SIEe\113729y\SOH\FS\26106U\DLEY\ETB[Wu\148140\1043600\a\1108631d\28497\b\1066901\\&F2F=L\rlk\NAK\1060365x\44894\n\1050464\1017030s\t\992979]T\\\1016800\1103758\177517z=^\ACK2)Y]E\NUL@\1024775_\1009598\"]\1027129\1018765\153761o0>Qd\ACK`GD\NUL\1013350p\51546JF\aw\FSCo\165594\DC2\ENQ\bq\\;\SUB\58807\986762to/Q/3\10426F\78530j\147563}yi_/\bj*y\1010612&\FS#KG\1040949\t{\USn\1009397\994198|\th;CJ|S\158628D\1100265rh\1043492\DC3\RS5VV\1017190ux9\SOu\141038?\1104936_\tx\DEL>4\994644\17535-d:y`*T7\166631_7\GS\SYN\36847bkb\EM\STX\1051731\DC4$\1022793i\NUL\178323\167679xUT\1003494a\ETX\134094V^\GSvz?;E\153708\67127\63995\n8X\183538\997398\CANot\r\ETB\NULV9>\126574`\58999c\SUB\66417W\161422\51315\120691rh\DC3&\139906\1102627RC\v&=2\b\1086617\ETX\994487)\8703\&7Uj\1029880A,ckL\CAN\155729o27\150661\ACKL\rucW\DC3\189366Bw_=\b" } @@ -52,7 +52,7 @@ testObject_ProviderLogin_provider_3 = ProviderLogin { providerLoginEmail = Email {emailLocal = "h)\49363Z\1063824\1071314", emailDomain = "\186791\EM\19978\995909f\b"}, providerLoginPassword = - PlainTextPassword + plainTextPassword6Unsafe "K\3841|\1085726HU\n\\Qik\EM\US\153712xG\165429\&1g\37919\&9\1104489v4\1082951\b\ENQ\1067666\1085466\&2\CAN\1081359|\1008692\NAK&\SI;2\DC1\b\1002383A\a=u\SI\40099\tz!pwH1\DC4\SO.lexJ\RS\1057638\&3\EM&s\177278\DEL\1004232\SON\1093350\v\1051086\49256\1091462#n\RSY\1087974\DLE#%ObL=s\59640(\f\EM\985401\1080301y@i\CAN/o\nof}Z\121432V\NULDqD<\EOT\987137\92240 ;\\\GSJ)\ESC\170276Sth\STX^'\993179\994691\171357/ct9\"\ENQ$s*\1112139\983635T\1000238\b\CAN\1053269\1032292\&9Drm\74016$3\78461j\37449y*R@\1045490\1075417mGM\1024517^~\ETX\SO*\"2#\1095216S\FS\CAN|Y]\SOWy&N\159632Dw\f\DC3Y\54016%^+\1070488s\1082203\SOe\1078681\SYN\1038724\71339\51097o\r\184885z\"jn2\f'(B\SYN?s#*d\ENQ\FS\24664\n\160475\ENQ\1088778$f)n\25546}&N\t\1074142\"1\1079112u_QCU\SYNeR`\1011732N\ty\1049057\15798\DC1\1106895k;\1088300\144423t~JX=vN6\a_~C\US\1001142\SO\1089507?$b\1031649\EMA\SUB=\57375vm\n\EOT\1031498'g@\\\162341AhVW\37558\143758\131257\1048128\4146\vO)U\1042082\1030755\\Ly\39677eA4\41869\ni\994151\43752\EOT_\97713n\DC4\140878\\S%\50171\1080044e\CANu\998027\1051199'V-D\1028947X,q\DC3\DC4\169513\ENQ$\46447\64290.e2l\1061537\NAK\ETX{TW\n\52800\1329H\1049309\1059378\994850\1094923\SI<\NAKM;[d\DC4\b}y3Jt\100213%l.\fU\1043697H\STX-.#\SOH\f\151738!\CANY{\ETBd\152209[(\1040856]j\181307am!Z[w\STX'*Tv\174621PM~\1033877\&0\RS!b\128530\\P}3v\EM\63181n\1064827\&9F2gW\DEL4\139178\1022339\t\176600\147459\175596hR\ENQ%\37966z\984421\1013392?*\13832(#\EMr\DC4\br\161885WN[ N{\1095601\EMv\53960\SUB\989224\1100619\1054425\DC1[S\DC38\RS?\1056015\aA\US\1047760A\GSb\98984\1057798-\CAN(j\7084t\ENQ;}\SIHX\ETB]_,\1110377xVY\92234\EOTH\1009657i\95997\GSuu'>S\SI\SOH\28747\19442\62495\1029286\GSb\985600\161392/\EM\1073931\&7\DC1/X\DEL\DC4oZ\1003485\171281\27236|FN'\1088003\DC1\1095084wB\DLE]\48797 V\1075141\1063573\an\1099423\t1\1049162 [}C}gG\1112557#o\990395kF\ETX\bK\1066860g<\SIn\CANZ\95543$}h\US\SYN\147130\&5m\78875\1022687<\1024861\38580h\DLE\37612\983382\r#_\1081233fD\SYN`\1048444_{n~I\EOT#7\"K\v{\28291L\1076561\DLE\1028456\991117\28670]l x\US\1060025\180458U\\\ACK\137215\37941\b>\RS$Y\1105355\EOT\DC4\135173\159718\66296gE[Z\129159\1030459\SYN@\ETX\1089314u\8040\46827(.\ENQ" } @@ -61,7 +61,7 @@ testObject_ProviderLogin_provider_4 = ProviderLogin { providerLoginEmail = Email {emailLocal = "\188360%\135755\169860", emailDomain = "wY0nE\45983d,"}, providerLoginPassword = - PlainTextPassword + plainTextPassword6Unsafe "\997376`h\134851[/\DLE\SYN\1057356\190778c\DLE\144700\STX$\1049974V\DC2}\"\1056292\&1\34169K\USC\183035\ENQ\DEL'\998099\&70\RSe\1032261\DC2w_\42155\SYN" } @@ -71,7 +71,7 @@ testObject_ProviderLogin_provider_5 = { providerLoginEmail = Email {emailLocal = "\1090846\1076550\ACKf?\1064024\DC1R`", emailDomain = "\31290c?\GS\1008740bOP"}, providerLoginPassword = - PlainTextPassword + plainTextPassword6Unsafe "\DELK\71213\EMr\52582s>qsz\1054321\1103761\14642\EM<*\1034130\37787\CAN^&\128177\&9N <\1092368YR.\163469A\ACK)u3\1077954\&6]\60462\133926\58248IwBZ\STX\991401\1006288mA}2\12958y\124971\168984\1071055\1032417/Q%E\bL<\STXI\ra/\1012873q-\50596 ]U:\DEL^YR0){\f{g\171878\ACKF\US<:\159079]IC\43618\99859\97648O($h\991036\DC1\SOHe\FS\SOHxQ)\1060127\1070870\177697\1050228OZ\1055218\NAK\"\989577/\DC3x[\153967\187729\DLEV\187019\99263\t\155326E)H\acIsa\CAN4\SUB\64144\&8\70723\ai\ETXDD4w\182368\tEjf\DC3h\1057977\1058648a\2001\ACK\30165\132313\118793\SYN<,\1000370+Q2'o\EM\60960\STX]\EM%\DC23PQ&H\1008877\1050884\SYN\US\1003442YR\1029695\18252\NULEx\v\SOH\110594\154395\132048O\US>FG\EM\6072\1035840(\185650B4,\161948\1082520\ENQ\1011783HUJ\SYN\1069998*\1100665w;j7\1041915\&1TJx\SI\1044958\1099495Pn\ETX*O\NULt>a_X|_MmL\6099\DC1\984250\985977:\1094973Cu[r>\1005272vp\DC4" } @@ -79,7 +79,7 @@ testObject_ProviderLogin_provider_6 :: ProviderLogin testObject_ProviderLogin_provider_6 = ProviderLogin { providerLoginEmail = Email {emailLocal = "z_\1019380\DC2", emailDomain = "\180905VDG"}, - providerLoginPassword = PlainTextPassword "R\NAK\19239m\20399|\168697|&\DC4\54144_/\1079716\60856Te\179713" + providerLoginPassword = plainTextPassword6Unsafe "R\NAK\19239m\20399|\168697|&\DC4\54144_/\1079716\60856Te\179713" } testObject_ProviderLogin_provider_7 :: ProviderLogin @@ -87,7 +87,7 @@ testObject_ProviderLogin_provider_7 = ProviderLogin { providerLoginEmail = Email {emailLocal = "R\1107663\1101373Tk\47808\&4\DEL\fm1r", emailDomain = "`O|Q%"}, providerLoginPassword = - PlainTextPassword + plainTextPassword6Unsafe "AV$\r:\SO\1057925\twBe#aP\GSf\\D\1093131\f\"\1062474\DC4\1063531N\vW\1032165\151144; O\1095945\1039439\163822\1053248\996935\158292\180227*a\1032308w,9\11932\33469\1042359Q$\NAK\NAKpv\992954(Cl\ESC\"cg&2j\ETB\100514|\ENQv&e\48648g\58097fK'j{F\RS\174779zn~\26851a?\989074\ACK \26744Z,#\128914:/\1073971\999239/sGF\ETBi;{|\9210?np\24919_pCi;1j\1075816w\132426\1101926\RS&\1094263q$\DC2p\GSC\ENQV\r\179342g\SUBls,\166835d\SYNC \1003970<}\1090450)x>~\113696VV\1038818\ENQm\177584\RS\DC2\146064\STXykNo\1109305]\FS\n\nZ\ETXp\1093301\1040700\14783\70715oy!%m\1055994Pg\29043Mz\63458\151167\142629\ENQ\GSoiO\1079223R\FSHG(\155361\1043624<[nAlz'\EMN\aX^J-\33133\DEL\SUB`ubS2a\FS\1089953I\DLE\NAK\1066424u8rSJT\34653\983177\1103439|\b\997721V\SUB\CANK.(\126129\a\1111643\1099135y&t(\54546\139956Y@t\n]\rlJ-\65671H\SOEp_Nv<=^\37923\RS>A\RSt\NAKL\1083189\28040t\EOT\1021817|/\46641\NULs50\EOT\167880\1053339\SI1\1081864\a\1004866\RS\8114W\157166vsqz\CAN\32807m\986009\60083~j\1045359\1031943l\169109Zd\1030016\SOHlKy\NULITt\20709\184328\SOS(\34490K\45599*\NULn\28796^\188678\&0\1040248\DC3\1109095\149822\1084021\FS\\\22362f\1106493&N(*\151139\1032885\NUL~*_\NAK\1034617\1023597\&6s\1046400\41249z\NULs8!0m\USb\142489\\1lu2?>7x2^t3\54489L\1080612\30405|j[Hi\SO75&\EOT!\37099_pFu;'\7181(\169297Jk\SI;\n\93039@m\41290~\SYN\SI\38271\FS\1041438\ACK(qh%\"\SYN\ETBC\1089293C\63782\&8Ff,O\ESCks.q\38452J+W\994044\&3/\a\882Q/\127952&\EOTl\ENQ55]5\SUB<`\t\f[#\t7!YI\tei\66807\37932\&1yUS\1095848X2o\1030170\t\96997{\t\ETXOp\tf$B\STX?\NULxv\1029314P-u:CW?\1014394z\EOThO&\97914\179719\ESC\1045462z\DC2\178600\&7\SO\15990@|\171677\&5" } @@ -96,7 +96,7 @@ testObject_ProviderLogin_provider_8 = ProviderLogin { providerLoginEmail = Email {emailLocal = "\a", emailDomain = "z\1065930\13842V:\178758\DC4\136826"}, providerLoginPassword = - PlainTextPassword + plainTextPassword6Unsafe "\1032807,+\26563:\1071780%G\NAK\997900/\DLE@\1027414\25655l7\DLEw\EOTe\ETX\USV\1028504i\1102233\tJ\1023546\188083k\143090Hz%|aG\1039292\r\52139Q\US&\GS\EM42\ACKC{t\16867]g\FSdO\48840`.\184346`m[\ETX\1077921-\ENQ\16213x\ESC\1101818N\142994\DLEK8\188217[\DEL\988988\1008523k|e\NUL\DEL}\SO\991947<\SOHg\1031754G\97218K|^\1095277\RS\167966M\168754/i\1093780!#\186388}\7777\EM0\1107848\&2 B^\DC1e@,I\SUB\1060988\93989v\1010096\NUL]\"c\138108\47542\RS\SOHB\DC4;+N\1108696UI:\\Zc\1066121zm|+{W\988550I\2530MU\EM\992874\n\NULH\ESC!n\1020509q.8{\1004748\162235T>\127905\1059100_ \ESC\NAKZ\EOT9\78187\990745r\ETB|\SOH\83522'#\3536\ETX{|=P\153911VTH\b\991886\8452'\95845\SOHvSk\1042204\52955\28694K#^\20633O!'_}\1093507\1043069\DLE\STXQp}\DLEfd\128876(\a;\1003531`_\a&\110809LI\GS\"\159092\\71\a\DC2F\998197\182925L\1070548a@\SYN\r\1076739\97129h\ACKP;_Yj\4138CH~V9\ETX\DC2M\SUB\1107806\1058529%5;\SOHd]" } @@ -105,7 +105,7 @@ testObject_ProviderLogin_provider_9 = ProviderLogin { providerLoginEmail = Email {emailLocal = "?!", emailDomain = "\f\1047642v\30589\ACK\28844"}, providerLoginPassword = - PlainTextPassword + plainTextPassword6Unsafe "\191077R\tF3{7\986307Q\5004\ESC\189822h[\NULV_m8\ETX:K\r\1003166(\1088407\DEL\DC4\1066192\rKz\ETB\96897^o/&!6\SUBV\bU\8154-1n-\1022625r\ETX\35324F\a\1087954+\990349,\\Xm\ESC\789\1107982|\53584\21152\EM=\SUB\1049274fR\n\1028364cC>jhZ-\vBnq\ENQ\ACK\"\DC2L\60229\1089806\US;Q\1018560D\"Q@\1027316Qq\20765" } @@ -115,7 +115,7 @@ testObject_ProviderLogin_provider_10 = { providerLoginEmail = Email {emailLocal = "N\1079983C\1019848\987758Q\1016550?\148085X", emailDomain = "\1069665\42373l"}, providerLoginPassword = - PlainTextPassword + plainTextPassword6Unsafe "-=\32480w\ACK\990091YN\186686Mx\fz\20991\FSa\416e0ARM\167347\DLE{\8548A\r@3\16428\DC3\ETXgA\47834\&0g\147348\v\49080\1086233OQ@+\1007101kw=~Z=q\1075779\SOHq\179325\SOH\45786\1013252\1008755l\ne\1071386\1009919)Z]A\1012627_o\95076\146226\1045971Z;\18446M\132612\1112886\\\1088243b\r\50791\1020046%\39407vLCFZ]f\GS\t7\1096142uN? \DELY,hXW\16146\SO\8281%[t7R\48925S\n^\EOT\150600\191010J\1000079\tj;P\189612rw\DEL>@\SI\SO~ \48000k7\1102878{m@\1023548}D*i\t|\1007134\vF\1070537+\57561\SUB(qIOhI#\CAN\1009728\1008900\&7E\144838/\SIM_z\1028908\2400\11810\1691\NAK\n\US\SYN^)\SUBLVo\RS\50243\178287w\12126UeQi\ACK\12340\97806&\52880\DEL3S$$\179126\RS6\1077404\1067610*\131637Kkk&Ie\NUL{\CAN@}\66331L\92974\152099L-\r6U$3wC \993821\1009888yf3\1086000u\144879}}\1102943\&9\SOX\1103437t\1027564{\DC2m\1072289o\FSEzaDAZ\24534T\t1\DLE\37862\145559\NUL\75066\DC4D|t0'W\SI,l\aYI\SO\1011545\182577l,\SOH\1018137k\aB\1048822\SYNn\993483\aEjoDc\127837EhKM\DEL|\159141R\68861\t\ENQ\DC1q\SI\SYNC3\1011231\EMUy\vB\132997a\34076!8\ESC\GS\NULw\40648xEV\ENQy\DC36\1106820\18443_\184217\ETB\96220\&7f\1108082v0\38992\ENQ2*\1025120bW \1032288\DC4\140659,\18298zh\1100304rO\1027921\DC4N(\t\1041653\SO\48792,D|\1066254\998442)\GS;q8{\US3\FS)\1089425Z\STX\SOH\SUB,:htBg6Qz\1059787\1002112\1068685G\RS\DEL\41342\26517iS\172060\&8cdr\ESC;\DC2nf38\28682gl$9\ngdX\59459\NAK\1062568\1035354\1086375\144492q6~\DC1d\ESC\n\153700-5B\1038372/>M0\t\bVK \65825&\DLE<\161308\133305\&2Id\993601\&0\fKg\CANZF\164950rE\156086\1026024l$\119993\SOHf\1062528T(bH_e\"\7643w\45515\STX9o^\SYN\DEL\147213eM\ETX\188951-\181301\1111821E\999551\t\121125@(\46533m\148530\150405\986944Q\f~\1032957\&8Dv1\30512m/r\ESCYr\8210yq\SI \1084841x\96176\17805\149489\DC4\RS\157280zR\SI\1053172\&0\EOT\1034755\147571kF\43160@\1094412xqo/R\165394\DC3G\DLE\179117\10618\97627\SO@NL\1101566\DC30)uc\139710\&9\1000371\1002633\&1\DC1\f\DC4\1018005\1101074|\fN.\20166z\ENQr\SOH,;\EM\162767\136173\ETBa\145336\EMd\GSKvH\1003959\&3G(F\DC4)`\1092995O\1061374\CAN\SI\fqL\1094451\FS\tI@\SOH/k}`e~-\NAKo\120778S\1105769\3991h\92498^u>\992342\&1\143466%E\DC1o\182499us\1033582\RS\45417\1110073\SI\1051765\41403\fl\SOHt/\1027515U\190039\1045439\NUL\a\160207\RS\144945\ESCI\SOH\\\f\180467Ni\DC2=>\DC4#\121462*\DC3I\ETBO\SOh{\1085656\US\24409\FSw!MY\181626X\45991.\tf?a\17399\1051598=\SYN\1036417\1082173\&3><\1003370Iw\"\SI\CAN2'@29\DC2\50699\\\1077056\r\4318\\4w\983354%/6\1104193g\58587~.g\74148\50911\1086151 Au\ETBP\141262\170054\174447x\1094618\1080957\34978\1069200?\99187\ACKI :\STXZy$\EM#\1092580\146673jv\b\179001<\SI\EOT\159484\DC3!\1011657a\a\62678|\194723])\DC1\NUL]K\1038195\&2qo6,\100506z\37503\1108939Av\995584v\990741\v[e\CAN\DC24!)\a),@e?\32096DA\1356%%F+\1007987Oc\175553\a\r\1022239\NUL\EOTg4g*\8850AkKC\RS\SYNg>=\171781\&3\1039525\NUL\DC4\1086181p:2HH\137199\29594\DC3\148134\159816q\SIk%\188310\23312\141112\\a\120019E\30348\SI\183647Nr\SYN$&\1002603\1088350i\36041,\151865\FS7f\98027e\CANL\39708\DC2\999013>\EM\1032242N\48509\EM\DEL\1056374\&7p\1109882vP1?,c\1065156Xv\SUBN'\SOH\1000812lE\a\110963J\DLE\176674U\1017909kP\1014472`O\28714\NAKjjC\1046204 Me`06.\SOH\EMs\1108581\984997Z)\505Bw\NULzKZ\170904H\ETXF6yhu\111193\35302d+\120753y\1055023\1044971\1113599)\151175|\SO\EOT \1088176\986374,\SUB$.-7\145796a\EOT\1066588R5g\1058599rM\143676\1015936H\NULM\SYN\rT\174913Z\NAK\59354\993617\190813\US\1025870-\45910\ENQ\1013980~h\RSpV\NAKMTRyE9\ETBt\EOT\32355\184982\186747]$v\181164\182566ap\1039132; \1092510\t\"Ww\98795$\1068166y:`M\NUL/~Z\\eF6\1044984\NUL8\998267\83100\&1\NUL\12214\NULdaS\DC1\1057662f\74458E\vt\128426C\EM%Q\1066510\132734Xp*78\34762`\194697<\1078829dJ\1064424\1086026\SIb8u\147637\ESC3\166488F\1096009\b\13973\83061E\n>\52949q\vj\SUB\CAN\SO\1113037\984171Y\EOT\1078017X%\145648in\1014030w\f9\65731\1077451\1020021Ff\1059532c>\n$}\EM\25419Y\54667)\1095983\32915\1017920\SUBW+l\ab\62782\24910\DC4\145359FmR]|u\ETBkR\a7D\51919G\53611\164554@\SUB\36018\ETB/kt.\US{\1092794#^(zcVm\f\1094121'6j$[\ACK\ESC/VOQ\996687VLw\1067078#f\RSC\146914\97477@s\46339me2\ACKe\fs5g\US\144879\97542I$t\54653\ETB\SI\1073768&tPz\1010457\RS\NAK\150524\DEL&`x\1007249?:1p\61424\US\993411\RSC\157776\15121X\1051749\65224_ZZ\SOaTW\DC4\EOT\1110937$\CANp\44513|(\92589\&67\15667\&7\29934\1058652\98055zLc-Y^\1004000\f\34235\n\92226\b|\1044732\\\DC1\NUL\1068972\EM,W7`\183734\DLE\142014Jn\999320Efa\EM\NULJLwN" } @@ -153,7 +153,7 @@ testObject_ProviderLogin_provider_14 = ProviderLogin { providerLoginEmail = Email {emailLocal = "q\100180\DC1 3%Y+RO\1022392\&1", emailDomain = "4`\1034127"}, providerLoginPassword = - PlainTextPassword + plainTextPassword6Unsafe "\SUB1\1011613\1053868\RS\DEL\GSG\1105681.\1071787T\STX\SI\128952:\165225\&8\ESC1aV\"\EMG|g|7\ETXP1\1113285A{_\44503\10934bufZ?\96371\53879\186363\DC3\1016903~g0\1011005B\186373\ESCR|\SO'4\t\ETB \1093146\67705\SYN\28861\40040\1058799\1095038I\1021393\&9\110714\DC3\EOTP+\SYNz@m\16478!-u}(\ESCG\\\187881\b|3\1032903\37872e\"\36203-\t\1095705\DC2" } @@ -162,7 +162,7 @@ testObject_ProviderLogin_provider_15 = ProviderLogin { providerLoginEmail = Email {emailLocal = "\f9a\flw\NUL,", emailDomain = "i\DC2/d\158245I\ETX\\\150537t"}, providerLoginPassword = - PlainTextPassword + plainTextPassword6Unsafe "':\1006988%\52805|\986668\1030808\135993\17938\1004363\CANo+!4T\b\ACK\99223W~\1028898\151854\"u(4l4nH2\1101674A\16088-\1018022\24141\111131K3@q#\161349A\ETXz}\190078 \ETBr\59344\9426\1078379C\45259\SI|\1067233\DEL\SYNx\ENQ<\1018777\GSrj\1070063\SUB\189644\EM\ACK[\RS\137992!VP\au\FS92f\\\ESC\172837 ^g\1015290\13337t\fvS\1043307\t\ENQF\r}\ESCP\144296<\ENQ)kD\99644\NAK\1067207@P\158876-J\11318\NUL\DC2R\52639\61640x\1000441\17876\ESC{Y\fk\n\37226\58197\ab\1032034u6\29086?5\ESC`\SI\ENQ`_\158422\&2d\RS~61Sk\178580\1068936K\50191\DLE\1019284\1080388\1107195\ETX\8366\134653v\DC1+\FS\1108302c8\42659\2331V\1104718\ETX\DC2h9\17336L\48192\&7\97754\154294)u#\NAK,\DEL)vl\1014830\&7Ogx\RSS\a\173846L~'g]\67981xG\995706j^\1102897\127370ZD('\DEL\n\DLE\DC35\1112268\185079L\1096532\1075622-z\f]la\1092147=]n#a\1038542\30579\1083984\vCPCRM\1042106\146305=uh2:Z\ETX\34750E\1105620Y[N\DC3\ESC\1110568\1066309_7\FS\1059513\&7\986868\&4x\nE\67808\29850\63016S\29795\be6\b1 F\bl\v\SUB\1045323\152687\ESC\DEL\128863C\ACKvX'/4\1091841\23510\151941+\996124$;+\DC3\174286/r'\1027484\v\983594H\1106500_M\EMUU^\1077134X=\EM0\ETX\ESC\1082555DQ\DC2\14716\133147=rm5Bv*\69735\1021551\171583" } @@ -172,7 +172,7 @@ testObject_ProviderLogin_provider_16 = { providerLoginEmail = Email {emailLocal = "\1067205bp\STX.=d", emailDomain = "y\15356\DC3\161068\41681\21426\1020089Zq\128566\143938"}, providerLoginPassword = - PlainTextPassword + plainTextPassword6Unsafe "\1036973\DLEv<\94969:C\1007217\&3\SO1_!\ESCP\t\180873JW?\97294\1017846\1045977l\1091653=\65478ct}sUS\1071996\EM\EOT\SYN\2051|/HR~.\1048900/N\f\\\141892\&4\32647t=CY\179433y\DC3\1054140p}_\r\rhEa\1075791c\f3\STX^@\GS\129362@\STX[=\ENQw\DC1\1029934\DC1\101063~\1092617\96064.\1089637\ESCn\36147.\1087267]6`A]\1050790pA\t\121086\NUL+\EOTMu\157233\ENQ\SI\1043849\ETX?&\FS\GSX\1100944dh6Fp\4861\DC4i\136377\1066303\&3Z7@8\ESC{\t\EOTQ4g\145660\65499)I\65609[t\EOT9\1026387w3\SOF<\NUL0\1071913~#\20452m6\61319I\CAN\1105033\988334-\ESC:76\DC4\US/?suL\1059445)x~\ETXn\nHI@b\1101299iT\988713%{a\52797!%\95084Ke\58514y\1087193>\1008548h*\74260Rs\ENQ[!R\EM\SI\129448a4\50351b\DEL=r<\SOHAQy_u\986104\NAK\73693\DLEj9,\RS\1008176\1060001[\71236)\EM\27772O\EOT\1029715\NAK\1067872\SUBm\ETBm;~!Nqx\r<%?\CAN\STXK[KxY5M?\DC3nS;\1039064\1056961@\143038\&1lb\40219-\f\USJZ\997750Ki\td\1091304i\132307[\100579~$#\SYN\SOHg\1921\1064317g\SYN9];|\a\189928bR\CAN\54684\143272\1035894\35517AlfL08\121168\DLE\US=.0\b%yk6n\169325\ENQ[\\\NAK\1032731\45474R\189972\179614[f\1038013\&5\NUL4f\13023\176583t\1100811\155909*\r/V$\1038174\44770\&8<\66811\132407-\b2\1066405\&6%f_-\1109010\&3a\ESC_\1110891\\u3XZ9 \187801Y5\1059719(\1006889R\b\1008505f\DEL\b\1031245z}\b\bY6UOz=\5767\DEL\1043399W,\1111327\DC4\1044326\66036\SYN>\1059628\92344\CAN\1114008Z\1076807\1019237g\SUB\1084387Z\t!Q46\DC2\68818jM\ETXc\1054316EZ\DEL\a\95416rcDK\SI\STX9\11372\1079523\bs\CAN}:\1101964\52216!gp\NAKz\NAK\119927\SO\62276L\1029468\no\97894;*E\a\15680gST\bj~\1071090d\1100387V\1015961Qf\1068607L\SOi\fY\190596\&6\GS<6N\"\a]\DEL,s9\1096598\1070844M\SI\990526k\US\32548E\1088460\RSTJkEc\FS\1012905\&4\1068530QE\1012911\24946\&8\92573\985406'\147964\SYN\1087141\a>?\v\a\r{rW,\1037280d\ENQ$2FA\1056946\USwL\30127!\993861\DLE\RScf\140888\NAK)uSz\1058795O#B3;\1035768rC\STXDA5\"\ACK \bj\DC1^\ACK\146554i\EOT\1108759BM]\CAN!\1111593\189909\rA&\r;GTE\CANz\EM6tQu\136534Rn\tT[\SOH\FSQn>\24878L\92573\1104026\FS#\180044&n\1033900\998864\ENQK\NULH#\DLEnpu\1057804Bt{X\FS2je@5\1047027\DEL\1035261\1071250;\"\1100173Q\n\99865\1085440\55133\DC1U~@:\US~\38191\nRLiD\1007697q\994233{9d\128484.\54287\NAK\b]HlZ\153287\t\26577\51191\CAN{3\1029087E;F8G0g\60158\1022972&'\1023598C\1054219\DC4i@v\CAN%[_g\1000893\&5\1087835+;.DtH\2792z|\1051376\1045157\DC4\1052468Z\fS\1029176\r\128163r~r^[\170410\17975o\SOH\25519_H+;\GS\127793\a&m\984047U8f%\FS\17902\37532Z\1029865\&8},P\1080560H\SUB\b\1054328H^\134222`gRf\133942\1092609+V\DC3\FS\SOH\128429#" } @@ -190,7 +190,7 @@ testObject_ProviderLogin_provider_18 = ProviderLogin { providerLoginEmail = Email {emailLocal = "1+dHm*\71181b", emailDomain = ""}, providerLoginPassword = - PlainTextPassword + plainTextPassword6Unsafe "N\155960\EOTC9\DC4\DC1\n\170284\35034\22589\DEL/p\1091599\&8\vg\\\nYGz|/\aNd\1029498\57517\DC2%\53814o\SYN\SI\38380r\1084853\987326d\1109120o\1093352\DLEl5&}i\ESCg\1104181\6571'`8N\1097917@\136616\1008516Q[o\\\f\DEL\1104293\5068\ad~\42876\ACKK\42883snr]pLA\ACK\1069066\ACK\73854Xl,\DEL;\DELwz\EM\998652\1069068\SYNc\NAKA7q\DC3\GS\53763ft\183331f\182272\\\1016358\1107081\SOH\4988\&9F\1036590\156517D\1066242\NULz\1030228\151834\&4\r\1072124\1071359\1036618\SIO\CAN\EOTY9\48690C0|8\168539]n\140134\SI\1024618\60889ra\ENQ\1076182\n]J\GSB\62239\64626\ETXgT\95776P\EOT>t\64744\a\CANr\t\1009997;Yy\NUL\EM\1071471Qz\SUB-'\ETB*\SOH;J4]9\182652\ESC\137920[z\40314=cpG_;\EOTu\1078222ic\EOT\vkz\SUB|}\1011861\v]\20796G3\f\f0\DLEd\v,\1080412\17244[#\57647~8OG`n)\1105707\&5\143076\SYN\67623wK\SOH;\DC1+[\1090978'\1013253\3118\SO\27597-\tB\1061075h\vVl\n$\RS\ETB\ESCw^!\DLE\1104169\DC3\986109\&3j\FS\149241\&8oj\96002\DEL\b\1010145t4>:\35038_\EOT\CAN\135636F\1027492lup\ETX+n\SOHP'yV;:\170481V\DC1~ 1\1007357\1103037A-\"&8}\t\DEL=\EM\SOH\NAK\DC3F\986041\1097800\NAK\54547/BI\5055,#\EM,\1036876gp\RSw][\DC2>\v*b\53753,\SO\EM\STX\69989\325CuTd\178982\&27B\1017690oRm$\157877Y3\n'?\SI\v\nf\175460K\SOH6\ESCl]`U)m\18666" } @@ -199,7 +199,7 @@ testObject_ProviderLogin_provider_19 = ProviderLogin { providerLoginEmail = Email {emailLocal = "W\1036612`)SR\STX,Zw\n", emailDomain = ""}, providerLoginPassword = - PlainTextPassword + plainTextPassword6Unsafe "yMl\64761\&0~\1050682\1021006a\1094043\fQ\94632~\\y\1074075\EM\ETXrqFsvG%So\ACKaTH,\f\STXP\100229\"\DC4o;UE\a\SYN\54275\DC2r`\vu1)2\DC3j*\"c9\988223\1066302)h#\164540m*j\DC3\44782~v+@\v\n5*ui\17305uS\1003317\1052173sqM\STX\1032754\FS\187251\156456fG\156207tv}\44129?V\r\1051429]\1014021\ETB\DLE*E+V}\155873\&9\EM*\1015779[o\17882\1106946\vr2\a\1067600\ENQ \47429@O\30164^\1059677R\42526!HLT\1018705.\1046981\1079471\GS=+D\160272\&4|\1023902T\NUL\111266\ETB\1028696\1054400w\1093841#\1038538b\EMF\a3k\CAN*#\989166\&3YKs\DEL\ACK\SUB\ACK\140776D\n\1089340(\EOT\1083765&/u\13397W@)pLY\987574)<{\GS[LkCU|\\\t\CAN\DC3Q\SI\GSS\DC2\1082373\1055041\STX\DLE\1005747\&3655H%\ETB\1002444\\k\150559xl\GSAf\CANy\NAK\1398\157796\1004189\auH\ESC\60829y0(\14035\986900q\99290\ENQ\1015592\SUB6l\GS\1101212H5\SIX&a1j#e\EOTgf6\1099735{\1100353\ACKa{\r\1075038\SUB\1021868Ih\v4\62793\13891\1065696\aSq\54460\135283B #\1022849\984792%1&\5746\1101604\NULW\NUL\1005888\v\12495,@p\996704_s\1005516\195083:\1053163\26817\&3m\1073977\v_\1030132\SYNL>\FSq_1\ETX\1014033I6L\ENQ\78160\995216\994139Y\64467zH\USu\1060134\1008128n\SYNvA\ETBG(\t~\GST\38349N\US\1055431bju\RS\164557Z\133252P \4977\rq\ACK\"K\SOH\DC4\aqcJ\SI{\171894\1057036K\NUL\175514F\1063815,\34846\SUB\ETXW\DC4\49752\ACKN\EOTLAE#\1052903hIWO\vj\125250:cP~q\1007541\ENQU4\32477\2386*z\EOT){\NUL.T\1065030=\176972\188734\1112137]\n\50074\&4s\f\1011957\140326+\52789\"Z\DC1oN2\174181\FSoee\DC1wD\FS\SOZ\165295\&4\b\NUL\r\917953\137757\DC4:7\1034978" } @@ -208,6 +208,6 @@ testObject_ProviderLogin_provider_20 = ProviderLogin { providerLoginEmail = Email {emailLocal = "#\4241|\18697\1075733", emailDomain = "\\\ETX\1091078A \ETXDT\r<"}, providerLoginPassword = - PlainTextPassword + plainTextPassword6Unsafe "y\SUBWK\1113065\US:d\996680\&1\1043738d\n]\29018\SOH\1033033Q\SUB\DLE\1049295\CAN\984477#7$\1067481'i2/%u84\RS*\989691\1039072\&4^BB?Ox\41256sW\r\1111532\&2\NAKF.\1058002I\13200\&8\STX\ETX`\GS!\1051949<5Y\1053480d\1029498P\1085303P\1110913?1\1100218n\NAKa\191218-\169668\9581\SOHc\FSne_\1089495\1073819c7\9454Yoe\NUL_>\178568\NAK\NULN\120250\1017029J\SUB\9102OkZ~yV$\SOHR\NULQ\1084924[\1072369\1103823?\RS\23331\1006369t`\3663}t\58854^p\GS\20579\1083297qA\bR^\54231\1070382f\132642qZr\ETXK\ENQ1\SUB$\DEL\1048222\1041456\1057367\1015965\v_\63061C\SUBb[g\156231i\1063061j%jZ9L\SI6\1092470l~\t{}>N\1028038\1093894;\CAN\1012890#\DC3:\f\EOT.\1034408|\999878%iMr\132269:\ETX\r\1059952\ETBB\1030258;\SOT_KJ=b0\1002499/u\SOq\fcV[h\146171}`\NAKN\DLEP\129031H\CAN:F\49767\988419+\1037039%2}|s1\49683[Rp\SOHO\t_E\rK\SUB|\SOH\DC3x$\1020540\1083269.\64699\ESCZ\69926\SOHU`\t^\t0'8\"O\SUB\1048972\STXnbGo3_\1056648\1083755\995789\SYN.%\RS\30271\1091646Q\GSD:gJ=Q-0\1113951q\STXx\ETXr\ENQT?\ETX+$\29558>\1103438\16152\396\f\146489Qj\SI\1067862Fs.\SO\118908Y.\1023882J\33637\f\154768A\156772\5117P5\FS\ENQ\118931\SOH\SOH\1059107\1064213\17324\1061449/x?\SYNI\61424\185549\NAKU\1007536\14119Ig\145126L]^.\166857}&(\188383\188556vGg3\1026179\SOl\EOT\142432\RS\fs\164737p5LS\998282\SOH|\nvm6#\1103808\1103698\GS\DC1\RS\b\157935\1006286\SOH\ESC\184018\1053117bE\RS\ACK`wj#\1014169>Q\US\1051484ss\150964\1092969ck%>Q\3766B$\STX&\28586%\UST\US\ETB:B\ESC\1025229\bC\185625h;O:?\v\1035263j\136984j\1011335;t\1080482\t\SO@\4855\1035237p\190479\a\996213\15858@,\169117duBfR\DLE\72879!\ENQ<\DC2\SOj\1080800XFs\1037447'W\NUL@SDA\1088037" } diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/QualifiedUserClientPrekeyMapV4_user.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/QualifiedUserClientPrekeyMapV4_user.hs new file mode 100644 index 0000000000..4070ee99ac --- /dev/null +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/QualifiedUserClientPrekeyMapV4_user.hs @@ -0,0 +1,58 @@ +-- 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.Generated.QualifiedUserClientPrekeyMapV4_user where + +import Data.Domain (Domain (..)) +import Data.Id (ClientId (..), Id (Id), UserId) +import qualified Data.Map as Map +import Data.Qualified (Qualified (..)) +import qualified Data.UUID as UUID (fromString) +import Imports +import Wire.API.User.Client (QualifiedUserClientMap (..), QualifiedUserClientPrekeyMapV4 (..)) + +domain1, domain2 :: Domain +domain1 = Domain "example.com" +domain2 = Domain "test.net" + +user1, user2 :: UserId +user1 = Id . fromJust $ UUID.fromString "44f9c51e-0dce-4e7f-85ba-b4e5a545ce68" +user2 = Id . fromJust $ UUID.fromString "284c4e8f-78ef-43f4-a77a-015c22e37960" + +clientId :: ClientId +clientId = ClientId "0123456789ABCEF" + +testObject_QualifiedUserClientPrekeyMapV4_user_1 :: QualifiedUserClientPrekeyMapV4 +testObject_QualifiedUserClientPrekeyMapV4_user_1 = + QualifiedUserClientPrekeyMapV4 + { qualifiedUserClientPrekeys = QualifiedUserClientMap mempty, + failedToList = Nothing + } + +testObject_QualifiedUserClientPrekeyMapV4_user_2 :: QualifiedUserClientPrekeyMapV4 +testObject_QualifiedUserClientPrekeyMapV4_user_2 = + QualifiedUserClientPrekeyMapV4 + { qualifiedUserClientPrekeys = QualifiedUserClientMap $ Map.singleton domain1 $ Map.singleton user1 $ Map.singleton clientId Nothing, + failedToList = Just [] + } + +testObject_QualifiedUserClientPrekeyMapV4_user_3 :: QualifiedUserClientPrekeyMapV4 +testObject_QualifiedUserClientPrekeyMapV4_user_3 = + QualifiedUserClientPrekeyMapV4 + { qualifiedUserClientPrekeys = QualifiedUserClientMap mempty, + failedToList = Just [Qualified user1 domain1, Qualified user2 domain2] + } diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/RemoveCookies_user.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/RemoveCookies_user.hs index 701ccb86e0..2239919ad1 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/RemoveCookies_user.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/RemoveCookies_user.hs @@ -19,7 +19,7 @@ module Test.Wire.API.Golden.Generated.RemoveCookies_user where -import Data.Misc (PlainTextPassword (PlainTextPassword)) +import Data.Misc (plainTextPassword6Unsafe) import Wire.API.User.Auth ( CookieId (CookieId, cookieIdNum), CookieLabel (CookieLabel, cookieLabelText), @@ -30,7 +30,7 @@ testObject_RemoveCookies_user_1 :: RemoveCookies testObject_RemoveCookies_user_1 = RemoveCookies { rmCookiesPassword = - PlainTextPassword + plainTextPassword6Unsafe "~\DLE y[b\aS\ETBf\165617x.lY\144244r`\v{\9628\CAN\39987%=\f\1096516U\DC4%\1062824\1060574__'m\RS\DC1\DC1c\58278\47267'eS\62075nST\SOH\38363\&70\184977\16409<\1087023\154326nF\1083847\&2~r$=\1023019IWVMd\23687\v**\CAN.)\128397=Pt..\160303m\152336a\a8'\129122z\1026688=e\DC1\1112305\SYNQ/_\100425lV_}(dj\1007316\rZd<\RSB\CAN\1040599\vY\1013052\986793\985671\NUL\ESCJ\1034011tY\21996\69863\FS&s\EM\1112635o\DC2\a7\b^\65317\&6Zi[`o\ACK;\169627\DC1w\18173WxA\1023958c\173780\&3\a\DC2s\133508l\DC4 k=zy\155530\10060\"\37575ex\1058728f\DELQ\1067079\DC2\917961P\26569*\10329\96874F\67677C\DC3\1078547\DEL#\1102527-\t\GS\1113174`\ETBCg5y\SUB\EOT\EM\179479\&1eS\GSRI\ETB\STXN\9021'jj(\1039923\\Cn+zw\fL\"\164893(1\131177\1102357\ESC\US\185088\11429\aoj\98391Y\1019608\"\\\1024267\DC3>9\1009548=\US\6648\GS\153529\ENQ\CAN\1086366\983773\fg\1007968\1061229\149186\&9y$\DC3L8\US\n\DC4\1081485\99847Fh\1021505'\63755&@\36277\138987\1067265\1037682\ETB[\61437A\1068948$D\1021662]\ETX\67726]b\SO\983789C\1113071n\1086865\&9\ESCC=`$\FS\161385g@\160312RS\135404s\59787\&4\1084324\SOH\DC3\1084568\v\1004384#\144094\166834v\1064183x\1007247)8&y\"\12739\&5\"mV\DC4\1024086A\SYN\SYN\1091794~\27582\SUBp}?n\1058047;\1046488\NAK\1089289\r7\150314?\t\69608c}xV\1014630\49894>\1043598\a@K\ESC42\12076\1001536s\31446~mXY[m\152863\a*l;\1028244!\\|\DLEys\1043026\48317aS-\DC4+\SYN'%\DC44\61424\&5\189792\159439\DC4\1009152\59988ayCtYM\162130L\SOm\69240\71450Mi\177207S\3658", rmCookiesLabels = [ CookieLabel {cookieLabelText = "\t"}, @@ -46,7 +46,7 @@ testObject_RemoveCookies_user_2 :: RemoveCookies testObject_RemoveCookies_user_2 = RemoveCookies { rmCookiesPassword = - PlainTextPassword + plainTextPassword6Unsafe ".]6?|n\a\1065388b`\n\GS\39199*K\DEL\SYN#&\nfu\303\EMo\SOH-\DEL\DC3\161956f\989883g\ETB\60099B\DC4[DG+\NAK$w6]s\GS\ACKNE3\1033233H\131509\&16u`2nt\1019805\&3u!\NUL\46988\1113403\\\149411\172028\EOT\41891DC\172619\38340h1of\USh}e]\51011-VT}\1095536\23412\SOH\1106779\58945\b\59014/\SO\1078889\1016692lsWIV\bvc[\3021g+i{\FSx\1103976\t\30057a\SUB\ETB\1104229\&9\CAN%Ima+\1070890\133992\ESC>c)@6Y\DC4m\b:S\b\1061075[$7\166679\r\EM]\FS`4\8919`caw\DC1\SO\995307\1059173_\120882\60175A<:K\181573Y5\47463R|\CANTzx8\ETX\1108945\186155\96907\USD\1046364~\97956\155949\SYN3\CAN\15406\1094233X\163803;\9600\&2\SYN-+\178365\24668M\153159\&0X\CAN\1075318O\48886\EM\174251&'\ENQ{m@\5450N\1089713$c84\US~#\1051743!\35284C\1053345T\ESCV\145721\&8Fwc\169935J\97503", rmCookiesLabels = [CookieLabel {cookieLabelText = "\26318\33391\EOT:\144276"}], rmCookiesIdents = [] @@ -56,7 +56,7 @@ testObject_RemoveCookies_user_3 :: RemoveCookies testObject_RemoveCookies_user_3 = RemoveCookies { rmCookiesPassword = - PlainTextPassword + plainTextPassword6Unsafe "n\1074004\&8^\1048275\48742x\1063089\EM\70670\1017945a\DC4\1092504<'\DC2'CS5\17737\rc\DC3\1028471a\171752\6694+\SIFwqHI \DC2Cc\156156\1047335n]\995075\CAN\1024943|\155491C\b\EMt\t\CANs\175523\988484\f\1045889\179665-X\ETXh\1010180\1038017'O\1004140i\63367\ACKM]\162300\&1T3z(Sb\996128\986764\1009876\&3\1019290\988277\1026196LM\STX\SYN\31631?\r\1012626@/R7;M\NUL1\r\1110659\CANX\1100936\EMQc\1102268f5#k\NUL\ESC\153067\SYNE=9\SI\DC2uw\SUB\DC1\EOT\1054510{\63090\SYNi\92523a+\DLEZO\"W:Wk\6376wg_J\US+S~E&1\165458\1034011\27203?'\157835\119845%TY\998234D({.\9336\&7\133572F\1022194&g=\1051853|\1072901A\a\DC4\CAN;P\1024587S\SOH1GyP\18999|\1048580s\135528G\9609IGB08B<\1097349\1063644\CAND\ENQ\992040|~y\DC2\v\984222&\182974\SI!z\DC2 \27161M\29167\EMW5\vN?]!v\172138,1\182336\STX%%\EOTv\SYNg/\144764\1081383\32652\1079881,3'\7545)\DEL\ACKP\CANI\US?\DLE\126073]\139395\1087857bo\f\1109978h\1044925i\SUBxI41Sf\144057\182522\153605\v\US\1024502\v.(\GS\EOT\175982$\DEL\58992\USEQ\177834!!K\1047971Q\ESC\145189`;\1092648.\ETB\ESC\FSRN:\SYN7\ACK:\n\154169\1023167<\146858:\993302E\b1JN\1017985\ENQWAK\ENQdAXD4[O\\>\RSxV6$\v", rmCookiesLabels = [ CookieLabel {cookieLabelText = "E9"}, @@ -78,7 +78,7 @@ testObject_RemoveCookies_user_4 :: RemoveCookies testObject_RemoveCookies_user_4 = RemoveCookies { rmCookiesPassword = - PlainTextPassword + plainTextPassword6Unsafe "4\1102804\1064901_\GS\STX\18346\&5J++\144782/\nR=8m[\21769S8\154932|\168718\&6\1024454w\52721\161903nTK\991546E'[l\34792$\47524\45942\1026587\1038545\DC49w\FS+\189755H\DC1n\989334\ETB\SIJ\ENQ(M\136816\SI\v\f:\NAKr\151754\1046700O\ENQ\63854\46485\1005290*\132235\1043453\154333Z\n\147930j\995537!b\66478\21782K\b\164738c\83125\v{Zm\126559)\DC4\1111162\96336\1011262\SI}M\1025962_\53279}&\989788\DC41\DC3\NULr\1052010r\119595D/H,\SO\SOH\SI\1038741\nD\54315\&4H7LK\1008789DM+!\GSY\f%vof\1007306\NAK\145073\1060272\139970\62576\ETB\SI\r", rmCookiesLabels = [ CookieLabel {cookieLabelText = ""}, @@ -98,7 +98,7 @@ testObject_RemoveCookies_user_5 :: RemoveCookies testObject_RemoveCookies_user_5 = RemoveCookies { rmCookiesPassword = - PlainTextPassword + plainTextPassword6Unsafe "&\8450d\2317E\1071031\162311\DC4OR\SYNE/4\SUB\n\bI\USz\ETB\1037079~lI\170695Id\SUB\72819G\a?\1078248L1\172461;D\ETBsI3\DC4\GS\1111322p\SUB!E2\fF/K\nE:R\DLE\n\185553\174465\EOT\1008445f9", rmCookiesLabels = [], rmCookiesIdents = @@ -116,7 +116,7 @@ testObject_RemoveCookies_user_6 :: RemoveCookies testObject_RemoveCookies_user_6 = RemoveCookies { rmCookiesPassword = - PlainTextPassword + plainTextPassword6Unsafe "w]\159418_H\172755\137808O\67751p&\1057267z\EMl)*\US,'\4481\STXz929v\42411~\a\15834N\STX6\STX\GS <\DC2\no~\1027753Nn\166014\1002433\1037607jH\ACKN\ENQIL598\NAKw\47985=Q\1075460#\1012931k\GSb$%\96700\163122\40349\DC2k\b\63286\FS#\"\40898?\1026494sE-W/\\|\SO\SYNjzYW\22937s\1101861\21205;'\1038086,}c2\1045179\NUL\ETX6\1036582\DC1wNis\DC1}Q\1074930a\1030918qd\996927JOUK\78101$a\SOH\75036\1091437\1109347fY?\SOkX\70170\191402\FS5\38440\1100416i\US(\ENQ\83248\52600}\DELm\53151=X\DC1\ETXM[6[\169507xW\15060\989837\b\SI,\1076623\49985\RS\RS\SOk)\44862zE\47080\ESC\2315\1090173^\1060289]\20780\EOT\97838\135916\a\138562CA&\ETX3YK'S2+fI&\1092619z\24349A\24648\&5Qu\187043r\1097016I\53422Z\SOHdv\1113858k3\GS\DELN\110964\14426\178818\54900\SO@\1020841\1073781\&5\STX\DC2-\STX\1059065\&7\r`\EOT%\SUBN\9521!3\ENQ;Pr\134932\11590\144594(\1059588\STX\1092686\GS\22445m[\SOH`\GSB6\1093918C[v\19595Sq\NAK7\DC1\45766\31039&\150919\48875\63709JTBR\STXp\180507?y\15242A\58153ik\1100370s:Yl\138494=\37874)\rGU\65530\177230\157036h\1109341\988577\9048\&8Q\a~\18382\SUB\1094063-}1v\18881h)\1052487~G\48401\"I1?\154186\11104%Px\DELth\DC3su=/\9183\143001m+\CAN\7444Z\DC2\133207\RSXe\DC3[\\8>~\SUBR\NULY?0\SOH?HI\be\1063343\&5\STX|\ETX,, e\1009518\t\SYN`-\20161\DC2\EM\1069697=\NAK\NULDrL54\CAN2Fm\1019712\&3\DLE\41731\19699\NAK;Jv\SOHb\47652\1077833(9\1053955DV\1094400\STX\47484\37476\61515L\bS\STXQ\NUL_\1066043Z\987480\27777(\SYN7\SOHT\1053283\131547PhI\154346;\171904[J\1033568POxH5\SOH\fu\1083780:\1087406\fx\"\160644\DC1\ETX\53105d\160880}\1047755>\ACK\SYN=j\ETB\1000264\22614mF\1040449\&6\tl`\357$\7983\1046005F91\1092328O\SUB:L\1030242\1044272j\1056871\26141SQwM:>\1073831|e^ixFd\a{\t\155098\f6q)>>&J\USm\DLE\63897A\SYN\ESC\145354\4649dPHIBp\US\DC3\1064840\EM+x\NUL\nk)\ETBS#wzkV@\1047484\162584`\1108477\EOTf~|M\1064576ju\128005dz\161287(\154242\1030107\169500\171140\993366\DC46\EOT{c\EM\DC1e\42116NE\138454\&1\44451\a<&o\61411\168863%Zi:Q\173515\US\15783:\DC3)\1077093$ViLW\SUB\"m.s3\179500e\7133* \SOHyny\EOT\DC2 xfa|d8@I\ETX@D\149538\ETB}w\f\1076872\ESC\ESC\156414:\22792\DC4\1009280fm\SUB\46688g\155275#n\158905\"\ETB8Z\FS*\154535!\1042094\US\24564\1009578>r\174073\DC3(\47427\EM.\rc!\RS\SO\DC4Mve\ENQ\155638&\"\1038446[k\1078916\ACKw0V1\1080263\153959\1070816\60593\37795\988596xt\USXI$\1040297Zgw\190774M\DLE\GS\144429\1080305(\nFot\n/\DC3\191281n\DC1/\r\1094779%1\STX/Yw6=\ENQ\991859>\95544\DLEO\1088332Q\124970\9347\&3O\SIo\176451hmVar\NAK\EOTCw\DC2\STXqKCE\EOT\1014559=~\FS|=#\191167\34136\1076113/_#a\22856N\FS\132958\DELt\1058130\1055453d,C=b\1072988\&1]x~\NAK\1030595\1043441b\58981\1003992\NAKn\1073851:\176269\1047965\24337kk\US\25317\31713\137045\45961\US+\33078\990516J\6312Z\t\68213@\1096088\1012809\&4I1:\NAK\b\160700\DC2\t\n\DLE\ESCp\DC3\161175k\26439f[Uq\SI`\1026102\1072178\188473\DC1x^\SYN[6nl%\1031781L\USX5^H&O\GS\SUBE\SO\10205\1068465F4\167670$\ETXIt,U\f4\ETX\SI\176494\ENQaga&8I\82998X%\1073997+`\93020f\987218A\119129OA|Lg\STXX\FSa%t&!\1023870\182892>\DC3\EOTlX+\DC1#\STXz\1043705t.|\EM\f\ETB\39680nr>D~F,\r\147801\DC2'Xv\"Y9w(ewO\CAN\168923\&3O \STXt8cK\SIW\16221\DLE\1020099\ETB;~!\ns\1060956F/p\NAK#\29723\ETXS\986769_r\DC3rP\SIa\47748X5-\996337wi\DEL(\41245\1111170\DC1m=\EM\ETXNGf\SID'\NAKq\1017502\r{\37868\1067045\DC2o\989345bZ\NAK\GS\1059619\EM0\187569\DC2\172967\NUL\nkh\1044166vsZX}\1024791.1%3Q\168727%\v\1110210(tmJ@\"X\"\32752c\EM6\995028r^\18683\STX\33207\NAK\150662\142232\&1f_\DLEC\993591c$\60318\aK\1062209\&0\DC3\1010730/H&\176383\b\160276-\r\1006544\&8A/}\US\66272\EOT\33298)\169967\SI\1022721\20001N\ESC\995853\46054{\a\125012\SO[:f\148243\1000397\59885\180729\EM\1017127\40168$\83501\1024153\1106750e*G\189328:wq9\31833W\1078\1060331\FS \1039837\97120\52002\137625\23159r\a Rap|\24194^'\DEL\1030264\ENQ\vv-=)\DC3TH\41337\SOH\CANC\ENQ\FSDi5\ACK\n\fy\\\18236zP\SOPx\DLEc\1000266\25869\1047578\1010027\1040769\DLE_%\SYNJ\ba\53088\EOT\1049972ZP\1034956\32225`\15608\990258\SI\SOHA \DC4wE\ENQ\STX\100145/\SOH\NUL9E\FS!\1076691q|\DC3\DC1$b\159600p\1001300@\RS|m\ACK\986847\94392\b\1057250[Ld\92689\&1T\27296X", rmCookiesLabels = [CookieLabel {cookieLabelText = "7\1007781"}], rmCookiesIdents = @@ -233,7 +233,7 @@ testObject_RemoveCookies_user_13 :: RemoveCookies testObject_RemoveCookies_user_13 = RemoveCookies { rmCookiesPassword = - PlainTextPassword + plainTextPassword6Unsafe "r\a\SYN'o\62168V\190230mk\96544\&4\ACKiE\1002439\n]\SYN///\54880}c\NAK9\RS\6820U\vOc\EM\167131\b\ESC \119910-\1024682\SUBwN\988237\&79\1009173\7678\SOH\1104366\176682\DELaD\ENQ\1077188H\176394(Z\a\ESCK\63389@\151539zfD9{Yu\DC2/EC\CANQ\ESC,5\1100500k@-\1034270\1018333<\168865\SOH\DC1J!\917791=\189915\1031587\180630J=N\155206\98572\fT\164918\65531Zd\STXp\SOHju\DC2,\990766@\ETX|\ETB\ETXX!\DC1uZ.\1041808\1016592\DEL\SOl\43846\1077733\&4!Z&_#m\1081763|L=\3360\SUB[\r\1025059{CN\998229\171237\&2\1015391\afl\144022\1068127\60998\&73k5\62642\1084159\45746\1098058\989650\150649\fLmE>V\GS@K\1035300\&3,e!\DLEcV\ETXI\GSP\ACK\b\ETBb\CAN\SI>", rmCookiesLabels = [ CookieLabel {cookieLabelText = ""}, @@ -258,7 +258,7 @@ testObject_RemoveCookies_user_14 :: RemoveCookies testObject_RemoveCookies_user_14 = RemoveCookies { rmCookiesPassword = - PlainTextPassword + plainTextPassword6Unsafe "\144709\1041442 q\78098^$8u\127324\SO\1044337\188234]G\6701aN\GS@X5jWZnJ\DLEy\1073950\174462\DC4if8T\1023255B\1097694\1029784z\33299\77922\161466CQ\1112827*\ESC\997114\&4L\DC4Dl\RS\STX\1033395 n5\1053371\1055345\ac*/\SOHn\CAN{K\1030765\162633A'%Hm\194943\a7\1023991\999654r@YSW5I\RS\137767\DC2J>ZC6\SOCn+\984949mdiK\FSS\165809$\159400\154650;g\CAN/(YfSO\FS`h\166140\1029155\ETB\DEL&TMoO\EM}PJ\ETXJ\SI\NAKwE\161621\NAK\ACKR\178897r3\DC3i\ACKs^RN\1110843o6E\NAKa\n\1069674\100758w$C\1066228\100483\1096666\98103\1044981{Ii\25764W{\t@\1108548?$\57746Y`JA\\V\DC2m\1094766\1093862\&62N\SIC\rK\183210\8084\60608\149539\1069467\1095302\182768\tGO\73813\a\1086319\SOH;\1088275^3\rY\17399i\NUL\2641S2k7\RSO\1035449\DC3\1025441[O!\990640\ACKN\RS2\NAK\7590[\DLE/\ETB{?,RNNM\DC2\SOH#J\vbA\34763\46139\1014229/\ETBf\US\110597\ACK\ai7N\987283\a41\SOH\156359\DC2~:f\SI\NUL(\172432\ESCCo1N\nx4M\43037\ETXUjM^\1052130#\1086102\ESCbL~=HM6Ud9|\SO=\1011725@`\r\1015961\ESC\">i\1025923\&4\101053'\SOwM\\\187240Olq\SUBo\RSZ\13073HB~\"s\144744\\gO\26365\&8\1102131.$\SUB\1055833r\52838\151195\CAN\DELdH\146689q~\1059688e\DEL\47543\26876\&9ZwHf\61877\1056592\1092993m1\vb\NULC#\NUL\SUB\ETXZ^\986446ig\1008052\US_\DELv!Zn.!\34745b_\190192\DC1\62443\159822\NULu\ba\170683)\13139o2\ACK\1043964\1038159[k\53126i\1106233\fu\1055760\152727>y\1057898}\1105150\1067962S/\SOH", rmCookiesLabels = [CookieLabel {cookieLabelText = "\"\1017491\a"}, CookieLabel {cookieLabelText = "WG\1030572\62089{"}], @@ -269,7 +269,7 @@ testObject_RemoveCookies_user_15 :: RemoveCookies testObject_RemoveCookies_user_15 = RemoveCookies { rmCookiesPassword = - PlainTextPassword + plainTextPassword6Unsafe "8\1096227]\1005032*ZB\1070453\128871Z\166100,Pt\"wf\1090677\1047788l\NUL)\442\1013744\1090379\1081560\1034157%f\171139WeL\DC2*\SO\35933\&0S@:\DELi#d\aN\50148dQ;\\w\NUL>fR\DC1PzIM\v<$`'8\165371_P\987002\1081070ex\1082394M\DEL\29721\SUB&-\1081097\1072025e\EM0\1018606\52274\984683\DC2\US\t\1034976\1056657\1056102\1010831\DC3\DC1\23452\r^pZ]>zFu\78656\"L\NULl\GS\169309%;k6/<\194614\1068607\1067112\1104518Q^P\162756\1018086D\137278\47522Q;\1095424\EM;\SUBk\96065&\EM\CANj\EMd9\1063514\&4|~\140375\ENQ\1084306:}\1070899'\178741\r!\1058004\DC4G\149855SUi=1\29733\95046O\EM\1103130fxn\EOTO\ETB\f\f0D\EOT\1047024\SUB*Y (\NULh\1066131\\\1096957k}\v@3N\160900\&0\1103512\DC4Xyo\119325\1019179\&9)>n\1053969s\STX3l\171104\SOH\f\RS\"\FS;\SOH2e\30047ioG]\185478\38451\159767\&5IHH?\SIO[~/\DLE+\nH.\1091753F>?7\1000332\1020204\1095293yvBM\986136J,F\68335u\31826\158325\34549I\ETB\DC4Z/\1091168`\144049:\r\fD\ACK\bqz\9653\54822\\\STXa?\1007578|\GS>=M\v\169342\1112091\163283Qi\ESC\ESC<,uT\30310Y\STX\30565\&2DC\1060622\t\DC2x\1091176:S@|1h\US\1089192\EOTh*B-\DC2\v\1091993@T\ACK\1013085pj\9481\&3i!\96510M|\DC1\FSAID\EMeXtMzq\SIn\1001835EBs@\b\FSi\RSrgD\144430+\45871\CANi\1000497&\139423t\23767\983347\2668\DEL\1068924<3\EM.+fd\39907B2\1029066\FS\STX\180134taa\1024831G\ETB\n\EM\151241\32939\1110848UV\65353$\984465\1058295\aaN@\152052pL\3978\NUL\1049575\rT/\ACK\1053411L\NUL&G\r\134346\&7-dB\ENQQ\143341qQ\ACKY\1032329\vP/\b\95910q\EOTK\2866\ETXR\RS\94730\1111649\ESCR\SO[\f:\GS|@\"\190867\119249\1073888\1037623ma\t\48187\DLE`\r\1070282\FS!M\v\126482\183394y\184184D3=A\1112567\&4sZ8,&a\DC40\EM\188383\EM\159443\1000258\DC2\35527j\FS\1011085\EOT0]:\USu\CAN#r\STXE+Ov\44692gb\1016640\CANZ[\1089017]\1025831\157479n\1066201X!\1071565\1001761\174983>!6 q\4912\1043252Bf-y`\191450f;*\110614B\28419\SUB\188203zQG\1019466\12802d\ETBkGj\"\1092749\21771\30425\NAKT\59321\STX\1003641S\v\1023077\&8P|F\39285C\SOHs+#\100532\120405\ENQ\DELtT\bY\a\993728q\1066350?\1071701lm^\1024461ijL\1057142\1028607+\"\ETB\176470H\NAK.jZ\60417B\119156I\\\GS", rmCookiesLabels = [ CookieLabel {cookieLabelText = "\DC1"}, @@ -293,7 +293,7 @@ testObject_RemoveCookies_user_17 :: RemoveCookies testObject_RemoveCookies_user_17 = RemoveCookies { rmCookiesPassword = - PlainTextPassword + plainTextPassword6Unsafe " 6*\NUL\53342r\DC1\165656}>48\DLE\1083939a\ESCu^\DC2r\1096635koq\1104892@\17218f\1029877:\1069113g\164097Pr'\22073\177171)\GSDE/`\DC3\SOH^M\SO\1041660o\DELN$%x\54111\125060\174761i\169089W", rmCookiesLabels = [CookieLabel {cookieLabelText = "\21985\145626"}, CookieLabel {cookieLabelText = "\172449i\GS\993013"}], @@ -304,7 +304,7 @@ testObject_RemoveCookies_user_18 :: RemoveCookies testObject_RemoveCookies_user_18 = RemoveCookies { rmCookiesPassword = - PlainTextPassword + plainTextPassword6Unsafe "\DC1(\189756\173754au(_i\999804kz\1087491K\DLEgc\SUBP\NAK\1087664\&4\162570]\125004\SUBZ3mS\175582A&\1030993\1089970O.=\EM,\170655\35143\US)\r\t%b]\GS\144263ne0\ESC\165573flCO\24483hI\b\ACKqM\38155JL\SYN\DELD iP;9.ms9\ESC\\F\NUL\EOT8\148621\1084732]g\r\59002\994545\&8\SOT\DC3\1108348~\38502\ACKW>o`\RS\42469\&9I`Vm,YFt@yl\1089376\166159\&7_\ESCev\NUL\SYN0\5663\SYN\177790/U\GS\CANu,\1012312\&3\ETB[TBs\ti<\SO\14039?Y?`\45468\&3!M\EMHK\1007650\NAK}\61329\&0o;\1042207\EOT\1109945~\149894\f$\a4j\1102230\1023392v*\1078608\51776$&_bP\99509\31656DB\131583E\44212}\58030\&7\985331\1031459\45622#\USA\11919\&8&j\135104o%ms(\ETX\1093281T\1042896j\STXF\a(\33759K\99873.\ETB(\STX\21943N\1046694\178021\ENQnk\SOH\DC4\DC3s\SOH0\1032873\t\1008544T\1006265F8,\33349\ETX\DELO\US{\v<\7337\1070826\EOT>\US.\"\1005396[\DC3SE\1044074\&9D8bf\32635\&5bR\ENQ\1112871\1097678\&2k`\182237\44708\bM\37922&*\DC1F\STX\b<\1095550\985555\NUL|g?_\63870<{$L#\US\188935 \\.3\t\1061467Q\6201\1020636P\SOH\NULb~B\DC2nu\139393\EOT\1106754P\1062273\NAK\177279\1020100\ESC8\\_J*\SYN\54258n\DLE\52564\&3\119865\SOH\1095637\1108514ol\1046547\EMto>8\"\DC3`b\DC2\b\1111456a\52678\SUB9Y\ESCO$t\f;\144560.\\N(sV\STX%\1029998b\NUL9\157340\1050167{Eb\EM{\SUB\1013254\&0\1094146\CAN\1001180@\1032710\1083507M\48504\985362\1093712\&8_\r\DC3/\1019693EB\1036798\SYN\1066289YV\997690\1007559\1055442_M\STX\1074672qweO\132991w\1070167\DC4\\k\SI\1046701s9\156143\&5#\1071695\DC2>;PmFl0b\STX\175038P\SI~P!|<$\29047Ta\"j\1069035p\36712t\171493\993283\&5&RJ:h/\1038980\DLEG:=|\148164\126978\NUL}BCY\1001333\162633r\DC2\43617\998882\ESC\1002253\ACK+\1031629'\t\6543\154711\NUL\DLE&+)TvZ\1089940\1036958]GW\CANP5yp\1098337b|m\153195", rmCookiesLabels = [CookieLabel {cookieLabelText = "-\996503\SUBI\46825"}, CookieLabel {cookieLabelText = "\65109"}], @@ -315,7 +315,7 @@ testObject_RemoveCookies_user_19 :: RemoveCookies testObject_RemoveCookies_user_19 = RemoveCookies { rmCookiesPassword = - PlainTextPassword + plainTextPassword6Unsafe "\GS(xl[\996326\US\1041865[\1044893&;R\991026*{\1015003\1107871RXc@r\SI\1031786\&4vb\DC3I8\153057\SYN\NAK\1094392\USR\1101578P}\1104153\vn)H\b\70201'\194565\t\DC2\1059702cO5q\10271]r\1005712C\nLJ\19671ax{Ys\15238\59228|\1033537\EOTn|\1016215;D7Pq \GS'_\142437\a\EOT\1105443Zo\ENQ\DLE;3\1008922#Y?E\1065738'\138580b\1062145\100292b-?\STX~\985185V<>\DLE\n\97656O\DC2o\31775\164593y\1188\28942\33019\152481\FS\41692}w\DLE\39133\"\n\1025851i'\1061701\142441\157720\1090746\"&H\30641ypueH\9764A}no\120190\a\62252\83434-\b-\f\181491\RS\ETXGC=\1004462o9!\50322y\n\CANYf\SOH)\1014682\STX\184078uIv\1088388\&1!xUr@O\DC1,w4yg\ENQE6r\30975\72240\US\1075499-\ESC#\"!\44449\983242\1028521\990982\r\STXi|\ESC@J<[T[\1072060Y,a!\15651E\141897\134217Ej/2$AF\1105526Y\3359$JY\27926\986109\SUBI.\1081785\1040668MhzLfbA2\44394U\1015373GC$\161715\ENQ\160894.\ETX'\DEL\ETB\1084878\1062536\SUB\1063528\1088062s\DC2\US\nc\163970zTm\v\63804\boc<\RSxO3>\129566AC^+Gp\US|qdT\54796\RS\DC1QRf\DC3\n\1105984,qfsb{E\v\"{\61000c\118887\37251mV\73057P\986945A{q\991924\b\22919L\ETB{PGle\180524I5\SOHu\NAKha47e\tB\169958m\1045444\STX:\190292dp\12771\DC3\ETXE\1066415\f\DLE\RSjZ\STX/~\DC1\184505p9\1056236\437\37920\f9\991390!HG\ENQ\7060\1066531hn$\SUB\14465}\1004123\38601\\cvma\1001639IuY=PS\"`RF\GSZ\1025568\175487J\v\984836\DC3\ESC$\150920\158449~.\168686\94591\r\SUB\1082885?M\"Aff\SYN}}+\142354\1019123\&2", rmCookiesLabels = [CookieLabel {cookieLabelText = "ZIa"}, CookieLabel {cookieLabelText = "A+k&7"}], rmCookiesIdents = @@ -332,7 +332,7 @@ testObject_RemoveCookies_user_20 :: RemoveCookies testObject_RemoveCookies_user_20 = RemoveCookies { rmCookiesPassword = - PlainTextPassword + plainTextPassword6Unsafe "TP$.\RS\1103560X\EMWWs(AG\187449\1037444U\"\FSC\ENQ\98292>\DC4h\1032976)\SI\172025VS)\US\\\"_5\100867e\a\SUB\NUL&z\DC2}\1070160\167750j\10866UD\ETXB\GS3/\97535\1065926~\"?\164238\FSc\1072711j\EM\1017060]-\134842\1085689\178500oPU\t\171087\f;\20343\RSR<\62244\67721{T\ESC\\RpG\SO\990145.\"s\v.\DC2\DC4\DLEK2\36309\1068697\4766\15657\&7~[\121227X\US\74339nk+0!\f\\Z9P_\98842$\1008036\SOH.]H\DC3D#N{\DC1\66422Gj/T\DC1\1045049E\999678,\182670\\\GSO\a$@UP\137031yf\1075416mX\aEO@B\182439#3\EOTHy\15097\ENQ\SI0B\ETB4\ACK4c~\ENQ\137312\&3Zu\179378\SUBR3=8\3144\1113106;b~\ENQ\ENQx\1099796\&7\rLP7l\ENQ\n7\146981(\SOH\70834<\1096963U\NUL|\148870>\a\a{\b&KYr_9Ms#k\DLE#\1091276z\998907\DLEl\1022340\176581VF\9961u8G\v\f;\52352!\998238_\1044096\61267\984894R\aGA\1022828km4:|?`&0|\1050827\DC1~D\"{|\986395\&9/t\DC4l\1056011F!2\US\1005570\ESCd\DC4/p\1113468r\US{(L\183948\18218,zJ\SOH\ACKa/\SOb\f%G &6h\152825|\b\EMi.\126079\&3\FSI\1017071\ENQZY\RS\1087793K/f?-f\SO\EOTN\1083227\USx\152853\&8PM\3623d\DC2#\ENQ68c%\DC3\DC3\1103767\176028\186261t\SYN\1004211G)\nl\143832\60085A>\164469\1093537\1017120I26~\164093\140945Jp\EM.V9lQbi>\US\bng\1961\1036480\138244\165277E)@WD\ENQ\RSjA\1074933\40315\1067853\&8K-\SUBt\DC1\25843.\15887\1031699t\ACK\37200\NAK18L|\SI\ETX\EMxE\DC48j\149410\ETB\32686\&22I;}\ACKNo\20841\GSg\CANquY\1035892\a\EM+\DC3\NAKe.\STX\22922u\1110172s\1015997MD\b\1104848H\SOH\NUL;i4u8Vp\1067632o\US]i\153757p\fv\38857N\8888F|s[\153706:h\1096400J6d\NAK\16632u\166131\1045843gq", rmCookiesLabels = [ CookieLabel {cookieLabelText = "\1107621"}, diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/RemoveLegalHoldSettingsRequest_team.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/RemoveLegalHoldSettingsRequest_team.hs index f179e68de1..15aeb32d4d 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/RemoveLegalHoldSettingsRequest_team.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/RemoveLegalHoldSettingsRequest_team.hs @@ -17,7 +17,7 @@ module Test.Wire.API.Golden.Generated.RemoveLegalHoldSettingsRequest_team where -import Data.Misc (PlainTextPassword (PlainTextPassword)) +import Data.Misc (plainTextPassword6Unsafe) import Imports (Maybe (Just, Nothing)) import Wire.API.Team.LegalHold (RemoveLegalHoldSettingsRequest (..)) @@ -26,7 +26,7 @@ testObject_RemoveLegalHoldSettingsRequest_team_1 = RemoveLegalHoldSettingsRequest { rmlhsrPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "7\ESCA\100751\&1\61326\&6=}:rs9$V\161942\ETX\157612p\1055504\980)\v\1065642L/D\1053395r\1002233\ACK\1087608\988791cK\SYNv\31472\1028619jM\ACK\78241ir\EOT\1011122\128841\1050018apqj3\994355H%y:b\1052537\DLE\71447V\SUBW{0\DC2-8\1036129\148518\SOEK\1016167\53526w\1009246feg2\150694\SI@4K\1096766u\DC4]\GS7\1081799|\ETB:\1085310\SUBPIr\b\2078\83313\SO\ESCA/\1101070}\136410%D\a0`\DC2o_\1007147\DC2u\1894En\f$\DC2{X\STX\",\157022;\190579\&0" ) } @@ -36,7 +36,7 @@ testObject_RemoveLegalHoldSettingsRequest_team_2 = RemoveLegalHoldSettingsRequest { rmlhsrPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\999579y0Wdy\51475\vec\19247P^!iU\f6i\FSPaP\149302c#\37611\189955\11423&!\1022184\1027449[XDU\ESCD\188997\b\ETX\NUL&kN\1047564\DC4us\1103907\SI\984907\53683,/\DLE\1099195\SOH8\\bM\1063487\35029\1104897]b\50833N0\1033891B\"4\1063964r\185996.\RS\1012529+\RSi0\a&\SI^\ETX/\NAKj\EMu7?>\1070946z\ENQx\139660\182371\39174\994357p`#<$\189186r\US\CAN\1094236\SIax\1055690\GS\1084221\b\1028820(\42097\SO5n\50432\&3.\177366W)\1067411\146847V\163238<\127547\&7u3\49906>rG\1013702\NAK\b\98747\992389d\16934N5\DEL\49009|\ETB\1000873\1013833\124949N\64373\vK\DLE\\f\8954e\12706Wt}t;v\ETXh5\ESC\FS\1052726^hjf\\\GS\186799_\1109588G\a,\SOH\128188\1006718a\r\SI[\SO\158480y\CANjVs\1090994Ii\20997i\ENQ\1062132\139823${\155387\&2\RS:\1001405\1053917\SYN\FS*@$_@f<\NUL\1008123_S8M\rw\20774%_|o=c0<\1017362r \997013\STXsy{n9\\\100387N\62724~0d$\1091724\120642\13100\DC1\1069778O\1019418\EOT\DC1\ENQlB!\v\1109842\171583\&3W>b\187932I\1101337\167182}xq\1087745b\11335V*[T\bv\f\83258\28783(d2l\EOTRL\EM\1089605\985155@ij\SO\71326n\34883 %'\ETB\1039928.\131212.\1039356\1249\&1$e\ESC\49043T~\153836\n\ACK\EOT\986971\144256Q\983788\\\ENQ\FS$;k=4dKt\re1e 0z\1012474\50175cl\58680[U\ETBG\40261=|\149785\1044808Ls$\SOH\992686\SOj\FS5\tb\"\bHp\48604Byn\1075408NQ\988598\EOTJ\b\SUB\ret\STX|\1040809&\CANU\SI;}_\168740T\98265$f\156216\"1a\137875V=\ENQ\1046966\&1b\1024171`\f\165395-\1039302\nT4;P\166390?7~,\1047860\1080742L,g 0\rA\1051754E\DC1\1040023\EOT\994020I\47187<&y\992553\54040?48'Qc\33996\786\b\22011\1010084`\21628\SOH1\n|\bK\SOH8\54633^K\182179*\34930K}mE\146114DP\175139\1007231-Cp_=hxz\FS\1113279!{\"\985558\SO-\1068034\&1J]N\USd\83482O4\1100385\RSV\1095656_\1032300ZX9pr}v8AW\1006177jg{\t)j\39203\US:\135288$\35831\&5$\46159l\1054716\1052270\DC3\DC2\FS\ENQ\SO\SI2Blpz\1079083\984969l\14968\1798\1064052D\\ \998260)\1001286\35749\1112300\DC4\SOH#\158054^\1043739\1008452\CANC2=3\6199]\nj\17671{S~mPuD|e\1074639{\31835;l\DC2O\f$\DEL\CAN\ETX&yry\aS\1110792)\SYNm`\US\1029364PB\22035g\1647\r\ETX \1102921\&1\tpC\1092242'q1\\\1095012r-@7\31207_\1079211\1085445wU\35553=L[A\fes\1004173\171080\137197\157687BX\\\ETB\ACK\1015195A?\NULL\aoB=on9a\152126\vrpm\36211<\DC1\20837\r\1029722\&0\\\40325+JW\1070047F!$\180822&\1024360\1026817\SYN\SUB\71266\ACK<\1073038h4\r\DEL\168269A\61311\v\176834wX\SO\51661S\DC2[S\USL\171186;id\1020361\38712\73081" ) } @@ -46,7 +46,7 @@ testObject_RemoveLegalHoldSettingsRequest_team_3 = RemoveLegalHoldSettingsRequest { rmlhsrPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "c\GSW\ETBQ;\NAKc\1092152V/\SO\160271\&7\155128jBd\EOT\137953b~\DLEsL \f\ESC\1048458Ez\1054067*\CAN\132814\RS9\SI4Bt\GSm\v<\f\1097168\ETX?5\SI\4815\97255\&3}\57927\151250\SUB\17900G7t\1096238\DC2\185177\t/wKm\EM\36927\1035925\1101822D\983622y55]*.\DC2)\1105128Z\1062108\1015388\r\1085459S\1946-\STX/#iW\1007334\rH=\177941\ETXG?\161972m0u\tm\DLE\1018175ga\EOTpCc\n\ENQ&\ETB!\160936I\132466\1108763\NULX\ACK\CANj\SIG21mR\151321\1092561 WCJ\1072763^\181130\&5\SO:.,\ENQ\99222\37567}n\188691\NUL/\GS\20891S@.\ETBR\f\1030960\142653Q\DLE\188259o@@\SOH=~\SYNQ\51875\175795\t\DEL9\NAKq\1080795}\RS\ENQ\171940xm'De<0\191178\b\186764R\1076539\&3\95570\169204\1076350p\EM\NAK\119301\54590\DC4|z\r\1031243\185318R\FSd{\127979\GSv;\17289VW\132040\ENQ\DEL$[\1029648\152045\51738\&2\161674X7\CAN\994559zB\1035459\154679\1031826&i\1113726/a\59989\ENQ\1043872\SI%U\135901!wzs\rJ^\"\1078279JU\77973\187257L\NUL\CAN\STX\1054173\1098874zE\140557\ETX\1101586$\1049695\&8\992442\142426\"\EOT\SYNd\v$\t\1028650Oj\SI\NULX\CAN\SUB\1008936\&5\1070826B>s-\995151\10168\1077198\1084647m.\1077978\38727K_\27138\f\45094]L\SUB\DC3 \1027351$#\1063691\RS\983599\ETBu\ETX?-\1092427{%\FSy\NAKY'b'\1011354-gXI4\SYN\156615\bJ6SelHMS\1016044|X:\EM*\ETB2\1083812@W\1052280\SO\997326\1108978z!q8S\DC2\63581\1062931\NAKB\1045022\&7\1000944KdI\\\STXd\52994s\ENQ\1002201\v4j\1007254\38332\&9h\\\\Pa\1070667+!Uf\NAKcra\1045334&\EOT`g\t\vqp\DC4y\SI\1039426s\ACK\178956\156538Jg\NAKH\STXWs\t-8.\1093880\tolC\756N|\f\1007942\ESCmp$\62080f\nL\n\1081011\28013\DC1UZ\DLE[%*/\SOH\CANm(\1036970}\1021617\133748\1074676\b\96687\990124{&A)\DC3\995685?\DEL/\148584\1081914\SO\GSs\t\DC1<\1048062\146700\163816k)Q\ENQ\SIp\GSvP1#b\159823\1023136\1059867Sp|\ry\134836\137333:0kk\SI \r\62115N\34525" ) } @@ -56,7 +56,7 @@ testObject_RemoveLegalHoldSettingsRequest_team_4 = RemoveLegalHoldSettingsRequest { rmlhsrPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "r\DC2*)&G\1053857;J*@\95575X\1050074O\\\SYN\DC2\US\165924UM8i(\ETBU\US5@&\1096083\8038\&4sn\999077\f\1095608G!\n\1007645\1035248\1105159\1100017\DC3,\1015268\182360Ey\14281+P[\1090694'Ph\al5i\121215\184260Ps\39822j,\12016\&1y*Wfw+\ETX8}7M\1076016H\STXAI=[*J3/_oO8~-TKw\152255\fK8Q\1040222\GS\th\1058111%J~b\RS\1110378Y\134678P?\188759\996137B5R\SOH9\SI\1081422\183507\SOm\STX_o\8362\131089\ETBSd\1018318\&6V$\1025876)\SUBM\ACK>p\1114082NGPV\98881\"\"@CeM\985873\rVnDwsxT?\1037164P\GSO^\SI^%e\1101462fTP\1040442f}*\983229\126225l\t\1062332;=\1061486$C\1065617}\GSn\ETXqs\1029013y%\SOHCe_<2.\b)\1022503\NAK}o\1108074J+L\r\DC1KR\vD1G\SI\1109958\STX\"]tq$i\1102846\ETX\ETB:\NAKrd D\n/\EOT\NUL\f\RSI\145600\41671,/5\2704\DC1$\35374\1042495KBU\EOTr\995189p+v[\31521\GS(i\1064032_/\GS(\37111tG/Js\1613v7\189231\DC3Ib \EM\a\ETBm\83346\&0Kj\1016188\141198lfF\998983\18139KJ\USN\SO\STX:\SUB!\US\1061023L%\ENQ-VAx\RSAdM<\t+\1027617'\47144\1108473\1050994\&1\SI\ETBC@r\167037!Y%\97255\&9x\1110422eqLV\CANZ\161903P\45599\1112903\1110855^$\180197\1016744\186304X\SUBv\1096229\n\SUB\1007369C~\NAK\1001754\145288\1022575\1015155" ) } @@ -66,7 +66,7 @@ testObject_RemoveLegalHoldSettingsRequest_team_5 = RemoveLegalHoldSettingsRequest { rmlhsrPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "{?)L\25802UM\64672zX@\992636^9\12432\&8aT.J\1019056z\999863_R\CANTqpG\3653\a\1006213\1100287\EM\DC2\15459t\SUBw\ETB{\SI\78545=\180643;z*\ACK\f9,\ETBg\1082603o\1061090\16929\ESC\ETXo\2271@:\6586\1053552\&2b\SUB\SIMt\FSN\22082\&2b5.?KL\a!\1108965,\SUB:\EOT3gm\n\DLE3Z \27424u\RS(z\t\1033433\158665#\fS%\917781R/4\n\1088586\ACK\\\GSL\DC4\94539f^\DC3f\1098347\SI\77848@\78399)\983831?v\n\a\149912L\NAK\991909\148012v\1004006\&9~GB:\"K\ACKr_\CANw(?\135765ugI\19568\ETB\1086808\ENQw\SO=z\141169\22894/\52795'j\994082\&8h\70113`\EOT\175793\t,@\185852\DC3$/(s\GS\DC1_MkVJ\179123\156608\1031581 I\\F.Kt\179855\1017781\a\1070416\EMd\DC4\1081719\39332\SI\FS\v/\DEL\SYNe\988741#d\28303\f5\DC3D\168058\50372\NULEie\USc\ENQRZl\1025046\ETB\1072153`\DC2\1087409D\17955@Q\160608\156833\1049870\ETXvp\DC4eN\1070535@8(\15013z/f\t\1113434!N\986349-j\10785\176611Nd(v#\1016064\STXP\1089505&u\v\100842\17873xs?\1061507J_0H\1108743\EMR\1069579\159709\147516sw*\53545>kMXA}%;\167116\138362\SO\ESC\1105972\tGZz,\97467\986544nE\995106\DC4#\\8v\73922\1093206\190830,9u79Z\58798W\ESC'\14038OEx\nY;\177292P^Zu]JnZq(=XD$>g\EOTm\1033200Z!(S]\SI{\ETB5|\1097012q\DEL\r;\STXw\991333\SO\&Hesy,\1015028\&9\98800\&7\180066#\1028606\1065514\1112443\DC2#\62203'\NAK\ETX5\983877P\186402\NUL|,\128783'S\50667\177675\DLEym=\190603\987654\52829\US\"r\1021595VS/H\SYN\33925a!^%\5813PW`\RS\DLEL?\a!\167876Q!W\bvi*\32973\US!n+\66434\SUBo+\160062\155912\EOT5G\142535\b[xT> \59625*\83023\1092602\184231x13g\EOT/\180644\CAN\r\1083750\1009186Ps;dxR\ETX!\1110043\45743v3\1098241\NUL\128961x8\FST*\nlZ\44209On^+Yk\1101997\163684/!$|\ESC}BE\1088092mr#n\95884\1085948P\155396I;\1105131\ACK\DLE\EMj&I1ZF%&U\NUL\992363\RSZy\SIOr\40551`0vt\SI~\GS\1025982\FS;\SOH<\1034854\1095780\168010\187153\EOT\1108056\n\995376y\171303\DC4\ENQ\1069057\74165!+y\142232z\1042242\120387|J\1096019/\154606\DC3<\SUB\a\27008\185804\ESCY\156373:y\ACKUR\1069946o.\1060110T\28671\NAK\110706\31445^\a\1076389\DC4\f\157950:$\1064077He.\r{\GS}\1033193_o\RSn\95449\1099867\nv\69457\SO\b\1046600\1070295\SO\174282\1040684\1058242le\STX'@Z\1057986opC^#@\1093574\DC2vw\47426\ETB\149186x\1015802\ENQw\97338.\990357\ACKGfD\a2)\1033604-\"#\157823k\DC1ZH\1055500x\1069715+\DLEn\1062602\&6b'\r\bW\1078324\ESC\DC3dE7j\NAK\ACK3\1028321\983698y\44844P.U\829\1039362_\986159\96304\149099S\1022552y*O\30764Rh\DC4_?\NAKg\CAN|'\134474-\1031374\CAN" ) } @@ -79,7 +79,7 @@ testObject_RemoveLegalHoldSettingsRequest_team_7 = RemoveLegalHoldSettingsRequest { rmlhsrPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "8\917619Cdq&%=nR\1110638\1055303\149699fiZ\ETX8f\ESC.\1050774C\1101255\EMQ\\i\DC2\US\72868\v\GS\FS\1086778sR\1007330^R@>\NULo\CAN\v)\DLEn!+h\bq7\990975\&8\DC2(\1080785\&565>se\SYN\ESCem B\993914[gCt:\1055911KA%uI\ETB} \1058737'\150411%.\b\172172C\1033707\989780w5\1099667\187381Y\RS\175935\990543P8NV3\38566\tU\1015543\169518M\1047448Q\1029898\r\1061840X\n8?\ETX\1072798\SO2\178885X\b\ACKN\"~]\EOT&\173881\137464\b\n;\1050745\94862\140424\DC3\\r\1047436\1075597D\989258f\SOH*\US4yw\147422\b \77941\1067679j\v\vn\1001680c+\131965\&2\1059714}U\"6s(D}\1005532\DC4\38889\171881\GS\1020663}\44434k]}\1043550>\ACK\ESC\1108390\37207\57564^\ESC\n\1033775\1051019\1033216\999985O.\31085\NUL!F`I\20832^\EOT\138661\1025512\n%:\128095\155245LN\43825+U\1055164V\EM\1011007iV!\1105376O\DC1\NAKI%\1048563D\RS\163226:R\1087161\DEL\ESC\992741_\37899hN\DC28\ETB{b7@V\r0u/\DC3\SIWa\CAN\120160\1022537\&1\169421u\156843I\ACK\">\ETB\73979\1082202q/0\1030181\f,\tBgVI\993493 \136884>\NULN`+p\USAhDY\GS\FSE\20671\ENQD\EOT\FS\31424\EOT\1061520\US\178077p\1010328 }3\22553\1061173&BwNC \1055961\176871\1109798\1020684\1677\STX$\1072535D\1027971)R\1031874>.\29514|C\987424\\M\1027121\ETB\1019672B]1\1102976\988016<5GT>1\119555|\DC3I,\FS\NUL\990467\1105476I\ETXC\USZ\f[;(&)\1022803\41542E&(n%\ACK\38374|I\96679\DC2\1095000eI#\SYNA~F\ETX\US\1017597\&6Q\1047292\SOHl&\ACKz5[lG\1053367\&4\167678Sq\53843nXl=\1027767\1031174\188465\nn21gb\CANYL\nYh\t\1027960\12470\1054880\1016629\169928\1098937Vx\fu\1006383H:\nK\DC3\35165\SOH\71902\1036223UmN\GS\99315\39374u@ \162412QV=\160772*" ) } @@ -89,7 +89,7 @@ testObject_RemoveLegalHoldSettingsRequest_team_8 = RemoveLegalHoldSettingsRequest { rmlhsrPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "W\1027044D\DC3]\GS!ZdpC6z\987851#U_xU\ETB\45092&iLDN\1076087hy%w\US\1029210\161035 \FSmz\993708z\63492m\FS-h\65784l{`\1011756\1048450\1023578@\138105f\183906\SOHe\GS\1004321\\%} \7215\34068R\CAN\1002857\1112772\f\f2I\\\158490CZ\1052\"\1028229g^\ENQ\CAN\162623z8\1084276\a~\DC3u*>\RS\f\NAK^@\100227\SYN+\NAK\2810\EM\DC3\132847\CANP\1059469t\NAKV)*_u$\133398\1026138\41180\151817.}Y\DC1\ETX\CAN&8yog\994847%\1049357\SI\SUB\143816M\11176@\992249\SO*\NAKz#\184431\DC2\1001396O\nl\155612\EOT\DC4\128323\b\992452\"\a \ETB\1097925\"X_\1008525\146798\DC1\SOH\50638O\1065150\44863\1025201n~\1048993iZ\180715\&4d\38798\&9V\1046104\EOTID\ACK\22198XJ\ESCF$\165039d\RS<\DEL{\1002709\13176\63311\151346UE[\991657\986212\EOT-Z#\985137TO^'n\165897D\57735\126616\NUL\1020419.uV\CAN`\175678Y\ACKa$\ETBw\176988m\STX+>\1021464Ao\42837!ju\131675v;n1e\53273 >k\136838?\fZ;\CAN\ESCI]0\t \1040746@\STX:\EM\tX\DLE\1037079\1107089'\ENQg\11853\DC4)S\SOkc>24\DEL\991986\SOH\SOHF\35465\1036624\a\SUB2?`T\ENQ\1084951\"FT0ef0q\16987\ACKR]Q\1058109\181469A\152914\52655W\1040166\139253\b{%\12513WX\1088542w@\GS\183846\&3\29305\1030326\US\ESCCJY\1050533`\16520\&3\120547\96399g\1033985Td\NAK\1092768\986684o\1106360\DC1\162963C\161982\53829\\\1023335\40117\152967\&2E%\1069276\51958\bk_\131376\169967\ACK\984510\190314\1060026n\vK]v\1076069T\1067945\rr\1028209{\f;|u'e\ENQ]H\94868U\ENQ\1027557Iv\1012004%[\153532t\SUB{w\US>r\1071084V4^-Y\n\183365|\DC1+\ESC\172042H\46455\US#\DLE\DC3\SO\&H\1026181<2)^LJ\FS\CANGn\NAK\USra\SOH)\98526\DC4\ETXBM\144239\EOTM%\DC4\ai>Y\b\27873\a\1317a\1041008{g\CAN\1108292h\98189\68311N\40841n\EM\1018413=R\fUR\NAK]\RSJ-.*:8K\ACK\SOT\1042415\\;#\988210\1089576f$+\177913jM\v\DC3\17823\SI\1027022X\27666`H<\1072977^\DEL\78146W\SO\986424J\62484V\t\1594W9~\24782H\SUB\FSvnD\rBsT8\1103500\SO\SUB-8\STXhi\SI\44017\20762\&1\1027338\989455\1055036f\996369j,c\CAN!\5696\&1\DC4yL\178284_\GS\"R!Rse\142338FD\1110906\1063851\158344z\SI^A\153132" ) } @@ -99,7 +99,7 @@ testObject_RemoveLegalHoldSettingsRequest_team_9 = RemoveLegalHoldSettingsRequest { rmlhsrPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "IJ7!\rkz \EOT+\142128\1056706\SUBf&\25801\1059911\EOTS\142096u\FS\SUB9[\20878W_\r$\1079900W\7858\&9\DLEWx? ?o\63066w\NUL{\NAK\ACKXg@\1027235=\RSE1rGg\176246\ACKu\ETBi{!2\7781n\23031dRt\140807\RSr#9o\1113360\FSMNkT\27505}\181370J\ACKqS\DC1\r63m9\99742\SO\998070PP~\ao\1011501\150668'\GS,\NULE&\DC1\STXkoe\\\989929\1111210u\DC4\1044044Z\126491i2\18253Y\EM\\\1097085\NAK\145241{\bGq\140099Y\DC3w?z\1051912\GS\165176\ETB[\ESC/\10155Q\STXK\998991\&2\1088451;[\41615\24252\92586\15944a\61862ix\1008891Z\1105253z\53313\38885Fl\ETXI\1025678\vhG\NAK)63N\1089246N\nw^\1066876\51485\1058993\SOH\ACKy\ACK\n0H$Bw\NAKT;W%\175509\ENQ\991419\SOF\SOH\EM7hi+e^t\DELr,t\983950\1058676{.\1005232>-\FS`K4\v\STX\SYNB\DLEx&\1091965[l\EM\1008679ifg\1000051\SUB2\1113074TJvks\175142\160649\NAK\DC2~\DC4\10031\1100483z5\65513\157303.o|}3x\143424Brk\73072\&8\53476 \1018222,p%}>\SUB\1106622R\EM%De0\19530-\US\158947B^4\ESCX\1038547~\"AQ\NUL\1113650nus&i\65560\&8Qi_\NAK&A\146017\&1\1020275\1042585\NUL\25179)\FS69\95944\b=^\SYN\DC3$TXY\ETXorV\1068341\SO%f\1008134~\152796\995140\&8\33609[i\178523\ETX\1076408\1052411=\16652\1014162L\1065202\53190sh\191047^\tH>q/\SII-\994947v%7[\STX\1114070y\SO\63687@E\128584\61085bu\164343\t\39354\&5Pc\95768$\EOTB\137234\1016652qV-\DC1D\"\SOH5\187802`\57760\DC1hh\30089{[l\DLEB\1068518|\1099501\v\1101071\RSp\993974\f*iJG\NAK}-\185450O\CANdL\ab \1032057+Kj\133645Wc~~m|h3\83248}B\DELZt\25276b\FSD.v\164031F\ETB dj\118836\"\DC2" ) } @@ -112,7 +112,7 @@ testObject_RemoveLegalHoldSettingsRequest_team_11 = RemoveLegalHoldSettingsRequest { rmlhsrPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "s*\ay\\+\986674\DEL\136338\&4\DC3Zc\DC2>5\96406\141976V:\154635\n(6\SOH\61419\&9\SUB\1034500\33504#\1089524g'\1077925p'?9\RSb\990721\11486\68080\SO\49122\&3z;\SIT3\DC2{)wJ\DEL[,\ETB<#\164941\ETB[KN\v`Y6P\ACK\23941G\GS\r_/X*!w\1010836*s\1011343#\1021717l?6j5\rxabx\36942\99574\ro\SUB\1027833\1094612Fy\1108218\166900A\SOH}\SUB\RS\151899Sj\173344\1103300\NAK)f\1097051\&8HsF2\NAK\174775T\SUB\STXTYO\DC3~\v\1072068\DLEq\SUBpt m\ETBg6z\160314\138260STgX\DC23Vv^N\1059838\64641Y\RSj.ZM`P1>\1016285y\175443\163910Wa\1052736\54721b\ETB\154584\159189\153739)}\178838+\1017985\&0ky!~\994946\34726\&9P\149081\ETXQc\DC2E\63873<*o\f\NUL\149488W\35007\SUB\998549a1\"{\2712\ETX(3\984836`\23679\a8F\1014953k2\190594\DC48uA'[lBq`\135799fy\n!nAM\DEL M\\$\1019576\139801B7=\159840\139319t^\"<\n\157525\DC3\1017319+l_cd\32065. xmI\1018813l\EOTE\1102490mFH`\a\991906\143801Iw\FSC\FS[\1109397\ETBC(8^o\ENQ$\\FS\SO\62254\"\v\991208FP5'\SUB\GS\1001736\1076879=\SI\DC1\DLEQ\57540/{\DLE#\ACK@e@\1005284\157943>C\r6)f\ETX}\998458M}\SINF\FS\1073196Ge\140412A\STX\DC1yM13t6\FS\24617\&96\SOHT\1028149\&4\169020QH%P\7746PEW\132872\f\GS\21246VM\60371x\170489.2H\a\1036793\175185\1028909\DC47Jn\EMir\1114085\&5\1049445'\999745\ETB\98134S\31838\DC2\1063672E*|\62948\SO^<\127394;\b+Au\f\DLE\1001990KD\83124J0\SUB%\ETBV\DC1J<\18643G\GS\DC3h\1085375,\ESC\1015078\"\1019703,KA[\144537\SOH%k\DEL\77972p\DC2\20550\&0\158880G\DC4_We;\92296\93789\STX+sZ\DC3Lc\143154\ENQtm\1062022NS" ) } @@ -125,7 +125,7 @@ testObject_RemoveLegalHoldSettingsRequest_team_13 = RemoveLegalHoldSettingsRequest { rmlhsrPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\v^O\50461\1064372\95098\NAK\FSwZ\1069249\DLE\1113028\NAK\13978??\1079861\&1}\170083B`v\1077470;2_a\6681\155189>\985302\1095030\STX\168236b\ETB#\1313S\83008\1008377t\SUB]\139691kw=L\1048596c9\184049\STX\11073b?|~\f\1078424\DEL\995344\&6O\138744M\1099781k\1049976Vp\1102121I)ds\ESC#\ENQ\1005753Z=L\34894\35694=G%\24942r\48420\STX\1068097^[Y-\a~(0\1001889U(\167279)\995137jcv\136362\FS(\ACK.\f\1091001\1044703\&6\983379\23485p\1053544|\SI`R\1022311\25792wP{\NULGV\3147\170703\1077935qS/\49822\&8\45096\1112061(\41791{S\SI?!R\188917\DC19}\CANnY;\180971\DC4|\1097335,\1114035r\995582\159285\99992\1071329\SYN\t\ETX#1\997719u?nm0\1110310z\46192W\r\SOc\99397\1097011iDu\ESC+\rp\1041137=Os\1056446Gz\6043H!+d\US\tu/nM\DC3N2\v\1088691\152048\57471%\18462\DC16\54844\1083625N\995101\1004758Q\121479\&7\DC1\ETX|\1032204.bk\r qWp;\61324\CAN_RO5Ss)\NUL\SOH)\ETB\RS\1012942U&9\94757\&3{o.\1069082G0\1019794\166873PB\141238\CANJO\NUL\ESC\DEL\SI.\SUBH5P^QFID\b\1074645\ETB\164249\SOH\35183\a\1059938No\41798AO\1022401\DC1B\ESC\1104156J\bGS&4wGm#>\188958-\1104112z\ETX\1028604\RSM\24756\&9\990746\SI%mq;\CAN\1082623\STX7v\1065240LW\ETX\CAN\n\1079402\ESC\33242kC\1011832\1008221\1062188bvp\RS\NUL{USyk\ACKMF\ESC\1109278\19305\154642kM:\SOHGv\t\f\1061497u8@\986396{\SI\SUB\44951>(\FSIw~\19276\1034127\1052236\t\989552H?\188852S[m\3427_\n,#\1071372\1047255\22694\55248gG6A3\163385;\1099269\40533\&7" ) } @@ -135,7 +135,7 @@ testObject_RemoveLegalHoldSettingsRequest_team_14 = RemoveLegalHoldSettingsRequest { rmlhsrPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\DC4T\58452;J;F\1002690H\2733%\DC40F!\158859q2{\42227dX\CANF\STXM8\138885\&6N\173645\b\1007366s<6\996892\1079003\187369>I\GS\1024008~\1090673\rg%jZ\a\1011076\175806\\S\DC3\133203Oc`n\134495MFp:2\1027836\999130S\DLE\150620\&2lp@\EM\917801)\CAN$*FM\NUL#\1101166\1100085>$\13113\28241\ACK\1033354\1040056\35349\1011007\1019260\SUBETJpCX\1006180\DEL\126254\1061887q\154921\a*\USoj@\1067901\DC3d\ESC\1020431;\ESC4\vP\1001569\CAN\n\GSN\51764\52267\163635j\aXYR:y\63244\994786_VG\1020199\ETB7\1069726Yl`\DC2T7\SO\SOH\1065787_s\RS\ETXySD\"{\DC23\151575Hh\ETX\f\1015245\1058018?8\1029420\8108vN\1018170\&6Z\1102119AWD}Kk\92736\GSQX6\ENQ\1002065cCi\niB-\1075655LkdM\\\ACK\189357\&4kd]\ETX\1079564\1045877|\DC1\a\DC3\1101782U%cm\SI|\44643\ETXjH\141858V1\v\td&FW\1013409\NAK\125009\995344\1001421\1008044B~i.5\58447\1028499\"Z~Y\STXrV\20576\1078615\ESC\DC1\RS\CANZaM?F[Bjm2T\145505y\"\1003309b.\1106545,_){w\"C&rG\ETBg@7\1057996\a\164612\CAN\7323[7~{\EM\154458\\ s\US!l\67697b'q\FSoSm@}\21388~{L(\1034663Ik\179579\1100468i{rk\992813\1016927=d\1026922\1098660\137265n4\153804l\FSn\133356\DC4!\1063664\&7\US-\DC3\1041592en]\DC4A`\30775\DLE6\1056517Hl\t\1105563\ACK\SOH\GS6:EjI+\146885{\DELNf/s\179098\GSti\\\98889\DC19eUu`H\NUL\ETBxO\1104547oM\DC39(\DLEk(|9/\DC1\138868,\1089850:\SOH\40278@/.sLi\ACK\128363\FS\1025516\1104052\146486\DC4\DC3?f\NUL|]Z&gx\SUB\43217)i1|;\987185\&1s\ENQ\98182\46528x>;\1091524\174283WR&8\nL\234OT\v~+\SUB\188704zOq\NUL\1064845l$\1103166}\"\ENQ\1004026}\1061257|\rzn\USdTNomHt\ACK\SUB<\ESCt\129307\129375\&1b\1103835\94858g\DC1<\12125\&7<0\37222\STXPZw\8111 \1064395D)Z{\48509\NAK\SYN\59397Cz\100275V\183928\1080734I\48573\US\f\1057149\DC2\DEL\DC2\ESC!}Tr\ENQ3z9\t\EMF\987155~q\ETBh\49229\23913\ESC1\GS\1101417\154972h.\1043722X)\RS]\ENQ=e\NULA\44331c_\1078600\988962\SYN`\DLEt[V\"3\181546\&7#c\983257\DC2)0B7\1078051\1094716M=&.f\US\8896#h\189650\1639\&9N@T%#!\220\&7\34533\vk6@\EOT\ENQf\12332\183654\182533j:{wm\163368\1054065\73743U\FS5\19217\1079616(\1049518oA\1015055!\177539_UNyG_e\STX\DEL^\SO\EOT)Hc\138194\60442~\93841=S\60578Z\1042634x\1101732`\1000240\64468\169401\DC2\ETXfM\SOHp;,\GShfbt\RSB\1110269\9142\997637d\93976\EM\120429\1028113\DEL~m\1018843)X\172894(Z\179509\DC4\b\EOT\DC4\1095562\1050808-8T\ACK/o\SI;\141316MLW\1016518\&0yjGd\DC29\EOT\996044\&4\SOHM|\b\EM\SI7SF\\8\41458dBJ\1008028\&7D%F+\39794\163900\a\988945>\1012031=S#K\1091447\74401\33635\93785\1064621f\1037974d.\1004109\1032805\v\50934\1033224p{`ZJr\a\46011c\US&\US^\DELC\DC1\RS\12913N3i&\EM\t#\DC4\NULhoj\989041\t\NAK\141272\1076868\16551t\EOTU^a\1001119\RS\DC4\48375\999927wh\1066750\EOTpy\b\EM.%5\DC1G7\42632\&2W\22114\83487?S\fKf\187295\EOTC#_v\USU\DC3\v\1031420Xu\41003y\994940\1022930\98959`a{+\161399bO\17116S\b\CAN\f\1027109&\DC4\158466\&5\50683Mgz.\DLE- w\1110940#\1070691b\1017001\SI3\DC3O\ETB\162744O{>q\1087329@]\ETX\ETX\f\989735[)\1014259\145631n\n\1061560\126543\163970yI[y}*7Gs\SOHz.\1082844\1057834I\7444\&03\NAK<](1sQ\26924\46008\1005711}-QL&p\51879\NUL4P5\NUL\r6\1020813\SYN{\18195I\174908\148029\1012747F?\NULc\DC3x\GS#`\19940d2\137237>\1013909'\62537\1004767`\SYN\DLEAEU\ETX,I)\1098057\168144\"\164849\FS\fN\rZ\DC2j\EOT\186373d\165581\&4\DEL\FS\nM\ESC/\54397r\1034027\179760A\US(Hsu\SOH`$\SOq{~F\RS\DEL\169370^\63120T\1063256\164794a\USfN[Y\ENQ\2824\EMW/\EMW\36125\1051304\143732fC+\134327_\995496_\174246^{Q8\STX,\1021842oCG\n\ETBB_(\nB\ESC\1082631\17506W,\1081763$\t\ENQE\119200>\164564-\179944\988620l\92564\1055566W\1086126)BZB\1087461\1073556\CANiP\SOr>+\RSo}m\1034255-\ESCen\171939\153785{}X7e\9207\1077819t\1090950\DC4eH\ETXZ\"b\1037281\82983$\ACK\29799\177644\&8\1015209O1[=p{|\US\1036483b\ACKJ\DC1I\vk\SYN\v\SYNF\SOHCp\ESC8?\RSH\128862rs\ACK\1094451\a\RSgD\30758zG[3\1017725\&7j\135163\SUBfl\127041IY\EM\vt\RS\DLEg+S\a>\DC1\174387\&7\bvnbA\17623\SOc\175703\1071578\SI\176239\71907\1106333\&1\13449k\59507lS D,\1599>\110999+\ETX/\989875\95432\755+h\SO\DC1^(\188945j1oVY~\1035894\156359\&7)\23212\\`\165892\SO\ESC\"<\1017602\755\ESC+\39135d{XeD\121176\1039978L\98779\STXU\b\1053044qw\\\1052340I|s\1045739\SUB*L\190144&o\ENQ_RS0\83074\1055324\1010402e@\984615>\1041636U\1048724\983614m\1080090\&0\GS\RS^dYE\158395\14423n\DC4+\f?\95557.\v\149628\987252\EOT\\\SOHM\DC2D\168773lW:U\152013>\26643\187275\129078YS^]:\GS;t\131568N\ENQ\r\CANlm\183209bdqo4p\n9f\ACK\EM}Zc}}p07Q-\n{\164911z=\"/\1047832\1078452;m\128795_\94968\&3\1070646`\t@A\55021rr0I\"\RS\1099881\1049867S\ESC1c\1918\&9Y\GSW\1050691\STXd\1006078\29527Tk\137827P\138958\rwk\US11 \\\191404I\51585\ENQ7\29990+M8T\1021112\10771\DEL\ETXL\128121\68423Jo;\SOv\1063077k\f\65257ex\SYN! \1080242j\GS&C\1098184\168608\RS\ESC\151019~+>=I{\7092\&5\181425H[o<\STX?\131425\1036414\165849\1025986E\1052092+a_ULd\n\1044210n\1110895(g%\r\1006641&Z\ESC\157625dtm*\1108463^zp\164541g\b\173320w`\SYN9\EOTF\EMq\CAN\1099969\1033310e\NAKB\ETBgO\r\f\1074139\EOT\1112089\"}\n\SYN\1013741{}Br\ETXe\174564U\GSP\DLEr\983601\185507S^\144788w\41049 m\128627l=\RS\161254|\1042723#)#\40130$g\ETX\EOT)\54412c\1075808C\59580\996027[\47540\v\1025121\b6EP|L&\v\65927WT#\69238*~\NUL\63826C\1103743&E\1042001\DC3|\1083210@\173066}\SUBLdi?\f^\ACKBFi\95060/{\50371zL=\CAN\a_m\DELd\US~\SIWL\GS;+\996005\1055094\1099579H`Do\165099y\159209Xw\SYN\1093387\ENQ\DC3\186017\178377/\38176C\1113933Wq25!{L\DLEu!D\12167\&7N\121329ng@P!\139900`\ENQ3\DEL\SI;\985525gl %2n\13525\149987;O0\1046990D\134989R" ) } @@ -155,7 +155,7 @@ testObject_RemoveLegalHoldSettingsRequest_team_16 = RemoveLegalHoldSettingsRequest { rmlhsrPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\994004\bm~\"\DC1\176123\1065324\FS\9077\160047\183950\DLE\SYN&X\DC1\161475Z\1001388\DC1q\ETBR8\ETB\DC39\1111364:\1040389p\f\vZ\95166YH<6\47637t4yJRg\139192\1106081%\SUBG\159714[-G\SI\99804:\1026260?EO\185439+\1042856I@s+<6\1019316\1044623\44397\7439\8848i5,.\STX\1028073\134139ny\a5NL\DC1Y\FS\SUBa\DC4\1097282\t\1067984/P:l9<\t\1090201r\ESC\1081986!\ESC\182109\&38\995184*]IW\1046988\25116V]t.s=fm}/\STXBt\RS\10019^\98542\ETX\SO\139106\SUB\1030136^\ETXo\23588(\fnq\ETX\ETB[\GS\SOx\US\SI\136339^,O)\DC1c[;cp" ) } @@ -171,7 +171,7 @@ testObject_RemoveLegalHoldSettingsRequest_team_19 = RemoveLegalHoldSettingsRequest { rmlhsrPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "N\991963p\DC1#\US\"Wj\f\1020567faJ\1071697nM\182498U?5<&\59098p9P[\151120\\t\DLEW\DLE\1074276OBH2|C\13518~M\133945\DEL\1026105\&5\DC3\f\994157}\STXH.\167503b\998185f\STXC\1058334\1027769\1002026|\US1Lm\FS\31880)\v\SOH\DLE7\CAN\986637!v5XM\ACK\42336C\r1(H\r\tzQ|rxmn\1078242HS\DC3\DLEnj?\1063896\NULcqW\125220n\165653\20584\&1\f\168538\r \18740D\18728\FS~4d[r\STX\1009407lU\983746HP!E>fvW\1087437)YA~\120418wr\DC11\SOH\180704\67890\&1\t\NULT\149064q\DEL^=\139822\&9\1105051JY\1059472\43618\&0\SUBa\28981\RSf\997324[0%0F(*\1036823d\r\1036095\nF\bg=\DC4\1110474\28747r\ENQ#)Y\143438cJ\SUB\185185\995287MtJ\DC1(\144867\163712FJ6^\FSnm\169734Mm\1039075\&5|\1052919\a:Fy\1087830\62571Mdt\46362>\1034685WK\SUBj?\US91\FSa}\120448\37503\&3\ENQ$O\SYN;FL\US$\62508\1054807Jue\r\DLEkG[!|f>\EM\ACK\EOT\b\1072044]Ojj\"c\FS\1005813\162032*\SII:\ETB%\15731\5795`L\r>\SI\39297\1113095%\ETXM_\997875\ETXCqkM\172372B\991843N7^\1043853Q\v\f\1049313{\EOT?\127939\&6.\DC4\ACK\SYN\149448jP|L&\f\992546X\190921M\127515PRh\1041028:\1015500\57481\\\1033348To\\|dG\991350n\1083796C;-EZ\GS\aN'\1018874\STXY@\1068631\1007399\&7\38975\&4~\DC3v\1022320\ESC\34523\147208E\1032547\v\SUBE\22322R/\1009023\1103232%\16901\SIb\DC3X\DLE:d\RSa\SI\1101190\1001758So|{\141831\&6\ACKm}}?6\\\DLE):\CAN\139105\&4\DC4\59446%<\n^f\160100\137645\DC2e\64089&\GS\1003966\&3\1030882\rwt.\62822'h\1068828C\DEL\1016844oO\61259_\178782A]En\179816&Z\1047565:\SYN" ) } diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/RmClient_user.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/RmClient_user.hs index 4b9af5e19b..095dbae647 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/RmClient_user.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/RmClient_user.hs @@ -17,7 +17,7 @@ module Test.Wire.API.Golden.Generated.RmClient_user where -import Data.Misc (PlainTextPassword (PlainTextPassword)) +import Data.Misc (plainTextPassword6Unsafe) import Imports (Maybe (Just, Nothing)) import Wire.API.User.Client (RmClient (..)) @@ -26,7 +26,7 @@ testObject_RmClient_user_1 = RmClient { rmPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe ",b>\NAKH'\149031\180170Q\EM\EM\175816{FX\160350\nu\NAKZ\177637w\1042829Pou\1099274WkQ\1032820e\STXWa\188817\FS\16300W\1000322P\110706J\ETB\988765J\EM\146149\986240M\1105177t _\161776\1072052~{.\78470u\1102696\vC\1737#b\37586<\DEL\129312\167517\1077035\37142\FS?n\ACKe\v\167493\EMh\1031373\SOHm\\T8tX3b\1020934C$*\1108469++\58420|ln|\167199s\r\1068066\22176K5d\21086| Vt\43503\149386" ) } @@ -36,7 +36,7 @@ testObject_RmClient_user_2 = RmClient { rmPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "|\38219\146109\1104272\68001\1007158cyJ\DC1/1W \b3}\27171O&\SOH?2\GSbT\69848OEg\ESC\RS$phd\1106057" ) } @@ -46,7 +46,7 @@ testObject_RmClient_user_3 = RmClient { rmPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\DC1C$\SUB&\9779 \74999m?Ao_i\994908\&5|(\74373\993551\1080071\ry\63906f\EM\67654\1047832\136850\1003353L\1080749yJ.\182225\&8\SI!.\bck\1104779p\ETX\173093\ESC\ACK\23225Ye\1007809\RS\n\985303\184009\95306\r\175489\1108039D\DC4\DELQ,''\NAK\160353\995523\&7l\120655@A\1093222\1107668Dd\n\1046822\1023527f\1029486p.N\SYN\998111\t\a^\986679R\\<\154586q o~\1095316=zN\t*B\167273-x\EOT\190128\SYN-\STXV\SIHbXi^\DC1\1096966\DC3N\ACKPMu(\ESC+VW5_H\185168\SOG\RS\"H`F0\1005392A\NAK]\75030\DC4\r\94424\9717kqk\\IV\1098464\5522\1014922-\1005479M:'6C*\168360\182775b4C@\DC1co\SO\SUB=\44742\1068818vV%\bM\DC1l~GI\1109000\t<\64505\DC4.Y\4871x6{\178251\1003872\SO\990516\1049646\GS\DLEi\1020935\SIG#IDo#\1083579\146691T\92763Y1\190785\ETX3U_\5182,GND0xT\EOT\ETXbT\1038139=\1023104\1066274a\DC1\DC3(o\995303~e\DC1\vnj\92360\voU\ESC9\1041053\ACKP%\1027535M`KT\94715Bp5m%Gd\b?M\1108211\EMM\1093455W\RS\bd\SYN\ENQ].Jj\19271.\t\US\158337k\177222%5.W\1007030dwbE\45342tlAYC8?\62378\1018506r,\b\SI:\STX\172291A\8228\RS\DC1,\1087495\146080-z\1111297W\1001491b\191422\147711\r\STXL\100907\"\1064084\&4\fK\135548B\GS\125113>\986449\983148!j\1077315\SOH\ESC\160941YN#" ) } @@ -59,7 +59,7 @@ testObject_RmClient_user_5 = RmClient { rmPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "u\r\RSKc\vwqO\DC1\ESC`\FS^T\SO\78233\2870,\NAK]:\190015b{\12662\ENQ>\1012526\53265wv\FS?\DC1Y]\GSJ4>\161019\127036\SIDt\NAK7\148811G\GSL\SOHI~?\152998\1079668M3\151251\160635Q\146150\136855\175056j/d\DC4[\GSh@sX2\DLE\36822\20669\r\1054692n?\NAKX\1021192:\NAK:S\FS%4\1000357\1023125\1094223\27882\NULFv~2\tdY&\1068216{V[\DC3Ob\4028\1113384Q7\189965xR*y8B!Z+1s}\1078810\EOT\ESC@\1060206\1064133\&9\1053766t\EOT\RS7\1082630-.\ENQ\28754\SOH\1024734m\EMu\DC1\1045651\20414W2C-eXl\1029878\54144L^X\a=E\996400'\1023967Kf\60191\t\1035046[6\1067319Er4\DC3\1067750\1034154\134998\&0\1010935'~ri\1049095\&6fHra\1000663d\DLE\\\36326U\1109249\1049434\1000455v\1079865S\ETX\EOT\28189\v\51876!*cU^>~~\41196\64528~F[ EA~6Ff\1112130FP/\111016OAa\39220\&6GY8*C%\EOT\1087848\SOHI\NAK\EOTY\61539ov\STX'\1023772\DC3\SI\FS{N\188089\DC3}eIr\RS\65287\42560\ETX\CANN%\EMv6\26591\21049\&3S\EOT\ETBf c\52779r\70741d" ) } @@ -69,7 +69,7 @@ testObject_RmClient_user_6 = RmClient { rmPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "f\1068600mj)\183679P\992997\US\1079905\&0sc\ENQ\167406\FSf,\50185\SI\988607\1032080\NULzRen)+.7O8\DC28\11736\&8z1\137184<}oQ GLfC\1098125\SUB\1108593\ENQHc\178829\&3Z7+\CANPg|\SYN~\1034724@>8.E2\148916" ) } @@ -79,7 +79,7 @@ testObject_RmClient_user_7 = RmClient { rmPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\194881\51083\&9_\1004885ETt\19904\DLE\1005738+\149283CE\1066640c\RS\1050216\1056071\a\SO\a\1084135?\995655i\1049139o\CANh9\bF,{\183672xd'F\n\166668dfh\50610z\1067294\\j\28361\RS\RS?\51780\t\r'\tN5'j" ) } @@ -89,7 +89,7 @@ testObject_RmClient_user_8 = RmClient { rmPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\\k\STXlDk\58709\168637&2M\b\DC3$gY\DC1\30177\SOGN\NAK\1020644\47145\ENQ$\EMp\SUB>\a;2\31773D/VktO\ETB\94786F\ETB\1063431\140361tGN\RSj\1097924H\1032883D?\1097409}>\12036w\1090856\ETXt0\120613n\1083811\19125+@I\181604x\DC2\139380jaoz'\GSP\ETX\132230|\ETBh3\EOT\138288r?7\DC2($\1054761\SYN\57353@\18013\39702>jL\1085777\&4\165044\186257\f;\DC3QO\1051006\9938G\FS\159207T\1036643=4\997105\140882\143332\US\ACK7\10935`\1026212H#\DLEbvb*\SYN*r\ETB\CANU&7'\97152N\133078\139852 4\t\1063173YJ\990648R7EJ'Cz~\v\"Z\1093198\1111312\37180\FS6b\DC1\62530\53959\1084720\181077e\1066979\ETXM\1043717t\38480[vg\1062558c,h&\tc0bm\1008860\62413=\US\59351r\SYN'0\GS\SOH\DLE`\CANq\ETB/s\1099041\158297\SOH\97324\DC1@\1031711\1033584P\25573\1111610\54934\1058576FH\66043\29556\n\1075739\996981?\NAK\STX\ETXRE3\FS\b\1063480\&2\1074988uH+q\185430\NAKb\995672\&5\\U_\GS\EOT,=\ENQ\nR\39177\1013849\1051954Z;H\57419g\1044344!G\DC2VUR9L\1090937\158091d(\DC1[s)wo6/3{\CANnxbq\"8\1105102;\50111\1082295e\1040029\&3Y\1029848d\1019994\STX>\NAKRH\NAK\176304\FSn\167700\SYN.\\|\1099362\CANL!H'\ESC:\1030596\1108869\143976\1043575Z\rs\SYNrN\33118\&5f\988325s\RS2\149587\1069227\177299\1057591\1023168v`\1045856\141152/\ESC\176589,d\566uG\996075\1024740\ENQ\118812\984941\DEL.uii \EML\CAN\a.\1092342\137112Pl\119859HK&\ETX_4\94605\ESC\EMqW\178369\USUM\ESCa\SYN\STX\taV\165246g\1013286\35382\1029703Oz\177867c\RS\DC1\EM\EOTZ>:#L\DLE\DC4\1028084.+_\bh\25871`B!}\4944\r#\131948\n;.\98171\&3+E\v+\986054c\62608\&3h@\164124j\EOTl\53853\&2I\16178]\168403\1015589+\1047817&\NAK\1010737WF\\\994386\181181\&7\r$e\SO\SI\1022270\\\ESC9^\983485E|HW\DEL~*b\995284\1015292\bB\133206\1024480(\DLE\30931\RS\1016271\DC2" ) } @@ -105,7 +105,7 @@ testObject_RmClient_user_11 = RmClient { rmPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\ETB\a'\\@)m\1059075\EM^\72201\1015541k\"dx\ETB\ESCk\a\nh\US15\SO\ak\39185\1081304\STX\ETX\ETXe\FSgPVGxoq~\160709m?\1091458pf$$\bwW\ESC\1026890\137523\&6\138333([$\DC1\1002695\DLE\1053749\986572G_KQ.\STX;\CAN\1040541Op\1039745\fz\23042\1111245^\992408\SYNFZ\48538$iW\174178\CANMF4SCdf\183630'\EMvO\1088031\68872Fg!a5=&\"wGT\ETX\re\160574\1068011\1070114I\NULQ\1079966W}\"iI\DC1\1010245GW\1006838bn@\ACK\12510\DC2U\998156\2120*0\EM\59045\1089866-\tH\1040686y\1030608S\1022283\181615\917559\1050355^\143688Po\NAKC\DLEG\1092512\&0\\V\136027\DLE?\DLE%\1040722\77835\ETB\190551'H\55061;v>+w4\EM\189653L\1045896\&73>z\1100545\GS\1046690C;m\a#\1102454F\EOTeE \1017094\&7\DELS;\EM\SUBc\95703\5551\141533-\41407\&8Rwm\ni\US\153804\161848(k[Q\1088187J\1006045\f>5#\177947\DC3>\1000951\1000056vL|\187670#\177307\154490\CANh:\9975\1089780\99284\SOH4H\ENQQ\190920*,;k\38432\SO\1006676\b\US\17954\1077355\DLE\tGL8\DELJ\24433Al\1011516hg\177209:\DLE\39726{g\1084006\aT\1108183o]|\1079519M\1082621+\1001591nf8m\14960\121239\DLE4 \154263jI\1113295]\FS\45303|3i\DC3\35299#\129557A\1062233i{\156175\n2:\DEL\DLE&p\40502\1081945#59\1044160:F\32935\35480y^\152665\&07\v?M\ACK\bH\368\20580}Xt;\ENQ0C\n!\1077494\19077\167442O3o_\ESC\STXNbB(\132160\1017760W\1018075HA\1089009\164474F\DC4N\1058101=pk\aL\12176\&8~\57813b1\ETB%J\DLEm\991879\121092TI\1062650\t\RSM<1 U\21056-7Wn!w\1078469\DC3\SUB \DLE\GSjx\"`\SUB\1055719\EOT]tc0]\"Lk\1089957\FS\18169\STX.uO\SUB/s\1006294\&3T\ACK\1009623.^\1108849D\DEL\1055418\&7Ic\1098123\SYN0,;:M:G\ETB^\SO.\1103088\SUB\1050786b\5905\25243'\NULR\fgy\NAK\34318\EOT\GS\ETX$\137105*\RS|\30344\DC3T\5529\rWY(1&8\DC3\1066256\159300\SYN\1087588\1113798\SYN]JC8#?\ENQqtkp*\DLEj\SYN\1098070@\ACKLzE&\DC4\111339/2jBel|B}\SIe\190306vM-\1019151\EMY+\t\1013622J\170034G\DEL\1078519y\vm\1018713&\nf\GS\1102895\41043\DC2D\43946A\49667\b .w\SOg\1017121\1001682?'0\44391\1389\CANO\1077433\40773,\92582\1012359\r\1011515B\1073868$\136645,\USGn\SO\36290\STX0Q\1110755\&5\NULN+a\SO&SQ\36504R\96913efu\1074135E\1063004\69816\ESC\DC2cb\DLE\CANsnz\52286\1015052_\1066195\vI4\1107184\v7\GS\998814\NAK\GS\986796\998902\1095537\1049320\136576NT\177463\&8NP\23931M\DEL\998395}L\ETB\1107078\ENQ\133738\"-\ri+\162913\STX\1067349dYK\12305gNI\STX\t\1091824r\1009246p\991411\20642k,+\1076655cJU\ACKc2\179407d\DC3\ACK\NUL\ETB0\1104172\SOH\44872t\DLE\7912;L\ENQ\134463\998861\&4\RS\b\189089\1077954\SOHh\1018425\aB\DC1J\23425Pz\67331d\187576p\24975\v ).\SYN\163714\NAK%\1015879\64593\1002966\1061172\n\1025501oe\1011894\STX;\EM/\t!il\1012248O\a\1097530r-BSLX$8\n\ACKBF$e\1001493\CANr\1017210\ETB\183079\EOTq\995822nrqAeT;\1100221\10430%7\DLE\NAKvP*pJ'\35280Ur\"y\n\SUB\FS\35772\170674q\1023935iW\62447\74646j\29562\EMXPv2\SUB8V\74822Y\184675x\NAK\64876EfH\"9\1089441<@\141755>\f\1092300\98292}QO6$ZI\ACK\a\SYNxLa\1074036\DC3qwGZx\DC3&cc\1073565\t?\1029925\1062160S\DC2xNC(:\ENQ\ESC!:52=\142112:,' \78760\DC1`GHZ\179155\163413\97615\t\34193,\SYN=X\1102133;x\23553(iT\DC3\172249Ni.\t\1080519\&0L0\STXln\NAK5b?!\b\1112670\22572\158487r\ENQ\1011620\"G}\DC2\DLE\133297\&0\DC4\FSU\159706" ) } @@ -128,7 +128,7 @@ testObject_RmClient_user_14 = RmClient { rmPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "JS,)\apP&^\US\fP\n{\DC4bwQPl\139468\1095461*`9\\\EOT\STXD8\CAN\10743\148195\1005784,\143168\1005601&@\SI\22672\&2\DC2\128381'U=64Z<\64338a\fe\29861U\35725dMJ|\t%?\a1\73972;\GS\190755d\SI&\ACKjgC\1042133\58072\ETXWP\DC3ZN\959Xv\137301\1034484c\f>\1079348udVB\1092880A}RHF\1109691\"\126246S\RS9\985146g\10146y,\1027865Q7n\SO\1056019\DC4<\1071322-\"\EM6\44193Yq\v\1022842\&6\17324qN\150474\EMyV\62280\DLEf\"xt\1071474{\1012500~I?\DC1Z\f\141931OEz\1083265JGdEb\1100849C\SUB\1029802\171738v\1094740\58371\83338\995436\&4\18752{R\FS:\GS\ENQA\985299\1049855+5\"\159265VxN\131406c|\SOH\180495O\1067889\1096101\181191W<\177853{v)\GS\SO\33042b)\v\990437\39439\ESC{'P4\RS\136975yu\ETX(X+1&\1107240\&5\1036872\988610f\128486\&6/{\1072363\2106A\NAK#\1021527^\985809\SUB\1061227\EM~{f\1070917\1056635\SOE\SOHVF(&zL\1061708f@~\51296\ESC7V\SUB#\160477\DC4\GSq\vd03\SOHoX6\1078388\DC1\DC3\1096865\998527\1011292O1\SO\141874\18191\t;\DC3\1034008\ACK\GS9\180579eY\127851D~\1063586c\EM\SOHn\997603\EOT\1000820Y\1086755k(\n?n\ETB?\1033490g\SOHK;N\986426\&9\ETX\997547\32787\1101424\187624)\51954\NUL\NAK\157227\SYNw9b\1010290@h\1099763)\SYNY\1105508\149194\&2\1013323Iv6!\1086161\DC3Q\EMAq^BK\ACKOhm~g\SI\b-35\994294H\151366V\181790\ENQ\ACK\ENQ\r\DC1\183450j\42936\&3\FS\157601(\149094\1097849|CC\43541\997243z%\190687oq<}\175058\1000174G}\ENQ\1002175 \DC3\DC1o\160995Z2-\11011[h\EOT4#>\1066859\60829Mn\aR\ENQy\1006323j\t\r\1054622s\SYN\158717\1053679( \17700B\18157@L\DLE\1000031\1075133\127484Z\36271pD\58813f3|\1097266T\167390\1042802E\r\DLE\SOH\1069786\174606\45404\v15eR,\ACK gQ%fhE\137991-o\1102116(J\1066702knt\ACK\1005141[O\rx\RS\n\64739i\1100555\182947tQ8\54609\&3`;\FS+\1099126\37776\&7?$o\1031939|\186091\CAN\59235\139215\23039Z\DC2[\SIv\SUB\SI\ETB\ETBR8\121042z\DC1!H}!\EML\36933\170734J\ESC\74828%k^\1092735B\1003335/Efm\23384Gv\CAN{" ) } @@ -144,7 +144,7 @@ testObject_RmClient_user_17 = RmClient { rmPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe ":^\1064227rW]\71111[C\ETBth\172645c\\i%Bh62xX\ETX\1003291\&7\1088794\1015330\n\DC2g&\r\SO\98557U\t\1849\161566\1085916>\1053071Qk,$i\CANaj?\21418\1005710^\186062\alB\1036347Y2Hj\1020009\990636\23254\&1.d\1009177}$\1031958\1030039Wg=HR\78422ev$\ESC\a\73012\1038678\12665\129352\&5\f\28309\1051661v\996552X0\21269J+\53984;\DC22l#Ikr3\\\NAK('HA)q_m\US?zDcx4\168332\&1y\149573m\8674\173780\EMcx\\\44711\t>\SI\"3\GS\STX\157812;DtMbX\62897u\45382\1057316!1\DELZ\21661Y>5\1072885\&9\SI:\790_$}\EMH\SOHy/\ajc=\RSof\DEL?0\SOH\158349\178108zZ\RS-\DC3\994567\&2<5\CANH\127293\191190j\139970\&8/A#-V@IgQ\1028998\STXqNi%I).i\DC2\1044694'y~[\EM,\47073o=\DLEc\1100011i\FS0\1001994\&4RA \SOHNx\1044774\&5`6P\985133\154298/l\1035874A|\SOH@}\NUL!R7*\1067646Y\EOT\SO\DEL>\EOT\NUL4\176080\tQLZ\14795\DC4\166607\SYN%\1019393x\1034448%\STXmRk=KI\1062040\1010121\ESC\991763\US\FS\166933\EOT\994930<>\NULRt\SOv\1073987\98391\fSl7\50002\121034 U_\FS\1086509FY+\ENQY\187880\1085508QVuo]bH\30696Z\FS\19827\17889p-^pD\62997\&3\DC4ZH\NAKG,t\FSU\DLEq:haf@\1097538@\1017660JsNQ\ACK\1093827\&7\SOHf\DLE\1073590\RSw3\1072223\FS\143760tw\162052Su^h\995522 \1074155\6548;_\"\68918=\\U\139264+\1033999\&1\v\1095259\43939Rl\134822-u;\SOHT\SO\SOHy]a\993692\149269\71120p5&ub\NUL\SI}\1107\1023682\1045554\182225\DC3(m|\1095432\1033702\DC1\npQ1\62892\1064567\DC4NH{;@,Y\t\SUBRi8\61771_]\STX\1020112\1022473\1065928\1046385\SI\134676\&0E\58889)1F\1010862\DC2\158347H\SO\DC1\1101828\28722\135590i\134246c'O\1002376\"\194733\SUB S~)\1033861\4030&V H\172870w'\188631-\"af\"8A pXN,a{sVN>8;Z\995796\150250/\f/\f\95037\DC3e\48613\SO\3262\&6wH\tl&\DC1H\995904\f\CAN\DEL\NUL`\n~\\\158630\&1\SOH" ) } @@ -167,7 +167,7 @@ testObject_RmClient_user_20 = RmClient { rmPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\\\179922\ETX\1063876gq\DC2\CAN\151652=\19388k\78018\1006299\GSdPY9\126638\1075436\n\ETBs;\"\USJ\146\SYN}s\\\DEL\149791\DC4\1009471W\1066869\"h\1050077\154303\174087&\34924\FS\DC3@\DLE\tpC!\NULj\1033662\SOH\44555}RsW\1002820[\12613)\FSTy<\SUB\SYN\DEL\GS\1102230g,[\1049430A\74421c8g\DLE(g\52062G%rmt\992360\1013971j\ETB\SOH\989970\1110618\25626\159497N't\ETB)[d\DLE)T\SO=\18676\ACK\DC4L~r\1033945;I$z\996571XI(ia5]C=\SOOk\57352(85\STX\SOb&V\163119;\53604UIHhrO`\NAKvpx\b[\62002U\ETX\1071858*\az\1094108\DC47>xS\SI\RSU\fU\SYN&\154636(?Ju\NUL\1076043\&6Of)\1010856J\25403^\171422\121331(\DC4\n\v\172750\35293#\98964\&7\36306_\EOT\1100877%()P\ACKT7\1084781g\DC2Eq\178647\1010593ZGV+\98214K\tl\1075434\992552ej\147375\SI\1005868\177430k9Y:\FSI{\163043WHD\169883\&4<6\FS/\DEL7\tM4Js6\100092\155924\&7Wo+7\61675\17819S\DLEd^f\184182?\DC4%\50344\1108647\EMo\bmR-j\142268GE\v#Hm%j\EM}~u\NUL3e\nn\1063407_\DELt5[\994606By\bn,\r\187693\SYN\vo\142453[\97973$:5\1036737+!\ENQA2UL4\168297`\181100\&3\1007444\EM%\\\b\1005402T\DC1\f.\72807wK~\DC1^~\143572i\DLE\SO\1081338\DLEb\nz\ESC\32124\&6\189340\95862\SYN\29594\&9\bX\n=j.\1094027\a)BB\1019205\1035208\FSD\1000886jw\175947\&3[LhSu)\141403Ko!\n\CANy\1016531T\EOTA\ETX\99030Sm'\1048650,\24162f$\ACK\27733\1218N_[*\1075509\82994\b\FS\SUB:\1045237N\998570ay\ESC\8175\983838d\nBy\1088104Hn\US3?\191176\190202r'L\RS*[S\145039o\1063510i\STXp#w^;\DC1x\ENQS\ETX(X\DC1e>\1018817+M.o[\1104806:[N" ) } 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 795e79bac3..a397c9fbcf 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 @@ -18,7 +18,7 @@ module Test.Wire.API.Golden.Generated.TeamDeleteData_team where import Data.Code -import Data.Misc (PlainTextPassword (PlainTextPassword)) +import Data.Misc (plainTextPassword6Unsafe) import Data.Range (unsafeRange) import Data.Text.Ascii (AsciiChars (validate)) import Imports (Maybe (Just, Nothing), fromRight, undefined) @@ -29,7 +29,7 @@ testObject_TeamDeleteData_team_1 = TeamDeleteData { _tdAuthPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "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"))}) @@ -40,7 +40,7 @@ testObject_TeamDeleteData_team_2 = TeamDeleteData { _tdAuthPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "?\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 @@ -62,7 +62,7 @@ testObject_TeamDeleteData_team_4 = TeamDeleteData { _tdAuthPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\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"))}) @@ -87,7 +87,7 @@ testObject_TeamDeleteData_team_7 = TeamDeleteData { _tdAuthPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "|\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 @@ -101,7 +101,7 @@ testObject_TeamDeleteData_team_9 = TeamDeleteData { _tdAuthPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "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"))}) @@ -170,7 +170,7 @@ testObject_TeamDeleteData_team_17 = TeamDeleteData { _tdAuthPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "*\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 @@ -181,7 +181,7 @@ testObject_TeamDeleteData_team_18 = TeamDeleteData { _tdAuthPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\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"))}) @@ -192,7 +192,7 @@ testObject_TeamDeleteData_team_19 = TeamDeleteData { _tdAuthPassword = Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "_\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"))}) diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/TeamMemberDeleteData_team.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/TeamMemberDeleteData_team.hs index bb82a2a839..87f3579108 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/TeamMemberDeleteData_team.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/TeamMemberDeleteData_team.hs @@ -17,7 +17,7 @@ module Test.Wire.API.Golden.Generated.TeamMemberDeleteData_team where -import Data.Misc (PlainTextPassword (PlainTextPassword)) +import Data.Misc import Imports (Maybe (Just, Nothing)) import Wire.API.Team.Member (TeamMemberDeleteData, newTeamMemberDeleteData) @@ -25,7 +25,7 @@ testObject_TeamMemberDeleteData_team_1 :: TeamMemberDeleteData testObject_TeamMemberDeleteData_team_1 = newTeamMemberDeleteData ( Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "&2B)^5\27217\997611=\GSa\1098108\1096149\1049861\SYN\61602TRK\1068428lVx,\1105032\&5\STX\ENQ\SYN9&CN\vp\1092020\EOT\1012795\41779\GS'\1096719Z\14081\154246\180894\&3\USa\1100084\&5\ETX\a<\1058892E}\994732\142498V(\t4\1036558\EOTziG\181736t\1090947mXH\f\tc7P&\RS\1029691&\US\EM6[|Y,\GS\SUBF\ESC\72792\ESC7s\vB;0X\988058@S\NUL~\1015602+\1023555gA7\1061589\&1\FS\147798#`\EOTvK\r3\EOTr)U\"p,\EM6|\SOz\131902\SOH\1109229V\179735\SOgL#\1090807\181196\GS\ACK\1092686\USV\113693\190978\ENQb\151272e\ETB>\"\150194W1\US#W{\184315tP\51389p\14531\1032964c\1025519\1026100\&4G\ACKms\STX6e\STXS\1028901\&7\43005@\SO\36950c0\34301\157527\ESC\t\ACK\1026325\f\rG\1106154\GSe\1057375\1011531\7921\53155\ESC\162927+8\ETX,hr6\DLEl=i;v\66250\1053903\DLE\1107670Mv\SO\ETBt\ttj\1075751\SI\1011614\5507\&8\DC2\49418M0\178101'#2@J\n*wQL\SI\1012503L\ETB\ACK\EOT\aV}\998620\DC3T\DLE\24204lmp\1058653[\FSY\EOTo\\\1080525$c\31448H,\158985;\142881`rvK\1085615\1079251`\67126CJ\999043\ESC\1017565`\1069493\EOTY\NUL\ACK\1099777;\SI\62359H|A\131837\DC1x\EOT\1010438\1009821\SOHo\1010613\989551u\182682\vO-\SYN\DC4\EM\1039702[[OSE~\5040rK\DLEKy`@\34897\CAN#G'\1032834i9\DLEa\STX\31292\46018\ETXD\987910P\1010172\NULJ\DC2J\1113377MX\a@\STXYV]\131249\NAK3R\GS?R'\1064707u\1031505\3616\SYNkq\1036778j )\98862@\986416`r\1040717\NAK\1032798\1057926X\1041466\RS\1083971\153648\&4\133508\DC2b<\CAN>G2\STX\SOH)K\98279\1002563\146951k\ETXU?\1095859I\1019264\&0Op\25587P:3MmU:\34041Z" ) ) @@ -34,7 +34,7 @@ testObject_TeamMemberDeleteData_team_2 :: TeamMemberDeleteData testObject_TeamMemberDeleteData_team_2 = newTeamMemberDeleteData ( Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "acb]K\178078e\USB^\SOH;\n\163588\1071467%\ENQ}\23878\nyvi+\SOh\SI\149149\16961|4\US\DEL\1032963*sdj/\f\1005841\"\1019968m\1075794\989025b[3g\RS\1079931$Y\ETXc;e\US\20193,\SOH; 'U\82972`XFC1\135269\1026695\vZ98gD2\121478j\995957K\187550\DC4t\61318cCSU\1016488@P\STX^*\NULg\19221A0\97440>QT\1009820\GS0O\92348\CANjHH^\tD\SI\1041192\173902z\RS\NAKj\1057305\NUL\1055498JSm\1079053\1082273\1064851\170607\1102733,\DC1\\oL\EOT\NAK\"kdJn\NUL\94292\1007933\DC2\EM\FSM*-\fE\120676\US\1105784{-z\1046731q\4293\1064428o\41877h\32109q&\135864l\1024579F\1021403vCCw\ETB\"\147064:\NAK&\49860Zb\SO6t\r\\\58544\12715\25005u\SOG4\ETX'\1110039\SOHn\1092697\18777\ESCW\1104803\bnW=\SUB#\1084581\EOT9\50052|k\b\DC3$\DC2\1050977,X\1103507Ipg\DELT\40394\1086028\STXPNz\v8\151326Rt\FS\1082298\a\153885\1033440W7fL\EMxTh\v\DEL*\1106735v!T\1079911\b]OM}}=;\n\1024847\63140\1084821N)I\97064\1016345Pe\EM\1016338F\1025320Z\DLEvL\1026587R\\\1085501\NUL}\997708RS23\ESC\1041467\20243\22708C}s~\24825\1100712*30#\120716\1023007.l\FS\65597\163921\181231T\47367\1073889d\STXt\19177f\1094805\1113992,-\1034284\120732^o\123174\&3c\DC4@" ) ) @@ -43,7 +43,7 @@ testObject_TeamMemberDeleteData_team_3 :: TeamMemberDeleteData testObject_TeamMemberDeleteData_team_3 = newTeamMemberDeleteData ( Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\RSAt}\1024747*\22698e\1057448\&3\59563'\1048899\983888\&3\n\ACK\149851.\n\159664T(KLRbW7\NUL7y\169693S2hDRvXySE_\172180\1071944\EM \1016579\&4\DC3:R=G\987434=\SOH\ENQ^\1015303u9h9^bk\34125~aVIJ\128740O\97189fs\RS\170653\987039I<\1101027O7)y\184399\"&\988077*\ENQ\bC\31103\DLE\NAKCz(\1011081\189527dnZ1\DC1\1022934i\t\161780\&0\65100\1095340\1056866\&2\2319\DC4\FS^%}'\1014069\183838\DC1\126100&8Tca\US;\997201#iM\ri04:lv\NAKu\183693\14775$*X}\160400*Y\RS\13194\GSO!\1078399\DC1\57726G\STX\94098\&7W>\1051681\ENQ\181860\EOT\RSy\166078K=!\1704\GS\FS\1077988n\1084276\EOT\1047740\1097469wvgh\GSg\62970)RrR\STX\1018140,6%\SYNZJx\DC3\ACKG/S>&\23098\&5\\\EOTD?\ETBr\f@{\146750\&96QQ\CAN9\ESC\DC1\78356\DC3\DC3:NXt\rNQ\t\GS\EM\186286\SYN%\1046637\1003183\r\156699\138317n6a\9313\1012020\57927Jl\b\1025172\NAK\SIL)z\162552@\1037185\179562--\996261rT\46953pR\NULIaW\1090705\7199\&4Z\NULQR\8409\1060241!\FS@\125225\n<8C\DC2LGC\1029217p\73960\ESC_\1091606\r\ESCL\169269;z$(=wQ\EOT\DC3uN!&\95524\ETX GF|$\CANq\144446\r\72275\1044960e\RS\ETB\996698f\SOHNM\60222\100278HL\148371\DC1r&ZJ/\SOH\7350:\ans\134938#L\FSUq\DC4Tc.\1023433p4[\27319*\SUB\46508\STX\DC1~\61293\ETBl$\4545\16402\DC3\SO\1043853\152709A\101039L%`t\fa\CAN\r\ESC@\NAK\160359y3.q\aj\1008088\135893|\btJ\1013730=\DC4\ACKVe\1075824\66846\SOH\1042291\DEL(<3#\nW\71273X\1086273f\DC1\SYN\ACK\vN3\t\1052238^\SYN\1052383*\1082319T\SIX\1003300\127882\1077382\1100079\1110627\&01\65912\11780kID\143991\b\au\5930[\SUBW\166919nX\19851Fl)\158756\1014343\&7ycD2SA\17170\r\f|\29281p\1039494Z%^bO\bMs/x\1058016\39049\SOHX\1078198\150146cs\15589\SOH\15808\RS|q#\33047*\r'\SO!Cg\4469\n\CANs\41276\SOn\v\47634\"\3512\&6\20600.\1070378\175128\990918e\DC3\186099\1004164Y\1024091\&9\15712R.h!4_|sG]\GSQf\EOT\120672\987362M2\119590dO3x\DC2}Xw\29641auU`\1086909\1070708#\188077\EM%['\1106260fg\26837\DC41\163968\188251j\144911\&0\1091478\STXz\31609H>'H1>P\170739-mn\SOH\142265\1089377\1000854DM\47969l\169379\EOT)x,Y_\r`mFB<\1013411.\159530" ) ) @@ -52,7 +52,7 @@ testObject_TeamMemberDeleteData_team_4 :: TeamMemberDeleteData testObject_TeamMemberDeleteData_team_4 = newTeamMemberDeleteData ( Just - ( PlainTextPassword + ( plainTextPassword6Unsafe ";:\1085202\142823\ETX m\1058562Y\f\151535P\998879~\SYN\USD\1042619.\1102761?U\ACK`ux5\FS\DLE\1081968\1012173\&9>G\"\bX\1099848|Q\EM3\1099473\185200,\165632\164814f\1012425\999050\1086718\17685Tblk+#LLR}J\41929\EOTy}\FSaG;\1083759\&2tbJ^m!\DC41\ESC\DC1\EOTO3\ETX1\19510\1072516.;lb\19090~i\110639\1081520\991127B}0\72219\DC1\a\184536\68650j[O\17759u\1079320Rq\1109475T\CAN?,]\t\1090948w\26834\139569\177186\170853\&3]\DC2qS\16480r\RSA\26274,T\ETXb$\STX\SYN\b\"\EM\EM\"x&\vOE9\SI\ESCV/(\984371\36346\1001714@j {1HW\"R\ETX\168415\97300y^\1051842^6\vx]$\b!8ZI^W" ) ) @@ -64,7 +64,7 @@ testObject_TeamMemberDeleteData_team_6 :: TeamMemberDeleteData testObject_TeamMemberDeleteData_team_6 = newTeamMemberDeleteData ( Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\3487/W`\153983s%\vU\1112508/(\65385\1064439'\1050481(\67096k\a2\DC3:l\STX\a\FSl\ESC\ETX\129591C/\SO\983273\1090051?\33341\DEL3S\n@\DC1C|gY\b\ACKE\r2\39737i}\1041551 5\ENQ/]p\1000964\DC2\SI\1087910\&6@\DC1\1107557\ESC\1017311\1099426V4\"\EOT.\1001487\1071804$)Pt\1056089\DC3rrqfzO#{\SO(n_PB\1076000g\SUB\GSvGOH/A\EM>2\41761 o\ETB&\tAu\988743\&1}\RS]DG\DC2\19661f\990457\DC2S5k\1110587\8460G\tG\ETB(\133823\&5-\118823\1066668\1013157_\1002652\EM,\1098522\1024605h\97532vu9J\US\\)\SYNa\137244\ESCh\FS\aLB!\1043177\1037242\120784\ENQ\ESC\1073460U\EOT\1047679Y}zA\40579i\nU\95278\1007558ty\SOH7[\1015211_?0\44712H\DC1r\1025883!1F9Q`Am\190499G\72737\983127:\DC1\1017287W\DLE\CAN\1037457\167039\\\fW\137997m\1061907\f\v\32839\986212\52707L~\ETBO\"\1068213\1003730\32523\1013970`\145591\1107661S\169217tj\68018a9\47676\60986l\157701\ETB\SUB\146892x%[\1053033c/Z-X\DC2g\GS|\SIs\1015364*ax*g\127526Z\179539m \1070364d\119303\f2i\ar\STXU\1050733\ETBrk\SOH8\CAN+\fJ\STX-:\GS\143351\42819B4\ENQ\SObe!|3>F\1081726\31835\1112480dp\fo\61236|\ENQ}\1100114.\b\1008563\aC$\EM\\[h\45771\1106497%S$l\25028\DELHv\1066351L\DC2\EMm\3889\&8]f\991104(\186116\9153\1111516\aUL)\DEL #\38246\134304\&0\1045868\6650D(\1043899\144324\r\vgRb%\1076097\CAN]`|aU\t?\1031761^%\STXQ\FS\187007 (0\181225G^xbS)\164921t\US\172509a\1093646\793\14293\SOH\SIVL\ENQ\18082\1031393{ \68631\ETX\1011851\186901`\US\ACK a/\17301\1078597\&38\1064739k\NAK\EOTkTmCq\128544\67246Zy,\t\a\CAN\149797C\SO\GSZ\165664v&\DC3\f\ENQ)g\110690\CANTM1N|2s\28970i\v?(x\1071141\131297\140793\SOHh-=D=\EOTT\1059569e\1079092B~\DC3\137367\n\1062900|+&\6281r\178923z \144406\1067890f+\r\121330\FS\164178\16423\17555\NAK'\ETBf2u45\1004862i\ACK\ETX\DC3\52306\1001867\EMD@\983062\SOH\NAK\DLE\37644\NULR-'Y\RS\b`~nlS{Ak\SOHY\176095\1045558\SO\141601\ESCcQS\1051338\167187\1075886b\194868\23624ZC6\1079693g\ENQr%\136486\1033915\33263\SOu|.=4)S\f\1095104\n\DC29\"TE\ETX\1026394|CO\1007906\157704N:\SI\1105418t\STXO\1112314\EM/P\DLEU^\153798\&0\1078274F4M\1070528@U\120600\188622\&5DEZ\nT\1051797\146758\STXU\1011851//[\1014740\44526\990059al\28914\SOHC\149214\\\1804\131723\21080\r\1042487QWP>7\138676\58055\5329T;hs*\1048394I\133479\1096568k=\92744\1092351N\SUB4\180298m\b\EOT\v}H\EM\t{{\146796x1L\DC3\n#\38760x,`\r\ENQ&\r\1011907\ab\24890\120931-\1013758iQ\DLE\991068\DEL\SIo\36732\1041631\8679\&0\t\5396\NULT\GS\132469D\DLE.gOBt\999749\61129\SO\b\1045482JNBJ2\SOH\111250%M\FS\CANk$\ETXo7*\bHQ\"J\DC1\DC4\1044276\&9+\28040y\1059360\n\28970\&5\GS\1008339\ETBqV\SYNpE\ENQ\ETX\ETB\vp-:c6m:\DC4\DEL'\anS\t\92947\SI\1020461\bBn\100283\168827\180063kbU\166881a2<\1067381D3p\"&q\983984@\nV\\O'rE\DC1\1083922\b\bMm\STX\NAK\1066606" ) ) @@ -79,7 +79,7 @@ testObject_TeamMemberDeleteData_team_9 :: TeamMemberDeleteData testObject_TeamMemberDeleteData_team_9 = newTeamMemberDeleteData ( Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\DELO\1050425eam\ETB!\n\SUB\135932\1098695l\1083248J\ACK\1086266T\1098257M\73080\160589\1034083\987941\189679g0\GSL\\y\DEL\DC2+9n\998050%9'\USGz\988696^\DLE1f\1063833\NUL,5\154927I-D!5p@\EOT?D\61446&\18381|\179692\74344(Y\20728\DEL\DC2\EM\SI\1032462e\SOH-`=\1093111f\1070563O\142039\DC4\1087417M\SYN\DC20Q\1090649X\163641v\DC3\f:2`dP*vm\135368\&9\SI\1078789\1047112A\1059530\50540Y\ESC\t\r~\ENQ\1088959aDOI\29995\US\DC3\nUB,\37118\CAN\tT\CAN_\44096I\ACKD>\SI_\1102079\ACK\GS$\29261\DC4\1018470\FSjLBiW(w\190415\SUB\USRe~J>/rH}\20790\fM\78469\ETBZ\1010301\1035243hW\1033113}x\1021481&\142713\146095m&\1050704\20304\aG\1004240\1019479\US[0`\ETB0*\1002355pw\1053613y\168822\SI.M0\1089128\42117\1057082 \1099778\&3\1062960'o$\GSj\EM\156179\NUL83\SUB\GS\SI\r\DC4\ETXB\1083044\SI4N\1038590\1078873\&8mb\NUL&\989375\999042P1\138615{\DC4pO\26935\bi \23899\&5\DEL\1103099 \61784\&5]\1002150F\10563q\17146V9d'I\1031217z\FS\74932e\78704\153532\187176$2^Dcy$7d c\US\DEL\GS\DLE\50144p`j\1092614%\SOP$f\"4\158190`\160237\1065205\FSv\\rUN\988676[\DC4\RS\SI|\fP2q,r\175085\1061357\1018750\188528)_W\1110575Fu\SOHi=\ETX&W\1006859\142187\1082035\1069093\US_\1095772etw\1105624N\STX\1045553\SOHX0f\"\RS\68372\1032263x\1001300\58222@\185889\DLE\1076041(!%6\23783A\1063735E\1004046\RS\1030571E\"uyI3\SOH$K\"\26198\177990\STX?\DEL\SYNO\rQ\1036500Gg5\27364\1063444\SOm<\61002\SI\ESC\DC3v\1039517\119064lAO\20456 .H.\n\GS[;pZ\EOTij\EOTd\r\fr8\1103701\1113470\1101645\12089\&8\ACKJS4Ud~\1005994\64545*\140117k`T\\B\DC4\NAK)#=\1078156gz5H~h{'S\1027690\tJ\1069967\1046253\ESC<\\z}eV\n\1080186\47097\995457O)\25222b\SYN0W\ENQ\83208\"\41818\1002078\171605i\ETX\aOV\1072264f[7G\59470m\SI)\SYN5on\ACK\US\n\1102622h7\ETXc\1080636A\134346\20666RbD\b<\163903qn\994722&\1111088e\135251~j\1096476\1105946J*\32448o\EM\1094977_\47678wf\22691\1002634\157283\164812-\DC1\186285\njh7\f\b5\a%Cs\46168cr\US\61194+J9\1078703:\24572\1107553cZ0\997528\1061816\159480\DLE\134356\GS${X\\\1072134:\135399\154795jz\999166\147606hWC\FSQ8u`\SO\1110434\917835B\bAy\990091/Kr\1010345\GS\42014Fa<\1009405Y7\DC4A\1083282\n\SYNCsU\EOT\1092541\STX\35217\996675\1028207\ENQ\DC3UX3\ETXco\64900{\SOHk\ETB)P\ESC.T\28767IliCpx1L\DC30\60869C\SO]\160844\DLEf.;m,\44596\SO\27669\SO\165805j\EOT>\111079\152224(qK6\149304\ETBal\1047401\&9c\DELT\1020087\NULa" ) ) @@ -88,7 +88,7 @@ testObject_TeamMemberDeleteData_team_10 :: TeamMemberDeleteData testObject_TeamMemberDeleteData_team_10 = newTeamMemberDeleteData ( Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\83412A\61351gPJEyG\74297\&0J\1043712w\95460\&4Xe9\24380\1033658m\f\DC1\EOTc#\1064434\nO\NULp!/P\1077857\995468|=u\STX\SI1\t\1079785\1050039\rc\DC3A\983391\SI\EM$p\DC1qvM\ACK\v!\163406\984631q\1026936\SUB\1002198T\997757/\993466m>\1054847(\\\61714\69655d\n[@\1100+%\1015441OI\DLE\ETX\DLE\b|<\133849\NAK\1113694<\1110454\10988\1035625%\SYN\RS#vD~t\1024338\ACKW\SO\178292\68768K\DLE1NK\"\1028038\US\11575H\ETB\184793@\SOQ^b|\96986\GS\r`)\EOT\39045\&2J\1038483\181864F\v\FS\GS\NAKO\DLEG\64438\RS\1007085 \985742=J'=-qo\1041196hv\SOHRh\1111098M \1015470\170419v\29935\987568\EM\12886i*\SITS\156772\1044699G\52460\SYN]Q\a\1016431Q\DEL\191200\1037910BL.\n~\DC3\DC1n\v\DC4\t\27032|\156824\48608Y\ENQN\6940+\"\ETB\tp\15469Z_\1024237\1024170\DC4\RSYGM]l\1107457dkj\1067848\&1\DLEns\188186\"\DLE9\vY\1012319\fJ[\1050817;t\b\3249H\USC\984133\1097997\t\DC4n(AE\1089006?{g+(\11135\1087122\US\DLE\ACK\ESC\1023768\991591R\1110736j+f\988454\20838\SO\SYNU\DC1\94025C\1068532{\26821Y\1043600\38349\SI\1071684,<)\1026801{`\3758\32328\ETX\1049443 \1046106f(\1041394nEsb*R\157021\DC1\DC24F!\\\ETB\44983\1029483uX$\b\1082718\&2SLv\5530ye\93816\154415u\987799\DC2\SYNdp\61791<\EOT1\1069574\37135\1346\131936uEK\1102514\1025355d\SO:\1028051g\1054471\f\1019329$\n\ETX\169034w\5567\119537\9961\&8V\EOT\1108102\&3\983943\1055118#\1024673\US\DC3\1082575\1071826\191187\"r\STX%\GSx\SYNt\\W\ETX\28273\78865qxrPse\ENQdR\1005148R\STXSx\DEL\b\1019923_sQ\GS\DC1\1113712\995580ak:\50743\170853m\100591\119963\rl\172736q\987780OC|\167364f\ENQ\NAKW\95663\1051900Wo\1027904\1099116\1087417\&0s|\1000122`5\RSkN-9\bvs\SI\1029280p9\DC4\aYt\30503X\NULy\SYNEGyc\DEL\\v\n\DC43\986010O8\US\42597\NUL\14050\v\EMe2q\r\53882\96137&\137515\96229\a9(j\188627\995272\1107074\FS::S\r~_\98949=\999881l?#\1009019\&2}\STX\ACKO'a\158080\SIDDW\1074562\n$\171396\DC3\ACKq{1b|`\158845boLT\EOTj+Q\1105600\US\1076465\1020627E\DEL\1087875$Nz\ENQ~o2|\153236\182999{\DC3\ACK\986080\&5n\rZ(cZcS*~w\DC1\NUL/1)\GS\\V\39758\1032524\DC4q\EOT`6)\1066190SiD\ACK8\ETBk\176915\NAK\DC3U\1026370\1064336n[\DC2-\64779\FS^\18048\57460rZ~\DC1~\DC4\41423&\US\ESCqK\181767|Sil~\DEL\73099\SOj\1033647\t\NUL3\DC1\SOH2}\1005634\NAK]A[oy~jjTg8IK|\1087090\GSH\f\v\n_\1037562\1087259j\US\131846Na#!\"\994063'W\1063542\140058\DLE\NAKO\DEL`m#7E\GS:\GSS\EM\t\EOT\ETXA\46372\1082713\94969\FSXn\r)c\RS\ESC\n8m=.\17764\DC2u\1053932.o" ) ) @@ -97,7 +97,7 @@ testObject_TeamMemberDeleteData_team_11 :: TeamMemberDeleteData testObject_TeamMemberDeleteData_team_11 = newTeamMemberDeleteData ( Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "1Ll\RS\34534^\174257\175909MkP\t\b\r\STX3/|eDY6~V\1113711\DC1\167458\1034114=\GSU\ACKk7\SI\US\17303!<\990890wQ\\\1040390\1051272\b\94985\22086\&0\156251\136523\a\DC4\119343&,}\SYNG?\40804\21765\73890.n\1059294Y\1033675\1037046\1065338' !C~{.\1043901\&2\3495\EM:\SO k\28176\r\1083152G\1056341\&3]'hIw5\ENQ\41457\1082131pO\t\1001743Kyj\vP4l<\134453 \1089988\137386\1075462cM\167556\996189\SO\ETBh\32507#\1058696\1087719)\63687,/w\164733TM[g\1103992\DC2%,)\33162+\1072884\&5\29073>\176421\US\RS\GS\\\98066r\DEL\SI\GS\1005934\t\1024766NQ\r\ETXzEaw\a\36260\&1$\1027881\DC4\1064258\150754\r\CANm\184464\1045472?d\156580\&5i<\t\1019705\1229\DC3\DC3\1019569\1029635\39156\1104313\ENQM,\173969Jq9\1048011\"\1091803&\ENQt=:c \31254\&7\RS\DC4\EOT\1073710,\1018152e\182940\EOT\\\DC1b)&rN\63924\1662b\CAN\a\ACKw=\62799:pC :.@JZ\1039635ST\NAK\DLE\DC1u~*q\19947&\1060285U\1000194~`\1038786bN(\1095328\95172\SUB]e\177173#\1065260\1014422\145171pb@\1038821\DC2\1094471v\SI\"Io\44750\1084407\SUB\RSt\1095234\96769G\139315\SYNi\1018411\1105238d\STX?K\ENQwe\\M\f\v\94803TDSt\\\167265\ENQ\1027562\1059432K+|\19135:\GSM1c\1085404\f\1063513\no\ETX~A \FSA\15958N\148081\180613\&6\83203.\1114060\b\174594\&7W\60092\140470\37198\1062520\ACK\1092950aX\97302\STXp\NUL.%\GShU\1083930_\141735\&7[M\183161t\1029299\142098h@\1030735\&7i\1102217h\SO\917589\1005270\&0\ENQ\ETX\60227RoTUGS\176146\96884\41439\"\140414\66886m\174923\EOT\1086724\165696J!\100746b\NULzBqu34hG;$mj\17247\142630RIoT^C\SYN\ENQ$8\65359\4278b0<\1079791\ESC%\ACK\20579\177890\ta\tv:\16193\n5z\1089394\99725\&4\FS8J7F\NAK9i\1065600\17261\EOT\166084O\US\STX;\1062345\1015887pOSp\1105641\1112276U\ENQ\1111281$F\1008597\1100728 icGo\58334\8659\3191~\187293\SO\ENQp/?n=s=QMU\23415~\96552\184588L\rE\aC\NULD:(`'k \39221=\DC24`\DLE\187956\1047668\1110588~Ql9j6j\4014^cpq)US\148237?\1109928\23514\1108448\36760\164779\DEL\140531;u\1055203#\74225\1053882Rq\985521\ACK\NULhMxe-\DC1/\a\nv\EOT;+7\DLEq\1012515L\1038628\DEL\1064863=k\20158ZhxnI\1042249\v~\FSP3?,\50197Q\tp5\1043599j\1085376i\1011949Zxe\a\1058147}\SYNs\SUB\1007883b\1003254\aC\1005565\STXH0\DC2\159894\SYNwU\1081522\a\100405cr>\1084170\SOL2\1059895d\SI\RS\DC2)3P5%\175808b\SI+ry\ESCV)e\35116\1074165\\\EOT6k\DC25N\96025\1088381\16176]\1054725\1084937\ETX\tRmj4\SUB\EMyh\120686@\v\162149\66003Au*\ETB\994156El\b\ACKz5\178654\182119CtvX\1588075\54939\1043814l\USi\984703Y!\62839V\GS\984833\DLE\985037\65094zy\SOHW\1091036C6\NAK\30817r/w\131690j" ) ) @@ -106,7 +106,7 @@ testObject_TeamMemberDeleteData_team_12 :: TeamMemberDeleteData testObject_TeamMemberDeleteData_team_12 = newTeamMemberDeleteData ( Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "N\24015\1096966\152426\&6;\DLE\ESC\986293p\NUL\RS\GS\1036637s\1068831\185741#\1039560=\254\30296C\DEL\SYN2%1\STX\NAK\vo\987921|p\CAN#\\\137842|\174823\47059\DC4\ESC7\STXy.[\DLE+7y-\141435\SUBJ\1008125\125195\DC1X\GSA\160745\1087091s\54357/\97016\34524\10389\178579\1075396H\120536\32348G?ac{\1038115;\tg\1075879\&44\GS\ACKp\b_VG^'=\UST,T_>\2092[\DLE5\1034860|e6n\83279X\NAK\50007\ESC\98997\1041736\188524)\190032g\fY\EMD\1001279\22718W)C\4517\1078663>\STX%\GSN\1006911\1056058y\127249\160541KOuZC\SUB\SYN\140342b\129197^=\1075733<'|Pyi\nSCq\151464\1100697\ENQ\36310tbce\ETB \\)x{%\19445\1027240\170065\\Ws\"/ U/V~!\SO?\1075540\1002408\24291C\1018130\36697$\1057417Z2p\1071734`\1025978Q\113784M:\16850\DC2\17249(\SYN>k\60064\36158\25265v*+\ACK\1007747<4\28416G1~\1040649Ej\1038847\1051162\7890\1094574\&0m}\ACK\1009925\SOH\1108696U]q\b\1104180N:\1056540\1046913qX\995621<\59058\1102461v\GSxf\1048191o\1043659\1067219o!J0<\f\CANH\1047778c\1070220\EM.\USa\ETX:\te6D\176805(\147806V\1090250\DC1\1091510G\DC2+\t\b\1041420\6096rt\185226f]\"\DLEP\24917\\\RSp\959.\25250y\1061180\RS\992875 > J\ACK\GS\1077353\t\CANw_u\EOTSUK\SI\DEL\32109\SUB0eqO\45283\&6\FS\194603\64746$Gi3WrR3}\NAKx\72987\FS~\33375O*+\99708'r\52817H\182850}\43254jnhc\\X\FS\1065212\176118w\NAK\FS8g\1085031\1035971\30462\RS\fc|*@T{c\1046846R9\4414Wyx\NULB\166171\b\1000149\ETB5PP%\998257\33469\42806\18356\53304e3\1083978\f#p\SO\anj3E\57923\NAKZ\STX\STX\179222\vJT\DC4\FS\NULc07[\29103:;uX\996174\1022756\1009113\SI\SI\ETBj;Ox\1090840<\146330V\163045)a\1039125F\1061869\99435\DC19\174639{\ETX\NAK\RSnhY\as\1108515\&781\DC1\184655\&6\191003\163929?E\ESC5;Z\57918P\SI\STX\77911\36199.\DC1\151435\63672*=\21158 \EOT\1031444p@_cz\1574\ACK\1005966%\137124\&9\142779}P5\153723\NUL\1001057\1096139f\DEL\FS\EOT\vaZI|,SW\DC2k\1106695\41042\&43\ACKc\SOH\DLEy\aW\51068\188928!\7237kj`)\1025092\ETBO\DEL~3S\t\ESC\1008292\&6?vK\DC3u|\STX\71344\1099620\1006702\ESCp\FS\1064473\&81\150803*\28310=Y\32423\27594MS\1020854@cchi\ESCHBpd\1011659x\v\\\47611j\FS\1041333J\ETXC)\1051987\992786\35810s\69973D|$x65\EOTUX\190982\DC41=fn\97134\1015015:\SUB1(\1013941\1095397X\144513\av8\r" ) ) @@ -115,7 +115,7 @@ testObject_TeamMemberDeleteData_team_13 :: TeamMemberDeleteData testObject_TeamMemberDeleteData_team_13 = newTeamMemberDeleteData ( Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\33192\CANmf>67Q>u\FS\1033157\1013261e\DC4^TX}lUk5\EM\SI\1062221\183426\1003185\1018961\163237-\vcw3\USa\r(\63491e\SI\STX>Hr\ETX\EMR\173292)TC\1082129g3h\1052503\48182BT\rA]EZZ\990161\4679\187541\36542=9'\138796\1104496#0\FSw8y\119951^\EOT\179025\1085352\189905\27803\&0oZ4\95790\&4b1\1031410!{n\\d\41996'u\1048056\&3nk@e\149018+<;\ACKMA\97873O0;Zv]\DC3I1\SI7f\187004<\49007E\1055172\1005697r\95478\15501\GSqZDU\44426\98775\1023292P\1100540\SUB\99804" ) ) @@ -130,7 +130,7 @@ testObject_TeamMemberDeleteData_team_16 :: TeamMemberDeleteData testObject_TeamMemberDeleteData_team_16 = newTeamMemberDeleteData ( Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\15589\&9\1009731\r\DEL\EOT\EM9\41649Z 4\NAKP\156456q\1107623\44694I1K[:E\SOH\989180\NAKm4Zu\166320\&7<\NUL@FM\STXW\166474\&1\984219O \1029040\"\n\145724\ETXPI/\ENQ\46933\&7\SI'C\1079683PcJOz\31676\&4ng&\1078347\&3Z\23876\ESCg8_\173330\SOHF`\RS\988197\131318F$\55199\159386qAUfmY\\2A\EMs]:,Rw\SO\DC2\EOT+Cs\1044905_biqB(o*-o\rir\95324\1059030{\DC3Z\b\169146:4\1086959\29148\GS\134036\985712\1014105\43461\160527Q\ETXQ- \EM\SI.\984335\nlw\"\DC4,\44737\ESC\DC1\CAN0K\1106832R\STXICH\1046804`\133120|-X\32935\15322NX\SUB)m\22353h0\DC1%\GS\19066D2w\7944A*5+FN6\SI\139175FN\160058\34604f1#z\nX\175529pBf\NUL\58704\41468V`n\DEL4\CAN0t\FS\CANQ\v\146614i/pEqW\1060666\185103'f\r?\1111432\ACK\1093551\67986a\38991w\SYN\1065227R\1041754\RSB\ETX?\95518MW\983294\t\95385\989237\171802o\23784\83344\1009094i\1016400@\161824\52318\1008772/2=eG\29324\DLE\134835\rAK\f\rqN=\n2\35812\170420l8\171388\1070274&\1042544\60456-\CAN\n@y\1055888\1001662\DC3F\191147\62062\134906:[\FSo\DC2\173167\ESC\52324{\SOH;\CAN\19263\NULf\43645$ILgeS=*\v\50213\SO\a`L" ) ) @@ -139,7 +139,7 @@ testObject_TeamMemberDeleteData_team_17 :: TeamMemberDeleteData testObject_TeamMemberDeleteData_team_17 = newTeamMemberDeleteData ( Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\NAK$el\42683\5047@\1037977xUC\46079\SUB\984210 \1034898\11783\ENQ2\\sB\DC44R\1020811\&6=\1094303hkp\ETB>fA*gw3=h\1074929\119248Djj3Erk\RS\DEL\NAKZt'\1094174be\1067054k+.2\1033368\"\RS_\SOc\NAK'?6\1019443\fS\63091Y\1048579\1026790k\f\ENQ\1002620\148243\US2\1100486\&2\US\42271\va8\7902\EOTXv[T\t1h\1018353\1009026\NAK\143335{\DLE{(Bj\178238\DLEP=N\1077911\141614\ESC[" ) ) @@ -148,7 +148,7 @@ testObject_TeamMemberDeleteData_team_18 :: TeamMemberDeleteData testObject_TeamMemberDeleteData_team_18 = newTeamMemberDeleteData ( Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\SO\1079219,i\4962\51100Nb0\1048541[;\34705}\1107113jN\STX\f\b\f6Sd\1030156\ETXFe\1081304$\DC1A\1085905Am\ESC\b9\SUBD{'R\ETX\1066687\SO\83028l\5663{P_\DC3i\1007228\1061083\NAK1oa\CAN\SOH\42276)gB\61934\ACK\132106[\78401\147755\DELZ\NUL{\1016678\DC2\134030\162548>\1084153\STX\1003940\1003986d\EMC\150107J\184210%< \SUB\GS\188653\EOT!B\1094939X\1111194\133007\61001r@\99686\1027900\1036947\19690$`Ua?kW^\nK\157869'\SUB;\STXL\154380}\ah\34504w$\a\118858ty\1008257\62457\21278@W\1042789\1031954%\16342vYN@\USF\DLE\62983\&3\54525(42\USC&\1012726{u\74184\b*\"\1026390/bw\SYNpU+i\SUB\1042224\NUL+\DC3\1034888P09\1060299WVt~Cy\42460g\SOHCy\1071220C]\b\20822PVy\FSB5\DC4W\1020494I:\"\162082\&3X\DEL1T,5\1069375\154310\SUBr\1026413<+0]\EOTE_\155984\&7:,\1042921<\GS\187683\29177_\1111199\170001\a6CP\1104656~_?a_|\62061\174537\1060915+\1014100\996030\USBR\RS\nC\1025913b9\1065365ng\994951\&7\1103565Ox\1047544:\f1\50312&T\fAH\1012528e\SOq+o\991890\SUB\1113819\ETX\DELP}h\185466\36417\ENQp\36891q$I\174106[\1026758$\1035500!\1067963\DC4co\US7'X\1039952\29286[|\179702w!\32593(?\29162]\92323Q\72725\94677\CAN\1026197?\45844E\136866~\1075966\ENQ2RN'K+\135400\4604\DC2Q_\46484\189580\163388\DC4d\1091178iI#-86,\35575B\1037524p*Y\1013187uKK\1009980Q\1046462z\35560x=t\97302P\31174(9\ETXXe8\EM_bb|V\SYNF{$\ETXv\vEy\f\164128\\~P}\1020151\&3\ETXW\1022741V\tvt8\189285\1049729`%\f\a-{b\25205\1084518?\v\145764]8i\21378k+kMW\ESC\t\172071A\v8M\US\152615nH\37085\SUB\1088289M\1056019\FS\SUBEP\1048269\32788=]\1002635pV6\1086206\DEL.\23007\DC2\64600\136741\1063986\ENQb\163091\NUL\SYN\SUB\1022702\SUBP\162982\1069106.0\185390\100339\1086931\&1B|a\DEL\1029066\17427Z\1100818i\1025501\r&\GS\132748Ao\1101364\38548\1094390\&0,\1001094\983565\&2\1057507\17016\SYN\1050118\994737u{0V'b48~Rd93\DC3\NUL d\26337:\140599T\27679\&6\SOH\DLE|\CAN\1031097r\137862_H\145870\"\\;\1084993\1105865)\167064,_\DEL\157730{}_%yQ1\ESC7^_\148072\ETBRct#\95498\DC4ZH\1100286\1107582/\1047685C=)\SOb7`\FSK\165067\\~w\3372\94439\&6\n\984172l\1074299J\47565aD\997148)z\NAK\SO\19016\SUBG3gX\165806\1099885:\SI\2819>3-\t\a2\STX\991793\n%\"\111160J\1080702)8\35288\99824q\SYN\1060294ka\1014899\nbQ\1009495\1104622\bn+4\1078466_\\Y_+N\SOn\1013318\NUL_\SI8+r\GS0\"\SUB(so\RS\92614\1098264!\175744\t\vr~N)>z\DLEp\1043039\&4%\GS\FS<\SUB\DELM\20912z\1110322d)OyH\1092079s\1037828\EMzo\1093836\DC1l4t\RS5=\STX\17846g\US^1d\165600*S\f\14837]\187531e`<\140390f\n9\NUL\STXY>P\73789db0MJ0/\1036427&5#\NAK_d]X\SYN\EOT:f::k\146477\15313\1070795\162330g(%%$d\168206xh{6l\EM`" ) ) @@ -157,7 +157,7 @@ testObject_TeamMemberDeleteData_team_19 :: TeamMemberDeleteData testObject_TeamMemberDeleteData_team_19 = newTeamMemberDeleteData ( Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\129541L\FSt\1054063\EMaig=\1042199dVJ7T\SOH\95781Z\r\RSy\ESC_\994179.\DC4\f|#\1024498\&1(@\SYN\SUB-]\1071062N\DEL|\\B{\SOH\EML\ETX\99216M\1069825\1004866\&7i\STX\163430F\49733e~H(&qR\1106972\1079613S/\SYN\STX_8'Gya4\t\1007953\SIb\EOT\172418I\64897\20465\157700:\1033011mA&}[\1053360\\H\SO^\1049978{b.N\CAN\ACKM.\22849\133597,\1016072\136101v*\167634x85S\146648\53251)|B'\ACK\EOT)3ie\1051932\\eq^L\ENQN\1006955J8\6187\\!\DEL\160575~\1048251)\176136\1021620\GSNu\1104674\1084405d4\SYN'2\121037m}7\63903O\"\DC3\1052921V\GSfM;Hdz\NULx\43344\1025245\f\148662\"N\986904\&0#\DC4Wccw\987243\44393#6\DC3T8*]\SO\SIu%\17390w>\b\RS\ngiKb\DC3\1085436~ily\v\62330#HD\94733\60525sG:*q8\DEL\1103154F\1081811e\DLE\DLE\7010\46653:\174810X#\984693\171379t\CAN{:\189202\tV@+A\\T\171737\ETBt\168962p\STX>\1060117\DLE\1003224\DC4T+I\8831}\EOTy?f\FS?*\1055790`\1030884\1011090\1029282\182887\174479^\1090217q\19518\RS\120379\NAK\170306!3vvq\STXG\1201\SUBprsw\SIsWq\1069664\54727\FSSNsA\ESC\140621\41759\43082>\167361 ij*U/\ETX\ENQ\ESC|\ACK;\1024654\1050844Y4zE[\52625\&5\39430\&0#ig\a.=\26067\983928~\SOH@kF;&E\137572Id9\1088361\GS\n<%\167117t#T\1090476\60634\&1\n\acU^V@\986937\1099714\1059584\n4\f_\SYN\71425\174798*\164248\180165H\2462QN@{\46850\1079287\v\EOT\1038410\ENQ4p\STXUG+\EM\DC3cy\57678\1042964\v\\\r{\167921\1035992[=+aKCx'\40553QB;a1\ETX@w3-\49941d3I)\SYN]\1105449\177148H\t+)\54770\SOH!{GH\168949\163779$E\ESC\1010685S<:rO*w!1!)\SO\FS\1111132?x`\SUB\b\989769g{\180761\US\1058151\US[|7\170621$\1014302;h\154644\989641\ACK\1065289\1049416MO3" ) ) @@ -166,7 +166,7 @@ testObject_TeamMemberDeleteData_team_20 :: TeamMemberDeleteData testObject_TeamMemberDeleteData_team_20 = newTeamMemberDeleteData ( Just - ( PlainTextPassword + ( plainTextPassword6Unsafe "\99268.7|\989650\989527\SYN~\STX\GS\a\n\100072j:Fu(f}F\142706M<<\42207H\1026851:\1021905A7\DELDb\62704\1032296\CANHDS\96785LG\146296^D=\1066899\1112771,N:\93816\181806\bPN\132709\"\DC220\97472\v\1022037\994785c]O\1050942\1021766:d\120878\1006623>c\DC1\DC3!p5e\997004\SOH\DC32\\\v~\1049017Ix\DELu4E'_\ETB\19021\ACK\DC3nG\SI \139631t6^\1043287^\1106414\1038014$7\SIo\34377\1055920\1002847j\US|`\rX\n\1112600mfJ\70356\RS\1101324\1066840\996159\134937\1078722\ACK\DC3\1090413\175393jR[8-lIu\ESCj$\1111365\1094018\NAKXB\1059040y(\17513E<\1049359\ENQ~B\1000631\DC2SLP\ESC#\135206\NAK\"\150906W*\NAK>\190656d\1011790I\131237:DiJ\9608og6P'\1100312\r%\1017518tSnw\1086322 l''\ACK\999475\30076~\22053\135026\&9w>\22790=U9\NAKa=\174354\61188\994592\&4\SUBm\1100093\1084496L\RSl\SOH`\\3\1046308qG:\SOHB*8U \rA\57700Z\r\99255G\SOHhU)/\189005\ETB4\SO\1063749]\GSEQ\DEL\986000\SUB.\61863\1033029\ETB\1073587J+\1010590\DLEee>4Y%/Ezh\1071046.5\143076]\SO\1033005{>\v\DC4\NUL\1018635=I\ESC\NUL\\p\RS\1088873`r\ESC'\SO\11831Ga\DEL^\1006920;\DLEXP\EM;^\176088\DC1\994395\&4;\69944\vg:w\46754f\v\1044970i\EMya\EOT\28251RSt.Vw\186568\140191Tr}hTw\SI\177118\n$)\1016142^q\ACKCAZK1WR\v\ETXy\ESCN\133370\1069365oV\SI\CAN$u}9;;:5\135813U\22444HA\SUB\1058069v\1021302Me\61659v\42245R}l\33891U5\nHA\CAN\EOT\DC4=\ETBy\RS}Hny$\\\1028178\60530\991485#\1108031\1104020]\996629>\984614w}\1083618`k\ENQ\177780V\ETX\1105900\RSAx~_9q>\65915\191406\1016510kd*\48536\&5\SUBOn\DLE\\ns" ) ) diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/UpdateServiceConn_provider.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/UpdateServiceConn_provider.hs index e9b91d4302..d2a41b87b3 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/UpdateServiceConn_provider.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/UpdateServiceConn_provider.hs @@ -20,7 +20,7 @@ module Test.Wire.API.Golden.Generated.UpdateServiceConn_provider where import Data.Coerce (coerce) -import Data.Misc (HttpsUrl (HttpsUrl), PlainTextPassword (PlainTextPassword)) +import Data.Misc (HttpsUrl (HttpsUrl), plainTextPassword6Unsafe) import Data.PEM (PEM (PEM, pemContent, pemHeader, pemName)) import Data.Range (unsafeRange) import Data.Text.Ascii (AsciiChars (validate)) @@ -51,7 +51,7 @@ testObject_UpdateServiceConn_provider_1 :: UpdateServiceConn testObject_UpdateServiceConn_provider_1 = UpdateServiceConn { updateServiceConnPassword = - PlainTextPassword + plainTextPassword6Unsafe "\48023\US\EOT-)]~A\6084X\158541\1085038\&5\49967:=\CAN\1042311\1110226\98388zHH\94299[Bn\9081\151207I87\DLEbJ\ETB]\158065\1042093Xx\167446\&5q\194776WjV.\141689xX\4761A\131712\4959\1043857m\27816\1066578tf\98275Q]\162246v[$\1041185&7\SUB+\60975\SO\1022130\60565\RS$~\176589\SOH\a+\US\47262\995553\ENQ\984394$1]\139626\995152\FS_\9559\1112532^M:\SYNnj\EOT\1053023\12419O3\SYN`j3\NAK=\1027692\&8\t\1023383\27247\RS9pY~+\1060011\3990\v8vx'Sf{\EOTUu\1003780\STXoJ\"~E\EMpo\FS=\STX\151702&U\STX\SOhM\135675f\RSr\DEL`F/&WR-\ETXP~V:\NUL\155119\fl.\135176\DC2\1020429Y\1779\b&ZX\v\1011849\DC2\51384\t]\983559\":\7506\GS\"\182388\&5$\1002096i\160424\1101600&6\127976S\30272\SYN\SUBC\1012663\EM\994623V\47942a\1041770]\r\EOTk/#\f{\159982\1022881a\150434\&8\DC2m\1011420\n,\ETB\1037975\61278<9\1052021\138859\1103888\EMl9iQ#y&\1045035+\162880\SUB\157158\186690mtb\FS\ENQF.\1044807\ETB\US/X{\GS\DEL:^)_\EM\"\SUB\180660*u\127154qn\t7P\CAN|b\32170\10673$\"SQ?E\992071\988250\NUL<\\\188234\&5.0\1044422g6d\NULA=n\tx-Hi3DU\1042619\179566=Yo5,\163525S\167821$/>\SYN\174673\b8z\1054067\1057469\&6!IG\DC3\ETX_m\182211Q\178659\bm\GS\5667l=0 \50133tA%\DEL\139117[}P\SYN\163285\GSb\"hw\34294\ACK\vJ1}\1037364$%\1089500C\138271?(\v\57736\v\154898\1048679\SO)Bj\ESCi\52062i\RS\1110207\EM\33516)\1013786V\121251)BM\ETX\30148\&3a\191006\&2\1051182\DLE>l\1012313\v\DC4\26436\1106068\DEL\ETB\44487\6721!#\SO\992108\70057\38800q\NULX\DC1F8\RS*7mPn\ENQW\SOif,\146459\68801\1081967g\atWo\SI|\166891\1095803W&/)\SYNb\1083839<\CANC\RS\55229a#\1027399\&3\1023861\983662wR\DC1 \1029712|/e\1041457\1078751\"&\ESCV\9896fA~\21012\GS\66884@\ETB\DC2\ESC!\vTJt\NUL\138082\NAK}s\SO,\FSy\SIEnElBS[-\149460lN\152753o\GS0jj\DEL?", updateServiceConnUrl = Just @@ -102,7 +102,7 @@ testObject_UpdateServiceConn_provider_2 :: UpdateServiceConn testObject_UpdateServiceConn_provider_2 = UpdateServiceConn { updateServiceConnPassword = - PlainTextPassword + plainTextPassword6Unsafe "+)| \ENQu\132121[|G`|\1040791\15047\&95GpkVm\179149\&9'\1095291y\DC4N\1090395\&2;f%\ESC\163769\6676\&9}\US\23801)\1004419\DC2\174995w\DEL\v82^\1113829jtZ\881b\ENQ\181571T\167461\993132\EM\1058779\66324\ESC)\ESC\GS\145456D\SOH2\"\78054G\1034108\1007724p\SUBV \156796[\176190\&9t\EM_\39958\1066046\a*DVE\131211'&Ls\990176\tt|\992680kk8\FS\97637e\1082040\aTi\145584q\DC4\1015584kR4\1051046\DC1vZBp)%)7\1049932\1067472m\NUL\61327I\ETX\1059016u\DEL\1042762\NAKi\1107524\1081325\ACK\CAN\1097686w,\DEL\DC4\"\44527\&9y\ENQ2A\DLE@\184152O~/rQ#\a\95564\19393MZ?\40205\161527\n\1052423\176558dHa\bS[Pg\DEL\182722}\t\n\164475\190962{\53676,\US\1004610\&3=f]p\1071518&\RS\STX&\8086\1054341:,\DLE\ETBx\1049389\&2\\\991260\SI\1043333\NAK8R,?6\DC4\65761\SUB\989022\DC1_HHSk\SUBopnH\tE\1076132\43655\&5/\STX\45409\a{\ENQ\ETX\1083721M~Y\144193\1073005\142836\&1\988121\1048654\992897\&1|l<\1031839\rPi\133054\1101047ABh7\27814\96862{N:uw3\151854p(h\DELwN\SI\NAKUf\1102463\150103P\ENQ\1074920q\NAK_HJ\1034658\1101595\v\EM\16883|K|\SO\ACK6yS\1019630k\20733\t+Nx;\1017121r\SYNQ/;s\GS\1045420:*G\164017C\ETXm\ACK#\1000114\12877V\169274\&7,\"r;\58557z\SUB7!YYI\61386A-wC\1086129c\1010103\28026&rKJ\r#7Nq4o\1006018\n\1055756}\EOTQ\ayIwA\111034\SYN\1075090\1003496'Y\47832}\SYNYc\47414#\27767Y\SI\16751\164771t,Zc\30393\ETBXP5-\NAK\1091008H\ESC2\1105144\185806\62391b+u.+\1002917,^s\ESC+\v\998922MTe\141056\DC1`\1100336~s4r^\EOT\1090306\rnEW\1007431X\1095464D\1108330 \141831\DEL\163685$\NUL\152132BS\1094612n<\GS!,fL\SYN\1019156\1089303\162030\184646xu\tVR\10264SvgXL_\1006409\&43\68768$>\SYN\NUL\RS:\171701\8999\1096643\GS\"U\"_\54854:\ACK\29845f\ETX)\9816yBK[KJ\DC2\1060909\a\7287l\1025318M[\DC4:EGBo\DELflD4\1011645L\ranZiv&'- ]\2070\fB[\v\1028002\1088988d{(&6\1091108O\SI\DC4\1080293\SOH\1089060`\12769\1101797\47171Hhq\160300H2r\39026\15463\US\r8\92242\1002459\&1\r0\ENQ\a\1078486\SYN\995748QQ\ENQdt\1093632\1086005#'\ETXK^\1064639(\SUB\990804N)M\11804\1092898\1002195cB}\1948\1095791\SYN\1046504JZt\NUL\1018901w\t=\164602ya}(\SO\b\996327e\94822\DC3~\1044914\29528)9\1080009\1099690nI1\23611#\9881\&1\1007335qFG\6500X9zM[t\44727ii\RS\r6JQ\SI3Q\FS\1063991Nq\26275\DLE\172731\141475[\1111927k\142278\ETX+nbs\RS0W3\US\1019367\NUL'\al\aIbN\DC1\NAK\ESC9\15908\155439?(\1027259oT[w\163780\66760.\177719\ENQ IlW\17013H\n\r81\v\EMAC\1111637oN\25386Dtg\191292\&3'\1037882S{\ACK\1071846l\998294\1020722oi\ESC!,\f\1073852\1034280\&0\184139{a\1060324\145065#p-\GSX\EOT\bI\DELq\DLEzo9U\DC4t\r", updateServiceConnUrl = Nothing, updateServiceConnKeys = Nothing, @@ -120,7 +120,7 @@ testObject_UpdateServiceConn_provider_3 :: UpdateServiceConn testObject_UpdateServiceConn_provider_3 = UpdateServiceConn { updateServiceConnPassword = - PlainTextPassword + plainTextPassword6Unsafe ",,\ESC\DC2i\40982F\DC4O8\164360P\1110158`\EM-{A}3\144146\EM\59157K\60476QK~+,X\28979sTF\RSCF.i#\1110927K\1037977Q\185888~'b\DEL)k\vJp\1013700B\ACK\164756\1026430", updateServiceConnUrl = Just @@ -149,7 +149,7 @@ testObject_UpdateServiceConn_provider_4 :: UpdateServiceConn testObject_UpdateServiceConn_provider_4 = UpdateServiceConn { updateServiceConnPassword = - PlainTextPassword + plainTextPassword6Unsafe "\998823X\51416x:L\1108503~\1081065\179896^u\147090`\v=\EM\17195\SI6\1084185\1110421\1014174Q\NAKp\1005116\191234\1072050\186595\1110889c\129596W\1053917g\175490/\DLEc?\13917a\1033729%=0@\GS?\ETXs'\1018967\168225cQFfA\1020720\ACK`6Vyu\1087659\SYN\SIQ\165109\1073798\62456o`\1072757\GS\r\\}y;\984161jCk\fg$\a}\SI\USh5,\ENQc\1048050m\10195\&1\59237\&7@2\f,-U(Z\1086790&\16311\164166\STXR$\UScB?\1027375\vs\SO&\t*;\1099821aj5\1011812\28555\162408\SI\DLEI\985837\1059736\GS|\95430\&5\DC2\aXO`\185053):\169868\NULl\b\1087087\&9\SUBz\5115\&1\NAKC\1070536c\DC4$ZY\151608:\atq \ESC&>\EOTdlu~\140630\98361^-\n\ab\RS\20775g\NUL\ENQ\1001283 qy;<\24769\SI\EM\DLE?\125026m54\NUL2)y1\SO\ETX\1106368\1076724\"V[\1035849\SOH]\988558i\121137O\v;\5801\CANB\180951\EM\1110465\NUL\1070697P\110997\996463\1098272\&8L\nC\1058911D\US~\EOT)\1106263\49650a\ENQWn\9909*\137125\1107951\EOT{\CANbCzzGW\DLE\1007282\131870r(\33868\&5T\SOH\39403\US\NULa\1030299DS}r\"yz\DLEs\ETB\1097590,dP\96305\1090751\ACK[\179037e\1076353mD!T\1008638\v^\70675T\EM>}A61\ETXm\DLE_7f\SYN\DC3a\16741\SYN]\1101678\1018543\&9s\DC2O~a*:O\SO\36905\26464?\NAK\1006010Lp\120936XI\127258o=e\fp\GS-\"\1078156\&9h\1089507?|~\SIa/FX\USl\US\1014043\190432uX\1059318%]qlfXxxLP1g\DC2r(Jjm\37174\134955_v\1022678j\SO\33008\&3\52949SY\SUB\n\48504G\DLE\ETXn\145113\f\1001617J\NAKD(ns.M\1046950A\94402\992891XI\996351\987337\36063\&6m\994039\FSS\1057973+!\183589\144687\ENQ\SYNY\f-e\ACK\EOT\EOTW\1094735\DC1,?3\STX+\1103278\38508.\167813\&2\"\1052642\ACKI\SOH\GS#\RStJ \11809\DC4,}.wo\1016501", updateServiceConnUrl = Just @@ -191,7 +191,7 @@ testObject_UpdateServiceConn_provider_5 :: UpdateServiceConn testObject_UpdateServiceConn_provider_5 = UpdateServiceConn { updateServiceConnPassword = - PlainTextPassword + plainTextPassword6Unsafe "]\ETBN`w}I\1077225\1063207\194886\ESCb\RSw\1009412\45450\1037598\RSTlqD\51158\161489x\b\1073534\991958\US\SOH6BZ \DC2\1111505\1041340\74089n]v|\1001629\ENQ+\b\1068414K\1096643H\SI\FS-9Of\42179ST=\STX\160642%\1026333i\ACK\1092593\155629,\SUB8;2\DLEF{\DEL\147933/5g\1061459\1108739T\EM6{l,#\SIVG\SOH\1019450e#u~#=\161137]6\1081794t \DC2/q1\NUL!\1015690\&6]g@o/\fR\DLE\1016108\27347VY\1091689R^\48943\35925Tqj(}\156901\ACKem\99629\SI\1017747\136120\121040\\\1092184q\ESCbxFQY\US\1106578!C3V>|\1095264\NAK\1045860h\RS\182757wl'\1067837I\1028704\a<:\182006(9E0j\35838~\14622f\\\DC38a1[N\ACK\RS\b\GSE\\^K\SOH(\166682\&6\tf\61599\DEL\ETX\999448Y<\22136Q\ENQY(%$Iy\fE\GS@G#\180989\171711\DC3\1034013\1035014\20714\SOK\1095577wS\44294\36694{\DC3Am\28623C\1083349\ne\179359p\1065578N\9086F\FSxfT\n\172966j\1046025\DLESk\1110958\1031038xar2\160384\&1\173990\141065\1037577\SOH\51109Y>\SOH\151803Sr\f\994611\1025721f\1013214\DC1\12375\67110L\NAKS\DC4\9494'aLM\GSl\ENQ5Q\r\b{+\1010407\54694\STX\3170E/\1038169\1101732\163194\1071944%O\t\50278E&\1097597aVEdM\1031226B#Sk\1063346,`=o\61550U\SYNP%$\ESCq\1106926U\t\127854n\58958\ETB\ETBWhVB\SOb\127121[T\155401\187876\169584+yf\1003534LJ\FS\SOHnn%\58734-\vM\NAKz\186535\174616jF\1112890-j\FSy\1056822ee\59349\RS\RS\DC3L*grCi'l#h|\1004844i0H?\164702&5\1002827nlD\25298\993777Z`\SOH\RS\SUBY{0\1054005\GS-4[W~?\DC36\1011105\&9\ETXb!mB\ESC\\.P\1087523igO\DC49\SYNF\131796\1040687}\4110q8\NAKYMS\1002659\2652p\1065434@f/\1099324\DC4\187209\1051638x\47542K\ENQ\998157t1`\54485\1017782\&9%&\SOHb\DLE]\181021&\25645u\1051933\1060980\EM}\32354smg?x\1048733\39344o\154541&\1053210\&1'\DC1\ETBO\SUBv8\161106\987513Pe9mK\33543B\1010759!j\1067279\186235\RS4c@M\DEL?\a\ETBc\1100803\5649#\994290\ESC(\1099246\1012906\DC4\111062@s", updateServiceConnUrl = Nothing, updateServiceConnKeys = @@ -413,7 +413,7 @@ testObject_UpdateServiceConn_provider_10 :: UpdateServiceConn testObject_UpdateServiceConn_provider_10 = UpdateServiceConn { updateServiceConnPassword = - PlainTextPassword + plainTextPassword6Unsafe "\997769\&5y\SOH\1077253-\STXv\ENQ'\1013305\187122\157714S{\rL\1081690]\n/\157912\58428aHB\24264WD\66619\&7>&\bP\"\1017643\b\1089515q\a\183348\r\SOR\1014552\176079\ETX\"*\828\DC2\1043428\1000500N_\1005066uF\NUL\"\DLE\tRIL\1078390\1098873[\EM\ETB.u\10586\1006943\"UiJ;Kd8\1039008\&0\30306&\f\SI\162744\SUBY\1008806LK\61093\DC2\DC4z\SOHva\EOT\12884!pV#\1104879+=\1100776\18104\CANm3\ETX\9066\63172||\169448^\96706k\1023330Z#la\59350 '9b\1113666\&7mX]2iWb\n\190991\1086837*2%\1021942Rqs$z>J\1015846B\1059046\1014472\&5|\n\ETX\1083565Y\133520\151004f\EMN\1008112`\151361\&9CE\1004364\ENQiU$%\1108721<\1051653;\1052829\1018452!\aF\SIv\172482\&1fx\1084389pz\NUL\SOH4m\158767&m\SOHzU\STXai\DEL\r\EOT{L^\1069351I)\EM0^T'pV\189557\142219{\33681?'\t1\b\ETB\1003846k2N\CAN]}\DC1\DLE,\164970\1071435>\11135U\190941u\vZ\th\SOI@\ETBe\f)2mg6}:s0\NULa\132591m1\EM[/-$\169856\CAN/-6\NUL\\P\f\fq\8201D~fp\1014825\SIRz\1026058\ESC\170772}c\vh\\\NUL\1005676:CY.+\150506\1018750$\r\77874\48956/\CANi\SI\"!m[\"M\1110323*.u\8922\NUL\ESC5C:\96606j\SO+6\64030x:J\GS\142277\"P~\v\1106653\1031178 azK\1045557C9\SUBH\EM\132709\1106185\EMD\59381\DEL\ETBa\a\"w!\vO[\1002646 c\DC3\152706\&8\DC1\ACKu\147193\r\FS{!\SUB\44738\ETBmM\1054254o\ESC\DC4\f\1501E\SYN'N\137549#\1079995\DEL\1040911\DC1\SOH\169691\&1nizru_\1080817\DEL\174475\SYN61\1075510\SUB\DLE\SUBxQ\6157-*zD\183523\GS\1271\ACKIx\DC1F\41942\1016837~\bq^\DC1G\59001E\24917\1017983n$\168123-j*\92680h6%^F\CAN_u4Ef\58125\1113047\&9\bV578\33478\142522F>\20387y\39307\SYNrW\DC36\n\991819`BmCl\t\1055055\&1\184705\131098\1054689\f\DC1.N\GSD\t\190261", updateServiceConnUrl = Nothing, updateServiceConnKeys = @@ -447,7 +447,7 @@ testObject_UpdateServiceConn_provider_11 :: UpdateServiceConn testObject_UpdateServiceConn_provider_11 = UpdateServiceConn { updateServiceConnPassword = - PlainTextPassword + plainTextPassword6Unsafe "\NUL\1039410+\1068094\f\32165=\26661\&6\96912uix\SOFI\1074896;Oy5|*sB`\rz\SUB\39196#il~3p\SO\136450\STXR1\1035394\&1y!\n:\FS\1030524%wbKet\986807\32629\ETXh\39291}Y\127366\EM,lSI\1047988$\171754X\184873X\GS&\SI\ETB\v\1034870\1008812cCR0i\163769m\ETB\1045128wIC\SYN\n\DC4\ACKY,S\1098786m\16798\a\187665\USpF~\CAN#\1103408\bAh\1046849\"\181489\ENQ\EOT~s\ENQ\DC3\t\992102I_xZ\ETX\988002s(\42396DA\1005736\1094958\133185\DLEL\31943(\b6i\DC4j\160392>\b\1092152\&8\ETX=3\rFz\50418{,g`\CAN.\GSC>s$\DC1cV }@O\995276\129551\tb\164051\ETX\1090390\166063\&6\SI\17512\&8lR|\ESC9c\NAK\1067118[\1084738\1082491j\1028113\SYN\rSR\1065825\GSg\EOT|\SUB\ETX\99553\1025396R>\RS\95055b(\1001611zP\1049004{NF\1110583gq\NUL\1061911s\DEL<\1098832!?\r\CAN5\1048092\1004099\DC4jW\STX\1062849Ib\CAN\149511\DELef\SYN&z\5327\186881l\CAN\175815x\n\SOHiE\1086555\157602zw\DC1\1073863|\1056621`*e\SI\SOH\n\1095029\&9\22631\1017717TgHL*4KU\29116\1038790d{\5770\1008429aF}c\29509lAV\SYN\SUBo\60764\GS\v \DLENnJmz\7285\v\98968X\n\1062559/)\tV!\151950#xH`qG\FS+\1022894v\1112591A+}pK\NUL\18200\DLE\1014161\39367@XB\1022649\fd`\r}HA\1098736O6F\b\1106094\176048\DC2E$5\CAN\SI\STX\ENQI\110776l\125049\1038537-\181021P\1008889\NAK\b4&Y6k\1049678;\1113712l\18726\1027540[\139508\"\CANW\110623P\STX\1011964\989283GMC\186990#\1016158_DD\STX\DC1\ACK\58642\1021046\175312\16600\SUB\8585\&4\EMI!1\FS|\t\r\DC1)\26943\DC10@", updateServiceConnUrl = Nothing, updateServiceConnKeys = Nothing, @@ -459,7 +459,7 @@ testObject_UpdateServiceConn_provider_12 :: UpdateServiceConn testObject_UpdateServiceConn_provider_12 = UpdateServiceConn { updateServiceConnPassword = - PlainTextPassword + plainTextPassword6Unsafe "\993220\1038660JXV''\GS+-'\SUB\170581\US-7\43896Xr&D\184991\NAK!l\DC4\180616\&37T2K\DC30\165699\1028198\FSf\1034228\78643a0\1040973\166882g\EOT5C\41427\18581\SI\DLEi\172589|\1023099|j|Z\1027919Hc\1090518 \1062911q\\\ACKF\ESC\25422\CAN\SO c>\74396ca\96458\&1\142138\35173\1004117\DC23G\"v7\CAN\EOT\1099295g\f\1107486\ACK\SUBEhi\GS\EOT9\SOHv\1080551e\"7\DC4\43597E\98124\r)\1102009\rw/Iis\1025536\SOH\97931\&4E^\27334m\1048941\1007679O'\48945A\1079964\19956\n~SSEJ\165849\DLE\EOTcL\1045161c\DC3\1016438`8\DC4Es2\RS(6lDMD~\NUL^\121204\1025259\1050222\SUBw3x\ENQ/g!}~\vR]\67993\37327\SO\ETX.X1\983377z\136253\CAN\CAN\144168+\1071342V5\165416\1054183\183010XQ\187880\7622\1077469I!kea\1097869\DEL4@\"\t\1078208\1099149\43628M\SYN\1065348dp5\1001583Hz+\1022080\83262\DEL8\STX\ENQ\SI\19782V\61880w\194717\170930\NUL(\176178$9\DC3g\25394\1046505\&2S;\t\ACK\DC3\EOT\SO\NULj\CAN)z|\SYN/\1041123%\t\181144\72411^D`0\DC2\1067402\1107058\984800\1005844\120958\149529\1049220\1002522\NULfgh\SYN4Td\129587J\1109052\FS\37807caG,Si\140100W\1091163E6\1066725\FSC2\8707+\n\144629fn}\1068169\17347\1014616\SUBV1%\SYN\157558R\990269\14875+F\984275?7\126233a\")G/=\vRx\1080985\63164\13794\1011824\NUL\EM\DC27jQ1S*XcO\17051\1107557;)ls`6\DELe\SI\1033603U\111261\96008RMf~q\140619`v\ACK\1053032\&0\GS/y@oF\1013954\RSr\1051855\CANA\ACK($'~\1100152x\EM4(\n~quJP\1110016\1014656^2D\tw\EOT\999641t\1007432W\1028093\ESCSJ.\DC2}\RS\1035745\97267~\ACK8M\146611\1051882tz3[Yv\27460~CC\ETXc\28165Of\1112868\GSn\1109968TNm\SIxU\ESC", updateServiceConnUrl = Just @@ -516,7 +516,7 @@ testObject_UpdateServiceConn_provider_13 :: UpdateServiceConn testObject_UpdateServiceConn_provider_13 = UpdateServiceConn { updateServiceConnPassword = - PlainTextPassword + plainTextPassword6Unsafe "t\GSC\1000753\bX\164154kx?\1013629L\36559\&7Is\DLE\1095203\986589\&1\28485Jd\95666\993761\n\36454&=\DC2\189436\r,2\EM\SOHpH\CAN0C#\\/\1056247O\SYNdDG-E\SO\142275\SO&\DC4(L\fZ8\1006244\1000574f\190213\1112952@\SOH(&\186075L\31730f\DC2Pn\73894h\1002020 vjw\SYN\1008529:znpI\SUBu\DELE\1065996\187117\146380f\1065951y:_\1094517d\EMr@#\194907\&06\1039784\SIaSq\169253=jJ\ENQWTB\42831\ENQ\b\a{e\12482\1130,\133300\1049410\1054859\US", updateServiceConnUrl = Just @@ -545,7 +545,7 @@ testObject_UpdateServiceConn_provider_14 :: UpdateServiceConn testObject_UpdateServiceConn_provider_14 = UpdateServiceConn { updateServiceConnPassword = - PlainTextPassword + plainTextPassword6Unsafe "\190394h\SUB\1109797\DC4*\RS\EM;\NUL\"0\EMUoX\1029877\ETX9L\1052240\ACKm\30939\169455\&7yc]l9N\1008545'\1102625oHl\1105755\1019260]xQR\707\1101356\143488__@7\33574\47923m\983514U9\42346-)\n/\EMT-R.\FSR\GShI,J\149880\ETBo.\21840\ENQ\1058903YP\1056152X\1100531l\48771\13939T\1015540q\SOH?~_|\121117\39498\1057936\NUL\175881\&2^=hBye$Gr4\EOT\61957S\\R\DC1\DC2,\ETB]\1045067\RS,]\46572{A\DC3]B[\999576f8s\ESC+VGgmTkj\US\"I{W\29261;/ 'N\168508\1092432\70364\1042873$L\EMno-\SOH\5386\1037350\1052214\CANO!OHH=7\DC1Kcj\36365T5899\DC3+3\152617PTHk/\1052286\1109078?@\\uDf0\DC19\FS&N\1040430nkE\SOHE*\27176\1029316\1002801\1034060d}\1022512FX`FD\DC3\1095997C5d:g\177379\1085981\by5{'\DC2\v8M:\DC4\19403\157453\&6Js0!\ESCbT;g\b\141132\&3^6\DC2U\1070466!z\1054801H\1079152Dr\ESCIV\166596L\CANlh6D-p9\SI\ETXrvV\ETB\"\SI\a\ETX\989243l%{c\1054177\987256\1018036\1050434R\\\1039005\STX\159894H1+\15160\&0\SOXqM\10186\&1c\GSo{Q\SI7{Zb\151593\&3\1021654\183743\t\136248}\NAK\GS\95886\1092115\997138\&0Nij\ETX\t\92506\1021352C\13748\35262[\1049660\SI\1000937\SO9\1013277\t\1032553\DC4Z(\63140\ACKB\128501\&0d1\26793\&2uhz}\987497\SO4,>L$\1060453aUv\1043860B7!\132218u\176663\USQM'\ESCFI|\991412\1061444&A\STXO\CAN%ga\NUL.h\ETX\SYNp\987112\993913u\ETB\986350u\1007673y\1080137>\1003299a1%\b-E*\97670uh-_!T\40834\166613*\ETB\DC1\1023495\32162k\74053D\985690\32642%J\95157H16\119596\\U\170700\1030522\61957l8M\1086340G\30550]\146680\171952\b%S\RS\1036496\1064001Q|BQ\1069432\92302:KO9z97l\SUB\158540\SO\1082542]c\ETX\140799o\1083227c2\n\DLE\RSF\1027349b\1050948\SOH\vp]\\o-\1021196c\ETB3]\DC4\SYNt\SUB4\1049581\10708Os|!fmz\63956>\2632N>\24775G\1086284\178948#\11371E#,\128740\NUL}\180512M\1030210j\1025092\nV\1086401\98223\&3D`7\EM\NAKv9$8Y\DC2\994529\1034217\ETB\150192b^\986967\53183Truf0b^x;:\11795\1084517\39347\26525\SO C:i\1023504\b<}\1053280)B\1050491\DLE\52672sD\1063444*+Gz>\1052360)4R\ti.\SUB\ESCp\ESC\DC4(I\16719\1034269W_\1017734\1075210Y\fg`P\DC3n\157709w0m\DC3ec#<8)\DC4|#\SOn$\38394\NUL|ejd\ENQ\1108747\58097EA\\Cz\1102504\GS&G\GS\170391OwH\50355r\1003495\188221G(\ACKuN\ESC\1097964k-\1065205\DC1Vz\US\ACK\153795\ESC\1080576&\990206D\1018960X\ENQw/sx\54555P@EP\1069026c:\"\134166N2|M\FS\ETX-|\39506})Htm}\NAK\SYN\ESC<\1032423\1055241\\\1012449\168010\&0@7??AkY\1096614Z4\1053341&\68619\&6\v|\21375E\vDR\998672\&5\DLE^\NUL\163478L\n?0a]*mQBV\1017677\NAK9\EM\SUB\57722\SYN1JfI'\DC1}\1034409\EOT_D\171988\1043457\DC4\18796\"hU)on5\27639\SOH0s<{`\NAKl\f9\\\NAK\8614x\bN{\1027748\1023446\US\US\66723#_", updateServiceConnUrl = Just @@ -593,7 +593,7 @@ testObject_UpdateServiceConn_provider_15 :: UpdateServiceConn testObject_UpdateServiceConn_provider_15 = UpdateServiceConn { updateServiceConnPassword = - PlainTextPassword + plainTextPassword6Unsafe "\SOH)1l\987683\148155[y6\\ht4;\DC37\1054742P\992335GZ\SOH`-\1084634\168490xF\ENQF\DC3\1049610iP*\20000)5\NAK\DC3q#4t+$#j\DC4{1\1097854\&7\126236\GS{\1056548a\1107816\1054171\STX{\48079z\1054195/5O\1081905\27127&(o\39424~\984292\24987\ESCU&\ESC\1088756\v\998764\ACK\t\RSgFXNig\DC1@8\SO\ETXqp\EOT2\r\1077703h\1090197\1037670y\65729\1094478\1078657\1055314$N\DC3\35281r\DEL\534\v\SOH\1005065~J1a&\156371Lz0Y-\a\ESC56\39613\1018854{:0\tCG]68a\ESC\1093341\77856\FSh6\FSM>rU\1015613s\DC3_3<\181722\170960E\1103690\&7|\162612k\SI*c6\DLEd\1009741s\1007391w\42177)\"\1103677,-k2\45021!`4l\1102141\1085344\3180\160568$s\65124l\1016531O-hb\1113375Wk`E\36192\173301Fzl\DC2\1091888\SIj\SI\SI\SOQk\tyb\ETX#B\SUB\1034586\1075342[\1090619b#\GS\1111268\33422\1098425\&0\1081669,Azi+$\33444J\ESCUD\176210Ml\SOHg)\EM:\ESCL\983478\NAKX\t\EMA\1063521Q\45205=Ol:\151007r\ETB\DC3\110789\US\n\168042+F\1049002~\DLEp\1006119&X~\46361\1044213\DLE\USa9<\1073068v*\1025840\tF\1000262c}\1069962o)\b\172315\37902}D\1068546:3\167728\1009034\&7 \1047970\SUB]8\b\\j\DC1\993234\146703)\1016109\1027454w\171333\SOH", updateServiceConnUrl = Just @@ -641,7 +641,7 @@ testObject_UpdateServiceConn_provider_16 :: UpdateServiceConn testObject_UpdateServiceConn_provider_16 = UpdateServiceConn { updateServiceConnPassword = - PlainTextPassword + plainTextPassword6Unsafe "\644'\n\7167q\EM;\1032724BLAvpn\SI\1002860\1023507<\49854\STX\1013153\137209\EOTs\b\b%7\DLE#X1\161745\ESC\59873v\SUBl!eh\1021505\v\35441\993107o0I^:\DLE\1086270\1069220tn\DC1C\1019637jX\NAKm\188269LTK\SI'\ESCF3]nr/\168452\\\1088205bB\SUB'\b\157100\1014790\&3\SYNg\n}{pq\NAK\1113906?\995672\190804\CAN:\175546\\\1069654ZMs\t\1068874\&6S\1024467\1093547nO:Xy\1064925\173331\1044605\164489\"ry\DC2\NULT#\1077621M3\DELS[\127107\48973K\1104211WE<\1018102\&1Y\b\53326\1051138Z\1038689\FSU\993629R\175863\DC4FN%\ESCi\DLE\EMy +m\RSk*Txd\19948ji\189084\1074062\1081201!6N\DLE!_!\1026215\&3a?s>\EOT\STX\1041788\31864@\129112\&6\f\DC3\996985.\SYNz+8\NUL\1077938\1069477\EOTQ\ETXtV\\`;c\fo\50816\120881\&0=\1065656\EM[\ENQ{\1052186B,\37696'\48642\157636\162832\98083\120030PBx\998172?6I]X`W\158572\ENQ\49963\986583\DLE'\a\ETX\1074659\&7U\59933\135290\1008696\9082\ESC<^__\100688B;\1099451%K\150128\78399E\989825\DC1#\26616_\DC4\1061882k\1059333]3.6\92298\14451%\US2\143989'p\DC3\EOT#j.\31151@\1054758\&1\155144\&0;NR'\1048341\60816O\1032754\1094257\DC3\abJ/\v\1010244V\1047548 \SOHo,2[\RS]4\f\nGb\179257|\1048501\1048359b\SOH2C^x\DLE\ETB%L\FSQEF.X%\SYN\1076692\1019419\\ze6\ACK", updateServiceConnUrl = Just @@ -692,7 +692,7 @@ testObject_UpdateServiceConn_provider_17 :: UpdateServiceConn testObject_UpdateServiceConn_provider_17 = UpdateServiceConn { updateServiceConnPassword = - PlainTextPassword + plainTextPassword6Unsafe "\EOT\1045806t\97003\SUB8<\ETB)\EMa2\NAKI\NULc=\1108345(r\SO/\148273.&\147705\&5\SOHfH\1035927\163968['\991226T\997928\&2\RS\83083\vy\150182\1096305\144065juC\94678", updateServiceConnUrl = Nothing, updateServiceConnKeys = @@ -732,7 +732,7 @@ testObject_UpdateServiceConn_provider_18 :: UpdateServiceConn testObject_UpdateServiceConn_provider_18 = UpdateServiceConn { updateServiceConnPassword = - PlainTextPassword + plainTextPassword6Unsafe "BK Q\1014125\bn0$T!r\CAN\150725m\SUB\NAK$\142935\1090156\1040023\1110772b\DELn)\1057067E\97334\&0)ZK$z?.\SOH\167550[:\1105660r\43928(H\43310\bU2\179051\&3oS16<\1105969Z\57930\CAN]\47876\&7/\120262JUI\24934d\1080925\1009915\SUB\19033\DC1\1061392Z\EOT\EOT\54639z\SOR\1086710\&7\DLEs;m\37477\28396>\998226p)\\`15\DC3q\1095343l\SO\152478b\fy\14607\1075202$>D\152059X|\182419\1110462\1091179\ENQ\54447\&0Up2]j~St\EOT\159824w.T\t\1068686\f\DC2\186530\145288&W(\ENQ\1024967h\SI \991230\ETB4o\v\FS\1096193\EOTB\1021960\&2!\DLE+\98373\vpK\CAN\163478b2]h@ei\143476\DC3\47232vG+ytaV\100000\&6\1033181\nE\n\1050459d@jI\168771\&7\18408\44309F&\1037698\134204(?#P\1102778R\1040710<\94302\1052687~c\GS\1079831RC7f'\NULf\1064876{_Z:Cl\SO\1103911\GS\74318\1062883&\101025\37781\1004774\1019853\&2ux\n MN\176144`\SYNp\EM\ACK\40602\1075473\1071332\991026bl\DC4\ETB_\US\ETX[U\SOH\NUL\995920\7454\RSZ-}e\991160V:L\46179 Xh\1032551io\1039546@\175935\NAK\STXF\SOH*/D\172325\&6\DC1\100415\12730OjPQU1K\1080043\SYN\994399O\RS\75043\&1\1092605\65399/[\182411-\a\1112589\&8\EOT\SYNcE\190631\1027179\1028700\1055026u\EMs\1042923tLzgD\184376y]o\DEL\59030\67658\bv\EM\11200\1009731nW\1058051[\GS\n\98337l*\1020276\149362*ca\1043242&\991049\GS>\DC3)\ESC\r\ETB=Q\149426\EOT\ESC\1110189\178428vGL{\1096339+\1068305\1097108\183886{+&\1032994\21683(\SI{',\987672@\98096\ETXT5\131519\140923\1009789J s[\\^2cSJ&g\155812\CAN\1110385 6\998376\1038801Q\159855\ETB7\\L\995599n\DC2\1103386\aO\1078070\1023853\1027079X:8YA7`\r\92176\176851dX$\1347\"\148822YYU\144717\987220#i#`\20260\1083835V\US)|\14405\&2\rN>\1107993\189621\DELM_&t\96315p\n\DC2\f\1105846\177556Vw\1015372\EOT\1063032\&2\RS$\69697SEF\SOo\53638\ACK\44500\1001122qYL93:5{b\120606\&3\991440\DLE\990212\SOI\bRT:\1065174vM\a\52129'?\EOT\1060415^\1013803\RS^\STXfY\163783F\42249q\b\vh?\1076636^\DC1l9<^)p\42648tmQBN#3,\DC3\119977\SYN\148346_\SOvAB\DLE42\1007350T!V\FS|\1054203\994976Y5gy\ETX=\132355p%e\46874\v\DC4\DC2\1018287ms'a\SO\990743\62031\DLED\RS\175516\993474[\1100496$y\DEL2+\"\24584\139851\155402\65081\FSe5\99380\&8iB*k;\SUB<\19304\1111933\t\STXz\DC3s88((+E\r!O\ENQ:\ENQ\94087\DC4T-CF\1093660KP\24961[f\66588fx\1104991$G\1099775\52417\r\990169\&9\991666\146083e\fs2\US", updateServiceConnUrl = Just @@ -767,7 +767,7 @@ testObject_UpdateServiceConn_provider_19 :: UpdateServiceConn testObject_UpdateServiceConn_provider_19 = UpdateServiceConn { updateServiceConnPassword = - PlainTextPassword + plainTextPassword6Unsafe " &n\160619\34154\&8\43937U\1039397>*\993401=\35882\171390\&3v\1059159Hs\26704o}\40618l\1014576\FS\148314m\NAKj8\987797\167591]/\14092K(:\DC4\68870\STX\SOHK\b\32766J\1065304\1087197\\\RS\NAK\1008251\1078793\155825%\ETXd\1077641qS\134619y*#\77898\SI\SI\ENQ\45021\42114\171376:{\nP\144997lC\1048175>z\1076881\1074716v\1105912\1001528\&1|g\2134\DLE\1058580UT\1092924)vUVM\CANS\1011756\n\DC1\fy\DC4\1087227c\STX}Tll\GS\"G9\EOT-\1056541\1048887\SI2]\178229\f\171206|}g\1045301\161759e\182773\STX\73448dY\1037520F\t#{;mK\137787\36684\29082\n\SO\71211\1019153\1018611\SORn\CAN\n(\1092530>FCD\154433-\1104128n\ETXI\94364\93819\ETBN94\178422?k\ACK?|,\1097051c\1040341Qkp\ETBm\13083\28246\STX\21644XpZ\1028843#~T\rB}@\EMB\ESC%\1056576\184331,\185280m\1000086\n)HlK{PE\138393_/[H=\22492\&4\RS\ACK\49640^+MX\99139\1094111{D>\1058311V\DC4).HX\1020816x\EM\4869E-i:,?{H\n\1032654^Xt\1075783\EOT\RSfC%\1036350X\992457\120586k_\rl8v\1059490O\132715\f[CJD\148581*\44858\31446~0lSdA\DLE\SUB\988261\68042|#vCkB4\DC1zwq\5448K\1109392\1071549\1094223aU\1046318\18272F\NULED\1016313\v\26976Su\vmB\1019120\US+\1011430\1090687\DLE\SI\1097286>\121221\CANUQ\162002p\37165\1019838(I\1077362\r\DC3\157492\96158\1110610>\1020297\CAN\170247hF^<7\ACKIn\152012\SYNw\83193\DELH\154839\189257\bF\170249\&8\1063107\11763\73876dR\1093883>D\1102005D\1079913\EOTT\GSP7nw\42408\DC3\147829m8*R\DC4\DLE\DC4a\998793\b&\153797\a\1064029\ENQ\ACK\26632\62173\a\NAKheaM\1103290\165755\992228m5\ro\154059\DC2\132686\&9/~&$c\EM\3111]0BuH\DC1\151251S\7591p\57398\28319\169436\16438\DELS PUYy\v-\1076742\DC16\995339\1102710\148982\138245\1105664\DC1t9\FS\27132z-\140909U\FS^c\147694!\194919x\98811\1016231{6\r\rG\fe\1060938\r\1022210\ETB\989852\bv2YN\DC4\ETB$-\1081630*\137645\&4", updateServiceConnUrl = Just @@ -809,7 +809,7 @@ testObject_UpdateServiceConn_provider_20 :: UpdateServiceConn testObject_UpdateServiceConn_provider_20 = UpdateServiceConn { updateServiceConnPassword = - PlainTextPassword + plainTextPassword6Unsafe "4\70367\1069671\141726\&4\ESC\126570\SUB\FS#7e%Lj;\SOHe1\152448\1038592\CAN +-- +-- 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.CreateGroupConversation where + +import Data.Domain +import Data.Id +import qualified Data.Map as Map +import qualified Data.Set as Set +import qualified Data.UUID as UUID (fromString) +import Imports +import Test.Wire.API.Golden.Generated.Conversation_user +import Wire.API.Conversation + +unreachableDomain1, unreachableDomain2 :: Domain +unreachableDomain1 = Domain "golden-unreachable-1.example.com" +unreachableDomain2 = Domain "golden-unreachable-2.example.com" + +user1, user2 :: UserId +user1 = Id (fromJust (UUID.fromString "a0000001-0000-0001-0000-000200000007")) +user2 = Id (fromJust (UUID.fromString "f0000001-b000-0001-0000-000200060005")) + +testObject_CreateGroupConversation_1 :: CreateGroupConversation +testObject_CreateGroupConversation_1 = + CreateGroupConversation + { cgcConversation = testObject_Conversation_user_1, + cgcFailedToAdd = Map.empty + } + +testObject_CreateGroupConversation_2 :: CreateGroupConversation +testObject_CreateGroupConversation_2 = + CreateGroupConversation + { cgcConversation = testObject_Conversation_user_1, + cgcFailedToAdd = + Map.singleton unreachableDomain1 $ Set.fromList $ [user1, user2] + } + +testObject_CreateGroupConversation_3 :: CreateGroupConversation +testObject_CreateGroupConversation_3 = + CreateGroupConversation + { cgcConversation = testObject_Conversation_user_1, + cgcFailedToAdd = + Map.fromList + [ (unreachableDomain1, Set.singleton user1), + (unreachableDomain2, Set.singleton user2) + ] + } 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 bde9b24c1d..51c9bd8eca 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 @@ -18,7 +18,7 @@ module Test.Wire.API.Golden.Manual.CreateScimToken where import Data.Code -import Data.Misc (PlainTextPassword (PlainTextPassword)) +import Data.Misc (plainTextPassword6Unsafe) import Data.Range (unsafeRange) import Data.Text.Ascii (AsciiChars (validate)) import Imports (Maybe (Just, Nothing), fromRight, undefined) @@ -28,14 +28,14 @@ testObject_CreateScimToken_1 :: CreateScimToken testObject_CreateScimToken_1 = CreateScimToken "description" - (Just (PlainTextPassword "very-geheim")) + (Just (plainTextPassword6Unsafe "very-geheim")) (Just (Value {asciiValue = unsafeRange (fromRight undefined (validate "123456"))})) testObject_CreateScimToken_2 :: CreateScimToken testObject_CreateScimToken_2 = CreateScimToken "description2" - (Just (PlainTextPassword "secret")) + (Just (plainTextPassword6Unsafe "secret")) Nothing testObject_CreateScimToken_3 :: CreateScimToken diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual/ListUsersById.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual/ListUsersById.hs new file mode 100644 index 0000000000..f8632b7eb3 --- /dev/null +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual/ListUsersById.hs @@ -0,0 +1,85 @@ +{-# LANGUAGE OverloadedLists #-} + +-- 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.ListUsersById where + +import Data.Domain +import Data.Id +import Data.LegalHold +import Data.Qualified +import qualified Data.UUID as UUID +import Imports +import Wire.API.User + +domain1, domain2 :: Domain +domain1 = Domain "example.com" +domain2 = Domain "test.net" + +user1, user2 :: UserId +user1 = Id . fromJust $ UUID.fromString "4f201a43-935e-4e19-8fe0-0a878d3d6e74" +user2 = Id . fromJust $ UUID.fromString "eb48b095-d96f-4a94-b4ec-2a1d61447e13" + +profile1, profile2 :: UserProfile +profile1 = + UserProfile + { profileQualifiedId = Qualified user1 domain1, + profileName = Name "user1", + profilePict = Pict [], + profileAssets = [], + profileAccentId = ColourId 0, + profileDeleted = False, + profileService = Nothing, + profileHandle = Nothing, + profileExpire = Nothing, + profileTeam = Nothing, + profileEmail = Nothing, + profileLegalholdStatus = UserLegalHoldDisabled + } +profile2 = + UserProfile + { profileQualifiedId = Qualified user2 domain2, + profileName = Name "user2", + profilePict = Pict [], + profileAssets = [], + profileAccentId = ColourId 0, + profileDeleted = False, + profileService = Nothing, + profileHandle = Nothing, + profileExpire = Nothing, + profileTeam = Nothing, + profileEmail = Nothing, + profileLegalholdStatus = UserLegalHoldDisabled + } + +testObject_ListUsersById_user_1 :: ListUsersById +testObject_ListUsersById_user_1 = ListUsersById mempty Nothing + +testObject_ListUsersById_user_2 :: ListUsersById +testObject_ListUsersById_user_2 = + ListUsersById + { listUsersByIdFound = [profile1, profile2], + listUsersByIdFailed = Nothing + } + +testObject_ListUsersById_user_3 :: ListUsersById +testObject_ListUsersById_user_3 = + ListUsersById + { listUsersByIdFound = [profile1], + listUsersByIdFailed = pure $ [Qualified user2 domain2] + } diff --git a/libs/wire-api/test/golden/testObject_ConversationCoverView_1.json b/libs/wire-api/test/golden/testObject_ConversationCoverView_1.json index 485f5b1ca3..917cfe4360 100644 --- a/libs/wire-api/test/golden/testObject_ConversationCoverView_1.json +++ b/libs/wire-api/test/golden/testObject_ConversationCoverView_1.json @@ -1,4 +1,5 @@ { "id": "00000018-0000-0020-0000-000e00000002", - "name": null + "name": null, + "has_password": false } diff --git a/libs/wire-api/test/golden/testObject_ConversationCoverView_2.json b/libs/wire-api/test/golden/testObject_ConversationCoverView_2.json index 2087db7b13..c36128fa05 100644 --- a/libs/wire-api/test/golden/testObject_ConversationCoverView_2.json +++ b/libs/wire-api/test/golden/testObject_ConversationCoverView_2.json @@ -1,4 +1,5 @@ { "id": "00000018-0000-0020-0000-000e00000002", - "name": "conversation name" + "name": "conversation name", + "has_password": false } diff --git a/libs/wire-api/test/golden/testObject_ConversationCoverView_3.json b/libs/wire-api/test/golden/testObject_ConversationCoverView_3.json index 0c280976b1..453b2e9b2d 100644 --- a/libs/wire-api/test/golden/testObject_ConversationCoverView_3.json +++ b/libs/wire-api/test/golden/testObject_ConversationCoverView_3.json @@ -1,4 +1,5 @@ { "id": "00000018-0000-0020-0000-000e00000002", - "name": "" + "name": "", + "has_password": true } diff --git a/libs/wire-api/test/golden/testObject_CreateGroupConversation_1.json b/libs/wire-api/test/golden/testObject_CreateGroupConversation_1.json new file mode 100644 index 0000000000..44959d7f60 --- /dev/null +++ b/libs/wire-api/test/golden/testObject_CreateGroupConversation_1.json @@ -0,0 +1,40 @@ +{ + "access": [], + "access_role": [], + "creator": "00000001-0000-0001-0000-000200000001", + "failed_to_add": [], + "id": "00000001-0000-0000-0000-000000000000", + "last_event": "0.0", + "last_event_time": "1970-01-01T00:00:00.000Z", + "members": { + "others": [], + "self": { + "conversation_role": "rhhdzf0j0njilixx0g0vzrp06b_5us", + "hidden": false, + "hidden_ref": "", + "id": "00000001-0000-0001-0000-000100000000", + "otr_archived": false, + "otr_archived_ref": "", + "otr_muted_ref": null, + "otr_muted_status": null, + "qualified_id": { + "domain": "golden.example.com", + "id": "00000001-0000-0001-0000-000100000000" + }, + "service": null, + "status": 0, + "status_ref": "0.0", + "status_time": "1970-01-01T00:00:00.000Z" + } + }, + "message_timer": null, + "name": " 0", + "protocol": "proteus", + "qualified_id": { + "domain": "golden.example.com", + "id": "00000001-0000-0000-0000-000000000000" + }, + "receipt_mode": -2, + "team": "00000001-0000-0001-0000-000100000002", + "type": 2 +} diff --git a/libs/wire-api/test/golden/testObject_CreateGroupConversation_2.json b/libs/wire-api/test/golden/testObject_CreateGroupConversation_2.json new file mode 100644 index 0000000000..433aeb494a --- /dev/null +++ b/libs/wire-api/test/golden/testObject_CreateGroupConversation_2.json @@ -0,0 +1,49 @@ +{ + "access": [], + "access_role": [], + "creator": "00000001-0000-0001-0000-000200000001", + "failed_to_add": [ + { + "domain": "golden-unreachable-1.example.com", + "id": "a0000001-0000-0001-0000-000200000007" + }, + { + "domain": "golden-unreachable-1.example.com", + "id": "f0000001-b000-0001-0000-000200060005" + } + ], + "id": "00000001-0000-0000-0000-000000000000", + "last_event": "0.0", + "last_event_time": "1970-01-01T00:00:00.000Z", + "members": { + "others": [], + "self": { + "conversation_role": "rhhdzf0j0njilixx0g0vzrp06b_5us", + "hidden": false, + "hidden_ref": "", + "id": "00000001-0000-0001-0000-000100000000", + "otr_archived": false, + "otr_archived_ref": "", + "otr_muted_ref": null, + "otr_muted_status": null, + "qualified_id": { + "domain": "golden.example.com", + "id": "00000001-0000-0001-0000-000100000000" + }, + "service": null, + "status": 0, + "status_ref": "0.0", + "status_time": "1970-01-01T00:00:00.000Z" + } + }, + "message_timer": null, + "name": " 0", + "protocol": "proteus", + "qualified_id": { + "domain": "golden.example.com", + "id": "00000001-0000-0000-0000-000000000000" + }, + "receipt_mode": -2, + "team": "00000001-0000-0001-0000-000100000002", + "type": 2 +} diff --git a/libs/wire-api/test/golden/testObject_CreateGroupConversation_3.json b/libs/wire-api/test/golden/testObject_CreateGroupConversation_3.json new file mode 100644 index 0000000000..45834b8c6e --- /dev/null +++ b/libs/wire-api/test/golden/testObject_CreateGroupConversation_3.json @@ -0,0 +1,49 @@ +{ + "access": [], + "access_role": [], + "creator": "00000001-0000-0001-0000-000200000001", + "failed_to_add": [ + { + "domain": "golden-unreachable-1.example.com", + "id": "a0000001-0000-0001-0000-000200000007" + }, + { + "domain": "golden-unreachable-2.example.com", + "id": "f0000001-b000-0001-0000-000200060005" + } + ], + "id": "00000001-0000-0000-0000-000000000000", + "last_event": "0.0", + "last_event_time": "1970-01-01T00:00:00.000Z", + "members": { + "others": [], + "self": { + "conversation_role": "rhhdzf0j0njilixx0g0vzrp06b_5us", + "hidden": false, + "hidden_ref": "", + "id": "00000001-0000-0001-0000-000100000000", + "otr_archived": false, + "otr_archived_ref": "", + "otr_muted_ref": null, + "otr_muted_status": null, + "qualified_id": { + "domain": "golden.example.com", + "id": "00000001-0000-0001-0000-000100000000" + }, + "service": null, + "status": 0, + "status_ref": "0.0", + "status_time": "1970-01-01T00:00:00.000Z" + } + }, + "message_timer": null, + "name": " 0", + "protocol": "proteus", + "qualified_id": { + "domain": "golden.example.com", + "id": "00000001-0000-0000-0000-000000000000" + }, + "receipt_mode": -2, + "team": "00000001-0000-0001-0000-000100000002", + "type": 2 +} diff --git a/libs/wire-api/test/golden/testObject_Event_conversation_3.json b/libs/wire-api/test/golden/testObject_Event_conversation_3.json index 2b9001a47a..95ff02e8ca 100644 --- a/libs/wire-api/test/golden/testObject_Event_conversation_3.json +++ b/libs/wire-api/test/golden/testObject_Event_conversation_3.json @@ -3,7 +3,8 @@ "data": { "code": "7d6713", "key": "CRdONS7988O2QdyndJs1", - "uri": "https://example.com" + "uri": "https://example.com", + "has_password": false }, "from": "2126ea99-ca79-43ea-ad99-a59616468e8e", "qualified_conversation": { diff --git a/libs/wire-api/test/golden/testObject_Event_user_14.json b/libs/wire-api/test/golden/testObject_Event_user_14.json index 2f32d7fdb3..1657df9614 100644 --- a/libs/wire-api/test/golden/testObject_Event_user_14.json +++ b/libs/wire-api/test/golden/testObject_Event_user_14.json @@ -2,7 +2,8 @@ "conversation": "00000838-0000-1bc6-0000-686d00003565", "data": { "code": "lLz-9vR8ENum0kI-xWJs", - "key": "NEN=eLUWHXclTp=_2Nap" + "key": "NEN=eLUWHXclTp=_2Nap", + "has_password": false }, "from": "0000114a-0000-7da8-0000-40cb00007fcf", "qualified_conversation": { diff --git a/libs/wire-api/test/golden/testObject_ListUsersById_user_1.json b/libs/wire-api/test/golden/testObject_ListUsersById_user_1.json new file mode 100644 index 0000000000..98cdc013c8 --- /dev/null +++ b/libs/wire-api/test/golden/testObject_ListUsersById_user_1.json @@ -0,0 +1 @@ +{ "found" : [] } \ No newline at end of file diff --git a/libs/wire-api/test/golden/testObject_ListUsersById_user_2.json b/libs/wire-api/test/golden/testObject_ListUsersById_user_2.json new file mode 100644 index 0000000000..81d485c005 --- /dev/null +++ b/libs/wire-api/test/golden/testObject_ListUsersById_user_2.json @@ -0,0 +1,25 @@ +{ "found" : + [ { "qualified_id" : + { "domain" : "example.com" + , "id" : "4f201a43-935e-4e19-8fe0-0a878d3d6e74" + } + , "id" : "4f201a43-935e-4e19-8fe0-0a878d3d6e74" + , "name" : "user1" + , "picture" : [] + , "assets" : [] + , "accent_id" : 0 + , "legalhold_status" : "disabled" + } + , { "qualified_id" : + { "domain" : "test.net" + , "id" : "eb48b095-d96f-4a94-b4ec-2a1d61447e13" + } + , "id" : "eb48b095-d96f-4a94-b4ec-2a1d61447e13" + , "name" : "user2" + , "picture" : [] + , "assets" : [] + , "accent_id" : 0 + , "legalhold_status" : "disabled" + } + ] +} \ No newline at end of file diff --git a/libs/wire-api/test/golden/testObject_ListUsersById_user_3.json b/libs/wire-api/test/golden/testObject_ListUsersById_user_3.json new file mode 100644 index 0000000000..f2b9d88379 --- /dev/null +++ b/libs/wire-api/test/golden/testObject_ListUsersById_user_3.json @@ -0,0 +1,19 @@ +{ "found" : + [ { "qualified_id" : + { "domain" : "example.com" + , "id" : "4f201a43-935e-4e19-8fe0-0a878d3d6e74" + } + , "id" : "4f201a43-935e-4e19-8fe0-0a878d3d6e74" + , "name" : "user1" + , "picture" : [] + , "assets" : [] + , "accent_id" : 0 + , "legalhold_status" : "disabled" + } + ] +, "failed" : + [ { "domain" : "test.net" + , "id" : "eb48b095-d96f-4a94-b4ec-2a1d61447e13" + } + ] +} \ No newline at end of file diff --git a/libs/wire-api/test/golden/testObject_NewUser_user_5.json b/libs/wire-api/test/golden/testObject_NewUser_user_5.json index 2d0137614d..a71e46bea1 100644 --- a/libs/wire-api/test/golden/testObject_NewUser_user_5.json +++ b/libs/wire-api/test/golden/testObject_NewUser_user_5.json @@ -1,6 +1,6 @@ { "assets": [], "name": "test name", - "password": "123456", + "password": "12345678", "team_code": "RUne0vse27qsm5jxGmL0xQaeuEOqcqr65rU=" } 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 291e71c640..d51aa85ebb 100644 --- a/libs/wire-api/test/golden/testObject_NewUser_user_7.json +++ b/libs/wire-api/test/golden/testObject_NewUser_user_7.json @@ -1,7 +1,7 @@ { "assets": [], "name": "test name", - "password": "123456", + "password": "12345678", "phone": "+12345678", "team": { "currency": "XUA", diff --git a/libs/wire-api/test/golden/testObject_NewUser_user_8.json b/libs/wire-api/test/golden/testObject_NewUser_user_8.json index 3331ac20e7..27658bbbad 100644 --- a/libs/wire-api/test/golden/testObject_NewUser_user_8.json +++ b/libs/wire-api/test/golden/testObject_NewUser_user_8.json @@ -1,7 +1,7 @@ { "assets": [], "name": "test name", - "password": "123456", + "password": "12345678", "phone": "+12345678", "team_code": "RUne0vse27qsm5jxGmL0xQaeuEOqcqr65rU=" } diff --git a/libs/wire-api/test/golden/testObject_QualifiedUserClientPrekeyMapV4_1.json b/libs/wire-api/test/golden/testObject_QualifiedUserClientPrekeyMapV4_1.json new file mode 100644 index 0000000000..1c15f0a679 --- /dev/null +++ b/libs/wire-api/test/golden/testObject_QualifiedUserClientPrekeyMapV4_1.json @@ -0,0 +1,2 @@ +{ "qualified_user_client_prekeys" : {} +} \ No newline at end of file diff --git a/libs/wire-api/test/golden/testObject_QualifiedUserClientPrekeyMapV4_2.json b/libs/wire-api/test/golden/testObject_QualifiedUserClientPrekeyMapV4_2.json new file mode 100644 index 0000000000..ac608bf31e --- /dev/null +++ b/libs/wire-api/test/golden/testObject_QualifiedUserClientPrekeyMapV4_2.json @@ -0,0 +1,9 @@ +{ "qualified_user_client_prekeys" : { + "example.com" : { + "44f9c51e-0dce-4e7f-85ba-b4e5a545ce68" : { + "0123456789ABCEF" : null + } + } + } +, "failed_to_list" : [] +} \ No newline at end of file diff --git a/libs/wire-api/test/golden/testObject_QualifiedUserClientPrekeyMapV4_3.json b/libs/wire-api/test/golden/testObject_QualifiedUserClientPrekeyMapV4_3.json new file mode 100644 index 0000000000..ffd0320a30 --- /dev/null +++ b/libs/wire-api/test/golden/testObject_QualifiedUserClientPrekeyMapV4_3.json @@ -0,0 +1,10 @@ +{ "qualified_user_client_prekeys" : {} +, "failed_to_list" : + [ { "domain" : "example.com" + , "id" : "44f9c51e-0dce-4e7f-85ba-b4e5a545ce68" + } + , { "domain" : "test.net" + , "id" : "284c4e8f-78ef-43f4-a77a-015c22e37960" + } + ] +} \ No newline at end of file diff --git a/libs/wire-api/test/unit/Main.hs b/libs/wire-api/test/unit/Main.hs index 7c3c3249c7..f0e2368876 100644 --- a/libs/wire-api/test/unit/Main.hs +++ b/libs/wire-api/test/unit/Main.hs @@ -21,10 +21,12 @@ module Main where import Imports +import System.IO.Unsafe (unsafePerformIO) import Test.Tasty import qualified Test.Wire.API.Call.Config as Call.Config import qualified Test.Wire.API.Conversation as Conversation import qualified Test.Wire.API.MLS as MLS +import qualified Test.Wire.API.OAuth as OAuth import qualified Test.Wire.API.RawJson as RawJson import qualified Test.Wire.API.Roundtrip.Aeson as Roundtrip.Aeson import qualified Test.Wire.API.Roundtrip.ByteString as Roundtrip.ByteString @@ -33,6 +35,7 @@ import qualified Test.Wire.API.Roundtrip.HttpApiData as Roundtrip.HttpApiData import qualified Test.Wire.API.Roundtrip.MLS as Roundtrip.MLS import qualified Test.Wire.API.Routes as Routes import qualified Test.Wire.API.Routes.Version as Routes.Version +import qualified Test.Wire.API.Routes.Version.Wai as Routes.Version.Wai import qualified Test.Wire.API.Swagger as Swagger import qualified Test.Wire.API.Team.Export as Team.Export import qualified Test.Wire.API.Team.Member as Team.Member @@ -63,5 +66,7 @@ main = Conversation.tests, MLS.tests, Routes.Version.tests, - RawJson.tests + unsafePerformIO Routes.Version.Wai.tests, + RawJson.tests, + OAuth.tests ] diff --git a/libs/wire-api/test/unit/Test/Wire/API/OAuth.hs b/libs/wire-api/test/unit/Test/Wire/API/OAuth.hs new file mode 100644 index 0000000000..a7775f8af1 --- /dev/null +++ b/libs/wire-api/test/unit/Test/Wire/API/OAuth.hs @@ -0,0 +1,44 @@ +{-# LANGUAGE ScopedTypeVariables #-} + +-- 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.OAuth where + +import Data.Aeson +import Imports +import Test.Tasty +import Test.Tasty.HUnit +import Wire.API.OAuth + +tests :: TestTree +tests = + testGroup "Oauth" $ + [ testGroup "code challenge verification should succeed" $ + [ testCase "should" testCodeChallengeVerification + ] + ] + +testCodeChallengeVerification :: Assertion +testCodeChallengeVerification = do + mkChallenge codeVerifier @?= codeChallenge + where + codeChallenge :: OAuthCodeChallenge + codeChallenge = either (\e -> error $ "invalid code challenge " <> show e) id $ eitherDecode "\"G7CWLBqYDT8doT_oEIN3un_QwZWYKHmOqG91nwNzITc\"" + + codeVerifier :: OAuthCodeVerifier + codeVerifier = either (\e -> error $ "invalid code verifier " <> show e) id $ eitherDecode "\"nE3k3zykOmYki~kriKzAmeFiGT7cWugcuToFwo1YPgrZ1cFvaQqLa.dXY9MnDj3umAmG-8lSNIYIl31Cs_.fV5r2psa4WWZcB.Nlc3A-t3p67NDZaOJjIiH~8PvUH_hR\"" 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 3586bb0a0b..23b7ebdb7d 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 @@ -39,6 +39,7 @@ import qualified Wire.API.CustomBackend as CustomBackend import qualified Wire.API.Event.Conversation as Event.Conversation import qualified Wire.API.Event.Team as Event.Team import qualified Wire.API.Message as Message +import qualified Wire.API.OAuth as OAuth import qualified Wire.API.Properties as Properties import qualified Wire.API.Provider as Provider import qualified Wire.API.Provider.Bot as Provider.Bot @@ -47,6 +48,7 @@ import qualified Wire.API.Provider.Service as Provider.Service import qualified Wire.API.Provider.Service.Tag as Provider.Service.Tag import qualified Wire.API.Push.Token as Push.Token import qualified Wire.API.Routes.Internal.Galley.TeamsIntra as TeamsIntra +import qualified Wire.API.Routes.Version as Routes.Version import qualified Wire.API.SystemSettings as SystemSettings import qualified Wire.API.Team as Team import qualified Wire.API.Team.Conversation as Team.Conversation @@ -117,6 +119,9 @@ tests = testRoundTrip @Conversation.Bot.RemoveBotResponse, testRoundTrip @Conversation.Bot.UpdateBotPrekeys, testRoundTrip @Conversation.Code.ConversationCode, + testRoundTrip @Conversation.Code.ConversationCodeInfo, + testRoundTrip @Conversation.Code.JoinConversationByCode, + testRoundTrip @Conversation.Code.CreateConversationCodeRequest, testRoundTrip @Conversation.Member.MemberUpdate, testRoundTrip @Conversation.Member.MutedStatus, testRoundTrip @Conversation.Member.Member, @@ -142,6 +147,13 @@ tests = testRoundTrip @Message.OtrRecipients, testRoundTrip @Message.NewOtrMessage, testRoundTrip @Message.ClientMismatch, + testRoundTrip @OAuth.RedirectUrl, + testRoundTrip @OAuth.OAuthApplicationName, + testRoundTrip @OAuth.RegisterOAuthClientRequest, + testRoundTrip @OAuth.OAuthClient, + testRoundTrip @OAuth.CreateOAuthAuthorizationCodeRequest, + testRoundTrip @OAuth.OAuthAccessTokenRequest, + testRoundTrip @OAuth.OAuthApplication, testRoundTrip @Properties.PropertyKey, testRoundTrip @Provider.Provider, testRoundTrip @Provider.ProviderProfile, @@ -316,6 +328,8 @@ tests = testRoundTrip @User.Search.TeamContact, testRoundTrip @(Wrapped.Wrapped "some_int" Int), testRoundTrip @Conversation.Action.SomeConversationAction, + testRoundTrip @Routes.Version.Version, + testRoundTrip @Routes.Version.VersionNumber, testRoundTrip @TeamsIntra.GuardLegalholdPolicyConflicts, testRoundTrip @TeamsIntra.TeamStatus, testRoundTrip @TeamsIntra.TeamStatusUpdate, diff --git a/libs/wire-api/test/unit/Test/Wire/API/Roundtrip/ByteString.hs b/libs/wire-api/test/unit/Test/Wire/API/Roundtrip/ByteString.hs index 467a837b58..9c4eb5b9e1 100644 --- a/libs/wire-api/test/unit/Test/Wire/API/Roundtrip/ByteString.hs +++ b/libs/wire-api/test/unit/Test/Wire/API/Roundtrip/ByteString.hs @@ -26,6 +26,7 @@ import qualified Wire.API.Asset as Asset import qualified Wire.API.Call.Config as Call.Config import qualified Wire.API.Conversation.Code as Conversation.Code import qualified Wire.API.Conversation.Role as Conversation.Role +import qualified Wire.API.OAuth as OAuth import qualified Wire.API.Properties as Properties import qualified Wire.API.Provider as Provider import qualified Wire.API.Provider.Service as Provider.Service @@ -81,7 +82,8 @@ tests = testRoundTrip @User.Search.TeamUserSearchSortBy, testRoundTrip @User.Search.TeamUserSearchSortOrder, testRoundTrip @User.Search.RoleFilter, - testRoundTrip @User.IdentityProvider.WireIdPAPIVersion + testRoundTrip @User.IdentityProvider.WireIdPAPIVersion, + testRoundTrip @OAuth.OAuthScope -- FUTUREWORK: -- testCase "Call.Config.TurnUsername (doesn't have FromByteString)" ... -- testCase "User.Activation.ActivationTarget (doesn't have FromByteString)" ... diff --git a/libs/wire-api/test/unit/Test/Wire/API/Roundtrip/HttpApiData.hs b/libs/wire-api/test/unit/Test/Wire/API/Roundtrip/HttpApiData.hs index 5ebf76a08e..00ef20df73 100644 --- a/libs/wire-api/test/unit/Test/Wire/API/Roundtrip/HttpApiData.hs +++ b/libs/wire-api/test/unit/Test/Wire/API/Roundtrip/HttpApiData.hs @@ -22,6 +22,7 @@ import Servant.API import qualified Test.Tasty as T import Test.Tasty.QuickCheck (Arbitrary, counterexample, testProperty, (===)) import Type.Reflection (typeRep) +import qualified Wire.API.Routes.Version import qualified Wire.API.User import qualified Wire.API.User.Search import qualified Wire.Arbitrary as Arbitrary () @@ -30,7 +31,9 @@ tests :: T.TestTree tests = T.localOption (T.Timeout (60 * 1000000) "60s") . T.testGroup "HttpApiData roundtrip tests" $ [ testRoundTrip @Wire.API.User.InvitationCode, - testRoundTrip @Wire.API.User.Search.PagingState + testRoundTrip @Wire.API.User.Search.PagingState, + testRoundTrip @Wire.API.Routes.Version.Version, + testRoundTrip @Wire.API.Routes.Version.VersionNumber ] testRoundTrip :: diff --git a/libs/wire-api/test/unit/Test/Wire/API/Routes/Version.hs b/libs/wire-api/test/unit/Test/Wire/API/Routes/Version.hs index 9af4812e89..6195c711f5 100644 --- a/libs/wire-api/test/unit/Test/Wire/API/Routes/Version.hs +++ b/libs/wire-api/test/unit/Test/Wire/API/Routes/Version.hs @@ -1,22 +1,45 @@ module Test.Wire.API.Routes.Version where +import Data.Aeson as Aeson +import Data.Binary.Builder +import Data.ByteString.Conversion +import Data.String.Conversions (cs) import Imports -import qualified Test.Tasty as T +import Servant.API +import Test.Tasty import Test.Tasty.HUnit import Wire.API.Routes.Version -tests :: T.TestTree +{-# ANN tests ("HLint: ignore Functor law" :: String) #-} +tests :: TestTree tests = - T.testGroup "Version" $ - [ T.testGroup - "toPathComponent" - [testCase "serialise different versions" testToPathComponent] + testGroup + "Version always has the shape V; serializations of Version and VersionNumber are `v`, ``, resp. is non-negative." + [ testCase "Version, show, 'v' prefix" $ do + nub (toLower . head . show <$> allVersions) @=? ['v'], + testCase "Version, show, int suffix" $ do + let expected = show $ (read @Int) . tail . show <$> allVersions + assertBool expected (isJust (Aeson.decode @[Int] (cs expected))), + testGroup "Version: all serializations are the same as `show`, up to string type" $ do + [ testCase "toByteString'" $ fmap toLower . show <$> allVersions @=? cs . toByteString' <$> allVersions, + testCase "encode" $ fmap toLower (show (show <$> allVersions)) @=? cs (encode allVersions), -- (`encode @Version` has extra double-quotes) + testCase "toUrlPiece" $ fmap toLower . show <$> allVersions @=? cs . toUrlPiece <$> allVersions, + testCase "toEncodedUrlPiece" $ fmap toLower . show <$> allVersions @=? cs . toLazyByteString . toEncodedUrlPiece <$> allVersions, + testCase "toHeader" $ fmap toLower . show <$> allVersions @=? cs . toHeader <$> allVersions, + testCase "toQueryParam" $ fmap toLower . show <$> allVersions @=? cs . toQueryParam <$> allVersions + ], + testGroup "VersionNumber: all serializations are the same as `tail . show . fromVersionNumber`, up to string type" $ + [ testCase "toByteString'" $ tail . show . fromVersionNumber <$> allVersionNumbers @=? cs . toByteString' <$> allVersionNumbers, + testCase "encode" $ tail . show . fromVersionNumber <$> allVersionNumbers @=? cs . encode <$> allVersionNumbers, + testCase "toUrlPiece" $ tail . show . fromVersionNumber <$> allVersionNumbers @=? cs . toUrlPiece <$> allVersionNumbers, + testCase "toEncodedUrlPiece" $ tail . show . fromVersionNumber <$> allVersionNumbers @=? cs . toLazyByteString . toEncodedUrlPiece <$> allVersionNumbers, + testCase "toHeader" $ tail . show . fromVersionNumber <$> allVersionNumbers @=? cs . toHeader <$> allVersionNumbers, + testCase "toQueryParam" $ tail . show . fromVersionNumber <$> allVersionNumbers @=? cs . toQueryParam <$> allVersionNumbers + ] ] -testToPathComponent :: Assertion -testToPathComponent = do - "v0" @=? toPathComponent V0 - "v1" @=? toPathComponent V1 - "v2" @=? toPathComponent V2 - "v3" @=? toPathComponent V3 - "v4" @=? toPathComponent V4 +allVersions :: [Version] +allVersions = [minBound ..] + +allVersionNumbers :: [VersionNumber] +allVersionNumbers = [minBound ..] diff --git a/libs/wire-api/test/unit/Test/Wire/API/Routes/Version/Wai.hs b/libs/wire-api/test/unit/Test/Wire/API/Routes/Version/Wai.hs new file mode 100644 index 0000000000..e5107205da --- /dev/null +++ b/libs/wire-api/test/unit/Test/Wire/API/Routes/Version/Wai.hs @@ -0,0 +1,67 @@ +module Test.Wire.API.Routes.Version.Wai where + +import Data.Proxy +import qualified Data.Set as Set +import Data.String.Conversions +import Data.Text as T +import Imports +import Network.HTTP.Types.Status (status200, status400) +import Network.Wai +import Servant.API +import Servant.Server +import Test.Hspec +import Test.Hspec.Wai +import Test.Hspec.Wai.Matcher +import Test.Tasty +import Test.Tasty.Hspec +import Wire.API.Routes.Version +import Wire.API.Routes.Version.Wai + +implicitVersion :: Text +implicitVersion = "0" + +sndGoodVersion :: Text +sndGoodVersion = "2" + +disabledVersion :: Text +disabledVersion = T.filter isDigit . cs $ toHeader disabledVersionTyped + +disabledVersionTyped :: Version +disabledVersionTyped = V3 + +unknownVersion :: Text +unknownVersion = "100" + +tests :: IO TestTree +tests = + testSpec "versionMiddleware" . with testApp $ do + mkTest Nothing Nothing ("good", 200) + mkTest Nothing (Just implicitVersion) ("mismatch: (Nothing,Just 0)", 400) + mkTest Nothing (Just sndGoodVersion) ("mismatch: (Nothing,Just 2)", 400) + mkTest (Just implicitVersion) (Just implicitVersion) ("good", 200) + mkTest (Just sndGoodVersion) (Just sndGoodVersion) ("good", 200) + mkTest (Just disabledVersion) (Just disabledVersion) (errmsg disabledVersion, 404) + mkTest (Just unknownVersion) (Just unknownVersion) (errmsg unknownVersion, 404) + where + errmsg v = "{\"code\":404,\"label\":\"unsupported-version\",\"message\":\"Version v" <> cs v <> " is not supported\"}" + +mkTest :: Maybe Text -> Maybe Text -> (LByteString, Int) -> SpecWith (st, Application) +mkTest mv1 mv2 (msg, status) = + it ("GET " <> cs path <> " => " <> show (msg, status)) $ do + get path `shouldRespondWith` ResponseMatcher status [] (bodyEquals msg) + where + path :: ByteString + path = cs $ maybe "" ("/v" <>) mv1 <> "/check-version" <> maybe "" ("?version=" <>) mv2 + +type TestAPI = "check-version" :> QueryParam "version" Int :> Raw + +testApp :: IO Application +testApp = pure $ versionMiddleware (Set.singleton disabledVersionTyped) (serve (Proxy @TestAPI) testHandler) + +testHandler :: Server TestAPI +testHandler mVersionNumber = Tagged $ \req cont -> + cont $ + let headerVersion = lookup "X-Wire-API-Version" (requestHeaders req) + in if headerVersion == (cs . show <$> mVersionNumber) + then responseLBS status200 [] "good" + else responseLBS status400 [] (cs $ "mismatch: " <> show (headerVersion, mVersionNumber)) diff --git a/libs/wire-api/wire-api.cabal b/libs/wire-api/wire-api.cabal index 8571f041dd..5e7afe420f 100644 --- a/libs/wire-api/wire-api.cabal +++ b/libs/wire-api/wire-api.cabal @@ -62,6 +62,8 @@ library Wire.API.MLS.SubConversation Wire.API.MLS.Welcome Wire.API.Notification + Wire.API.OAuth + Wire.API.Password Wire.API.Properties Wire.API.Provider Wire.API.Provider.Bot @@ -80,6 +82,7 @@ library Wire.API.Routes.Internal.Brig Wire.API.Routes.Internal.Brig.Connection Wire.API.Routes.Internal.Brig.EJPD + Wire.API.Routes.Internal.Brig.OAuth Wire.API.Routes.Internal.Cannon Wire.API.Routes.Internal.Cargohold Wire.API.Routes.Internal.Galley @@ -95,6 +98,7 @@ library Wire.API.Routes.Named Wire.API.Routes.Public Wire.API.Routes.Public.Brig + Wire.API.Routes.Public.Brig.OAuth Wire.API.Routes.Public.Cannon Wire.API.Routes.Public.Cargohold Wire.API.Routes.Public.Galley @@ -173,6 +177,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -184,6 +189,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -239,6 +245,7 @@ library , hashable , hostname-validate , hscim + , HsOpenSSL , http-api-data , http-media , http-types @@ -247,6 +254,7 @@ library , iproute >=1.5 , iso3166-country-codes >=0.2 , iso639 >=0.1 + , jose , lens >=4.12 , memory , metrics-wai @@ -263,6 +271,7 @@ library , saml2-web-sso , schema-profunctor , scientific + , scrypt , servant , servant-client , servant-client-core @@ -445,6 +454,7 @@ test-suite wire-api-golden-tests Test.Wire.API.Golden.Generated.PushToken_user Test.Wire.API.Golden.Generated.PushTokenList_user Test.Wire.API.Golden.Generated.QualifiedNewOtrMessage_user + Test.Wire.API.Golden.Generated.QualifiedUserClientPrekeyMapV4_user Test.Wire.API.Golden.Generated.QueuedNotification_user Test.Wire.API.Golden.Generated.QueuedNotificationList_user Test.Wire.API.Golden.Generated.ReceiptMode_user @@ -535,11 +545,13 @@ test-suite wire-api-golden-tests Test.Wire.API.Golden.Manual.ConversationPagingState Test.Wire.API.Golden.Manual.ConversationsResponse Test.Wire.API.Golden.Manual.ConvIdsPage + Test.Wire.API.Golden.Manual.CreateGroupConversation 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.ListUsersById Test.Wire.API.Golden.Manual.QualifiedUserClientPrekeyMap Test.Wire.API.Golden.Manual.SearchResultContact Test.Wire.API.Golden.Manual.TeamSize @@ -563,6 +575,7 @@ test-suite wire-api-golden-tests DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -574,6 +587,7 @@ test-suite wire-api-golden-tests MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -652,6 +666,7 @@ test-suite wire-api-tests Test.Wire.API.Call.Config Test.Wire.API.Conversation Test.Wire.API.MLS + Test.Wire.API.OAuth Test.Wire.API.RawJson Test.Wire.API.Roundtrip.Aeson Test.Wire.API.Roundtrip.ByteString @@ -660,6 +675,7 @@ test-suite wire-api-tests Test.Wire.API.Roundtrip.MLS Test.Wire.API.Routes Test.Wire.API.Routes.Version + Test.Wire.API.Routes.Version.Wai Test.Wire.API.Swagger Test.Wire.API.Team.Export Test.Wire.API.Team.Member @@ -682,6 +698,7 @@ test-suite wire-api-tests DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -693,6 +710,7 @@ test-suite wire-api-tests MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -734,6 +752,9 @@ test-suite wire-api-tests , filepath , hex , hscim + , hspec + , hspec-wai + , http-types , imports , iso3166-country-codes , iso639 @@ -749,11 +770,13 @@ test-suite wire-api-tests , saml2-web-sso , schema-profunctor , servant + , servant-server , servant-swagger-ui , string-conversions , swagger2 , tasty , tasty-expected-failure + , tasty-hspec , tasty-hunit , tasty-quickcheck , text @@ -764,6 +787,7 @@ test-suite wire-api-tests , uri-bytestring , uuid , vector + , wai , wire-api , wire-message-proto-lens diff --git a/libs/wire-message-proto-lens/wire-message-proto-lens.cabal b/libs/wire-message-proto-lens/wire-message-proto-lens.cabal index b3a9b35bc9..7bf8dd67da 100644 --- a/libs/wire-message-proto-lens/wire-message-proto-lens.cabal +++ b/libs/wire-message-proto-lens/wire-message-proto-lens.cabal @@ -41,6 +41,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -52,6 +53,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/libs/zauth/zauth.cabal b/libs/zauth/zauth.cabal index 7ddc1a138c..8b6fe191da 100644 --- a/libs/zauth/zauth.cabal +++ b/libs/zauth/zauth.cabal @@ -34,6 +34,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -45,6 +46,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -102,6 +104,7 @@ executable zauth DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -113,6 +116,7 @@ executable zauth MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -171,6 +175,7 @@ test-suite zauth-unit DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -182,6 +187,7 @@ test-suite zauth-unit MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/nix/default.nix b/nix/default.nix index c377bf7102..6f75f4e105 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -26,6 +26,7 @@ let # packages necessary to build wire-server docs docsPkgs = [ + pkgs.plantuml pkgs.texlive.combined.scheme-full (pkgs.python3.withPackages (ps: with ps; [ @@ -39,6 +40,7 @@ let sphinx-copybutton sphinxcontrib-fulltoc sphinxcontrib-kroki + sphinxcontrib-plantuml ])) ]; diff --git a/nix/haskell-pins.nix b/nix/haskell-pins.nix index 1a59b723a1..8e7f1a69c9 100644 --- a/nix/haskell-pins.nix +++ b/nix/haskell-pins.nix @@ -3,12 +3,12 @@ # 1. If your target git repository has only package with the cabal file at the # root, add it like this under 'gitPins': # = { -# src = fetchgit = { -# url = "" +# src = fetchgit { +# url = ""; # rev = ""; # sha256 = ""; -# } -# } +# }; +# }; # # 2. If your target git repsitory has many packages, add it like this under 'gitPins': # @@ -35,7 +35,9 @@ # 1. Determine the new commit ID/SHA of the git repository that you want to pin # and update the 'rev' field of the pin under 'gitPins'. # -# 2. Update 'sha256' field under `fetchgit` to be an empty string. +# 2. Update 'sha256' field under `fetchgit` to be an empty string. (This step is optional: +# since the hash has changed, the error will be the same if you remove it or if you leave the +# old value in place.) # # 3. Run step 3. from how to add a git pin. # @@ -171,6 +173,13 @@ let tasty-hunit = "hunit"; }; }; + jose = { + src = fetchgit { + url = "https://github.com/frasertweedale/hs-jose"; + rev = "a7f919b19f667dfbb4d5c989ce620d3e75af8247"; + sha256 = "sha256-SKEE9ZqhjBxHYUKQaoB4IpN4/Ui3tS4S98FgZqj7WlY="; + }; + }; # This can be removed once postie 0.6.0.3 (or later) is in nixpkgs postie = { src = fetchgit { diff --git a/nix/manual-overrides.nix b/nix/manual-overrides.nix index 807e39fbd1..000dc3f8ff 100644 --- a/nix/manual-overrides.nix +++ b/nix/manual-overrides.nix @@ -2,7 +2,7 @@ # FUTUREWORK: Figure out a way to detect if some of these packages are not # actually marked broken, so we can cleanup this file on every nixpkgs bump. hself: hsuper: { - aeson = hsuper.aeson_2_1_1_0; + aeson = (hlib.doJailbreak hsuper.aeson_2_1_2_1); binary-parsers = hlib.markUnbroken (hlib.doJailbreak hsuper.binary-parsers); bytestring-arbitrary = hlib.markUnbroken (hlib.doJailbreak hsuper.bytestring-arbitrary); cql = hlib.markUnbroken hsuper.cql; diff --git a/nix/overlay.nix b/nix/overlay.nix index 767abcaa20..f5f8853331 100644 --- a/nix/overlay.nix +++ b/nix/overlay.nix @@ -88,36 +88,6 @@ self: super: { inherit (super) stdenv fetchurl; }; - helm = super.callPackage ./pkgs/helm { }; - - helmfile = staticBinary { - pname = "helmfile"; - version = "0.141.0"; - - darwinAmd64Url = "https://github.com/roboll/helmfile/releases/download/v0.141.0/helmfile_darwin_amd64"; - darwinAmd64Sha256 = "0szfd3vy6fzd5657079hz5vii86f9xkg3bdzp3g4knkcw5x1kpxy"; - - linuxAmd64Url = "https://github.com/roboll/helmfile/releases/download/v0.141.0/helmfile_linux_amd64"; - linuxAmd64Sha256 = "0f5d9w3qjvwip4qn79hsigwp8nbjpj58p289hww503j43wjyxx8r"; - - inherit (super) stdenv fetchurl; - }; - - kubectl = staticBinaryInTarball { - pname = "kubectl"; - version = "1.19.8"; - - darwinAmd64Url = "https://dl.k8s.io/v1.19.8/kubernetes-client-darwin-amd64.tar.gz"; - darwinAmd64Sha256 = "23b847bb8b545c748e9078e7660c654eef74d15ccab8696d294f3d6c619c788e"; - - linuxAmd64Url = "https://dl.k8s.io/v1.19.8/kubernetes-client-linux-amd64.tar.gz"; - linuxAmd64Sha256 = "8388ff8b5c676bdbb8fe07ef7077de937b0bf60154f302df5f248f38f95122aa"; - - binPath = "client/bin/kubectl"; - - inherit (super) stdenv fetchurl; - }; - kind = staticBinary { pname = "kind"; version = "0.11.0"; diff --git a/nix/pkgs/helm/default.nix b/nix/pkgs/helm/default.nix deleted file mode 100644 index 8b68403913..0000000000 --- a/nix/pkgs/helm/default.nix +++ /dev/null @@ -1,52 +0,0 @@ -# Copied from nixpkgs and modified because it seems too complicated to override -# buildGoModule packages. -{ lib, stdenv, buildGoModule, fetchFromGitHub, installShellFiles }: - -buildGoModule rec { - pname = "kubernetes-helm"; - version = "3.11.0-patched"; - - src = fetchFromGitHub { - owner = "wireapp"; - repo = "helm"; - rev = "949de3195be5b3d21ed707da18ee3bcb2a9a2af8"; - sha256 = "sha256-alyR6+gm7WEvFfJxHl9a0jpC3+457Kg6aRHcidA0RZg="; - }; - vendorSha256 = "sha256-LRMDrBSl5EGQqQt5FUU4JJHqdwfYt5qsVpe76jUQBVI="; - - subPackages = [ "cmd/helm" ]; - ldflags = [ - "-w" - "-s" - "-X helm.sh/helm/v3/internal/version.version=v${version}" - "-X helm.sh/helm/v3/internal/version.gitCommit=${src.rev}" - ]; - - preCheck = '' - # skipping version tests because they require dot git directory - substituteInPlace cmd/helm/version_test.go \ - --replace "TestVersion" "SkipVersion" - '' + lib.optionalString stdenv.isLinux '' - # skipping plugin tests on linux - substituteInPlace cmd/helm/plugin_test.go \ - --replace "TestPluginDynamicCompletion" "SkipPluginDynamicCompletion" \ - --replace "TestLoadPlugins" "SkipLoadPlugins" - substituteInPlace cmd/helm/helm_test.go \ - --replace "TestPluginExitCode" "SkipPluginExitCode" - ''; - - nativeBuildInputs = [ installShellFiles ]; - postInstall = '' - $out/bin/helm completion bash > helm.bash - $out/bin/helm completion zsh > helm.zsh - installShellCompletion helm.{bash,zsh} - ''; - - meta = with lib; { - homepage = "https://github.com/kubernetes/helm"; - description = "A package manager for kubernetes"; - mainProgram = "helm"; - license = licenses.asl20; - maintainers = with maintainers; [ rlupton20 edude03 saschagrunert Frostman Chili-Man techknowlogick ]; - }; -} diff --git a/nix/pkgs/rusty_jwt_tools_ffi/add-Cargo.lock.patch b/nix/pkgs/rusty_jwt_tools_ffi/add-Cargo.lock.patch deleted file mode 100644 index 9de4d963b0..0000000000 --- a/nix/pkgs/rusty_jwt_tools_ffi/add-Cargo.lock.patch +++ /dev/null @@ -1,3025 +0,0 @@ -From 13a4229f8ae9f91f57a620a06aa1d6771aaad168 Mon Sep 17 00:00:00 2001 -From: Leif Battermann -Date: Wed, 1 Mar 2023 15:27:03 +0100 -Subject: [PATCH] generate new cargo.lock - ---- - Cargo.lock | 3006 ++++++++++++++++++++++++++++++++++++++++++++++++++++ - 1 file changed, 3006 insertions(+) - create mode 100644 Cargo.lock - -diff --git a/Cargo.lock b/Cargo.lock -new file mode 100644 -index 0000000..1e1537b ---- /dev/null -+++ b/Cargo.lock -@@ -0,0 +1,3006 @@ -+# This file is automatically @generated by Cargo. -+# It is not intended for manual editing. -+version = 3 -+ -+[[package]] -+name = "adler" -+version = "1.0.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -+ -+[[package]] -+name = "aho-corasick" -+version = "0.7.20" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" -+dependencies = [ -+ "memchr", -+] -+ -+[[package]] -+name = "android_system_properties" -+version = "0.1.5" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -+dependencies = [ -+ "libc", -+] -+ -+[[package]] -+name = "anyhow" -+version = "1.0.69" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" -+ -+[[package]] -+name = "asn1-rs" -+version = "0.5.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "cf6690c370453db30743b373a60ba498fc0d6d83b11f4abfd87a84a075db5dd4" -+dependencies = [ -+ "asn1-rs-derive", -+ "asn1-rs-impl", -+ "displaydoc", -+ "nom", -+ "num-traits", -+ "rusticata-macros", -+ "thiserror", -+ "time 0.3.20", -+] -+ -+[[package]] -+name = "asn1-rs-derive" -+version = "0.4.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" -+dependencies = [ -+ "proc-macro2", -+ "quote", -+ "syn", -+ "synstructure", -+] -+ -+[[package]] -+name = "asn1-rs-impl" -+version = "0.1.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" -+dependencies = [ -+ "proc-macro2", -+ "quote", -+ "syn", -+] -+ -+[[package]] -+name = "asserhttp" -+version = "0.6.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "337b30b878c9c627e04044451ed6c2626dae094674b76d26807ea88e2ed03274" -+dependencies = [ -+ "anyhow", -+ "assert-json-diff", -+ "futures-lite", -+ "http-types", -+ "regex", -+ "reqwest", -+ "serde", -+ "serde_json", -+ "tonic-build", -+] -+ -+[[package]] -+name = "assert-json-diff" -+version = "2.0.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" -+dependencies = [ -+ "serde", -+ "serde_json", -+] -+ -+[[package]] -+name = "async-channel" -+version = "1.8.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" -+dependencies = [ -+ "concurrent-queue", -+ "event-listener", -+ "futures-core", -+] -+ -+[[package]] -+name = "autocfg" -+version = "1.1.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -+ -+[[package]] -+name = "base16ct" -+version = "0.1.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" -+ -+[[package]] -+name = "base64" -+version = "0.13.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" -+ -+[[package]] -+name = "base64" -+version = "0.21.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" -+ -+[[package]] -+name = "base64ct" -+version = "1.6.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -+ -+[[package]] -+name = "binstring" -+version = "0.1.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "7e0d60973d9320722cb1206f412740e162a33b8547ea8d6be75d7cff237c7a85" -+ -+[[package]] -+name = "biscuit" -+version = "0.6.0-beta1" -+source = "git+https://github.com/beltram/biscuit?tag=v0.6.0-pre.core-crypto-0.6.0#36b6f30964142f0ca5bc17c908e8b2ad78febaf6" -+dependencies = [ -+ "base64 0.21.0", -+ "chrono", -+ "data-encoding", -+ "num-bigint", -+ "num-traits", -+ "once_cell", -+ "rand 0.8.5", -+ "ring 0.17.0-not-released-yet", -+ "serde", -+ "serde_json", -+] -+ -+[[package]] -+name = "bitflags" -+version = "1.3.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -+ -+[[package]] -+name = "block-buffer" -+version = "0.10.3" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" -+dependencies = [ -+ "generic-array", -+] -+ -+[[package]] -+name = "bollard-stubs" -+version = "1.41.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "ed2f2e73fffe9455141e170fb9c1feb0ac521ec7e7dcd47a7cab72a658490fb8" -+dependencies = [ -+ "chrono", -+ "serde", -+ "serde_with", -+] -+ -+[[package]] -+name = "bumpalo" -+version = "3.12.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" -+ -+[[package]] -+name = "byteorder" -+version = "1.4.3" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" -+ -+[[package]] -+name = "bytes" -+version = "1.4.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" -+ -+[[package]] -+name = "cc" -+version = "1.0.79" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" -+ -+[[package]] -+name = "cfg-if" -+version = "1.0.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -+ -+[[package]] -+name = "chrono" -+version = "0.4.23" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" -+dependencies = [ -+ "iana-time-zone", -+ "js-sys", -+ "num-integer", -+ "num-traits", -+ "serde", -+ "time 0.1.45", -+ "wasm-bindgen", -+ "winapi", -+] -+ -+[[package]] -+name = "clap" -+version = "4.1.8" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5" -+dependencies = [ -+ "bitflags", -+ "clap_derive", -+ "clap_lex", -+ "is-terminal", -+ "once_cell", -+ "strsim", -+ "termcolor", -+] -+ -+[[package]] -+name = "clap_derive" -+version = "4.1.8" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "44bec8e5c9d09e439c4335b1af0abaab56dcf3b94999a936e1bb47b9134288f0" -+dependencies = [ -+ "heck", -+ "proc-macro-error", -+ "proc-macro2", -+ "quote", -+ "syn", -+] -+ -+[[package]] -+name = "clap_lex" -+version = "0.3.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09" -+dependencies = [ -+ "os_str_bytes", -+] -+ -+[[package]] -+name = "coarsetime" -+version = "0.1.23" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "a90d114103adbc625300f346d4d09dfb4ab1c4a8df6868435dd903392ecf4354" -+dependencies = [ -+ "libc", -+ "once_cell", -+ "wasi 0.11.0+wasi-snapshot-preview1", -+ "wasm-bindgen", -+] -+ -+[[package]] -+name = "codespan-reporting" -+version = "0.11.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -+dependencies = [ -+ "termcolor", -+ "unicode-width", -+] -+ -+[[package]] -+name = "concurrent-queue" -+version = "2.1.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e" -+dependencies = [ -+ "crossbeam-utils", -+] -+ -+[[package]] -+name = "console" -+version = "0.15.5" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60" -+dependencies = [ -+ "encode_unicode", -+ "lazy_static", -+ "libc", -+ "unicode-width", -+ "windows-sys 0.42.0", -+] -+ -+[[package]] -+name = "console_error_panic_hook" -+version = "0.1.7" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" -+dependencies = [ -+ "cfg-if", -+ "wasm-bindgen", -+] -+ -+[[package]] -+name = "const-oid" -+version = "0.9.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" -+ -+[[package]] -+name = "convert_case" -+version = "0.4.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" -+ -+[[package]] -+name = "core-foundation-sys" -+version = "0.8.3" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" -+ -+[[package]] -+name = "cpufeatures" -+version = "0.2.5" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" -+dependencies = [ -+ "libc", -+] -+ -+[[package]] -+name = "crc32fast" -+version = "1.3.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -+dependencies = [ -+ "cfg-if", -+] -+ -+[[package]] -+name = "crossbeam-utils" -+version = "0.8.15" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" -+dependencies = [ -+ "cfg-if", -+] -+ -+[[package]] -+name = "crypto-bigint" -+version = "0.4.9" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" -+dependencies = [ -+ "generic-array", -+ "rand_core 0.6.4", -+ "subtle", -+ "zeroize", -+] -+ -+[[package]] -+name = "crypto-common" -+version = "0.1.6" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -+dependencies = [ -+ "generic-array", -+ "typenum", -+] -+ -+[[package]] -+name = "ct-codecs" -+version = "1.1.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "f3b7eb4404b8195a9abb6356f4ac07d8ba267045c8d6d220ac4dc992e6cc75df" -+ -+[[package]] -+name = "cxx" -+version = "1.0.91" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "86d3488e7665a7a483b57e25bdd90d0aeb2bc7608c8d0346acf2ad3f1caf1d62" -+dependencies = [ -+ "cc", -+ "cxxbridge-flags", -+ "cxxbridge-macro", -+ "link-cplusplus", -+] -+ -+[[package]] -+name = "cxx-build" -+version = "1.0.91" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "48fcaf066a053a41a81dfb14d57d99738b767febb8b735c3016e469fac5da690" -+dependencies = [ -+ "cc", -+ "codespan-reporting", -+ "once_cell", -+ "proc-macro2", -+ "quote", -+ "scratch", -+ "syn", -+] -+ -+[[package]] -+name = "cxxbridge-flags" -+version = "1.0.91" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "a2ef98b8b717a829ca5603af80e1f9e2e48013ab227b68ef37872ef84ee479bf" -+ -+[[package]] -+name = "cxxbridge-macro" -+version = "1.0.91" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892" -+dependencies = [ -+ "proc-macro2", -+ "quote", -+ "syn", -+] -+ -+[[package]] -+name = "darling" -+version = "0.13.4" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" -+dependencies = [ -+ "darling_core", -+ "darling_macro", -+] -+ -+[[package]] -+name = "darling_core" -+version = "0.13.4" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" -+dependencies = [ -+ "fnv", -+ "ident_case", -+ "proc-macro2", -+ "quote", -+ "strsim", -+ "syn", -+] -+ -+[[package]] -+name = "darling_macro" -+version = "0.13.4" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" -+dependencies = [ -+ "darling_core", -+ "quote", -+ "syn", -+] -+ -+[[package]] -+name = "data-encoding" -+version = "2.3.3" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" -+ -+[[package]] -+name = "der" -+version = "0.6.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" -+dependencies = [ -+ "const-oid", -+ "pem-rfc7468", -+ "zeroize", -+] -+ -+[[package]] -+name = "der-parser" -+version = "8.1.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "42d4bc9b0db0a0df9ae64634ac5bdefb7afcb534e182275ca0beadbe486701c1" -+dependencies = [ -+ "asn1-rs", -+ "displaydoc", -+ "nom", -+ "num-bigint", -+ "num-traits", -+ "rusticata-macros", -+] -+ -+[[package]] -+name = "derive_more" -+version = "0.99.17" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" -+dependencies = [ -+ "convert_case", -+ "proc-macro2", -+ "quote", -+ "rustc_version", -+ "syn", -+] -+ -+[[package]] -+name = "digest" -+version = "0.10.6" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" -+dependencies = [ -+ "block-buffer", -+ "const-oid", -+ "crypto-common", -+ "subtle", -+] -+ -+[[package]] -+name = "displaydoc" -+version = "0.2.3" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "3bf95dc3f046b9da4f2d51833c0d3547d8564ef6910f5c1ed130306a75b92886" -+dependencies = [ -+ "proc-macro2", -+ "quote", -+ "syn", -+] -+ -+[[package]] -+name = "ecdsa" -+version = "0.15.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "12844141594ad74185a926d030f3b605f6a903b4e3fec351f3ea338ac5b7637e" -+dependencies = [ -+ "der", -+ "elliptic-curve", -+ "rfc6979", -+ "signature 2.0.0", -+] -+ -+[[package]] -+name = "ed25519-compact" -+version = "2.0.4" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "6a3d382e8464107391c8706b4c14b087808ecb909f6c15c34114bc42e53a9e4c" -+dependencies = [ -+ "ct-codecs", -+ "getrandom 0.2.8", -+] -+ -+[[package]] -+name = "either" -+version = "1.8.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" -+dependencies = [ -+ "serde", -+] -+ -+[[package]] -+name = "elliptic-curve" -+version = "0.12.3" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" -+dependencies = [ -+ "base16ct", -+ "crypto-bigint", -+ "der", -+ "digest", -+ "ff", -+ "generic-array", -+ "group", -+ "hkdf", -+ "pem-rfc7468", -+ "pkcs8", -+ "rand_core 0.6.4", -+ "sec1", -+ "subtle", -+ "zeroize", -+] -+ -+[[package]] -+name = "encode_unicode" -+version = "0.3.6" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" -+ -+[[package]] -+name = "encoding_rs" -+version = "0.8.32" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" -+dependencies = [ -+ "cfg-if", -+] -+ -+[[package]] -+name = "errno" -+version = "0.2.8" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" -+dependencies = [ -+ "errno-dragonfly", -+ "libc", -+ "winapi", -+] -+ -+[[package]] -+name = "errno-dragonfly" -+version = "0.1.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -+dependencies = [ -+ "cc", -+ "libc", -+] -+ -+[[package]] -+name = "event-listener" -+version = "2.5.3" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" -+ -+[[package]] -+name = "fastrand" -+version = "1.9.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -+dependencies = [ -+ "instant", -+] -+ -+[[package]] -+name = "ff" -+version = "0.12.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" -+dependencies = [ -+ "rand_core 0.6.4", -+ "subtle", -+] -+ -+[[package]] -+name = "fixedbitset" -+version = "0.4.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" -+ -+[[package]] -+name = "flate2" -+version = "1.0.25" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" -+dependencies = [ -+ "crc32fast", -+ "miniz_oxide", -+] -+ -+[[package]] -+name = "fluvio-wasm-timer" -+version = "0.2.5" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "b768c170dc045fa587a8f948c91f9bcfb87f774930477c6215addf54317f137f" -+dependencies = [ -+ "futures", -+ "js-sys", -+ "parking_lot", -+ "pin-utils", -+ "wasm-bindgen", -+ "wasm-bindgen-futures", -+ "web-sys", -+] -+ -+[[package]] -+name = "fnv" -+version = "1.0.7" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -+ -+[[package]] -+name = "foreign-types" -+version = "0.3.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -+dependencies = [ -+ "foreign-types-shared", -+] -+ -+[[package]] -+name = "foreign-types-shared" -+version = "0.1.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" -+ -+[[package]] -+name = "form_urlencoded" -+version = "1.1.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" -+dependencies = [ -+ "percent-encoding", -+] -+ -+[[package]] -+name = "futures" -+version = "0.3.26" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" -+dependencies = [ -+ "futures-channel", -+ "futures-core", -+ "futures-executor", -+ "futures-io", -+ "futures-sink", -+ "futures-task", -+ "futures-util", -+] -+ -+[[package]] -+name = "futures-channel" -+version = "0.3.26" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" -+dependencies = [ -+ "futures-core", -+ "futures-sink", -+] -+ -+[[package]] -+name = "futures-core" -+version = "0.3.26" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" -+ -+[[package]] -+name = "futures-executor" -+version = "0.3.26" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" -+dependencies = [ -+ "futures-core", -+ "futures-task", -+ "futures-util", -+] -+ -+[[package]] -+name = "futures-io" -+version = "0.3.26" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" -+ -+[[package]] -+name = "futures-lite" -+version = "1.12.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" -+dependencies = [ -+ "fastrand", -+ "futures-core", -+ "futures-io", -+ "memchr", -+ "parking", -+ "pin-project-lite", -+ "waker-fn", -+] -+ -+[[package]] -+name = "futures-macro" -+version = "0.3.26" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" -+dependencies = [ -+ "proc-macro2", -+ "quote", -+ "syn", -+] -+ -+[[package]] -+name = "futures-sink" -+version = "0.3.26" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" -+ -+[[package]] -+name = "futures-task" -+version = "0.3.26" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" -+ -+[[package]] -+name = "futures-timer" -+version = "3.0.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" -+ -+[[package]] -+name = "futures-util" -+version = "0.3.26" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" -+dependencies = [ -+ "futures-channel", -+ "futures-core", -+ "futures-io", -+ "futures-macro", -+ "futures-sink", -+ "futures-task", -+ "memchr", -+ "pin-project-lite", -+ "pin-utils", -+ "slab", -+] -+ -+[[package]] -+name = "generic-array" -+version = "0.14.6" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" -+dependencies = [ -+ "typenum", -+ "version_check", -+] -+ -+[[package]] -+name = "getrandom" -+version = "0.1.16" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -+dependencies = [ -+ "cfg-if", -+ "libc", -+ "wasi 0.9.0+wasi-snapshot-preview1", -+] -+ -+[[package]] -+name = "getrandom" -+version = "0.2.8" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" -+dependencies = [ -+ "cfg-if", -+ "js-sys", -+ "libc", -+ "wasi 0.11.0+wasi-snapshot-preview1", -+ "wasm-bindgen", -+] -+ -+[[package]] -+name = "group" -+version = "0.12.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" -+dependencies = [ -+ "ff", -+ "rand_core 0.6.4", -+ "subtle", -+] -+ -+[[package]] -+name = "h2" -+version = "0.3.16" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" -+dependencies = [ -+ "bytes", -+ "fnv", -+ "futures-core", -+ "futures-sink", -+ "futures-util", -+ "http", -+ "indexmap", -+ "slab", -+ "tokio", -+ "tokio-util", -+ "tracing", -+] -+ -+[[package]] -+name = "hashbrown" -+version = "0.9.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" -+ -+[[package]] -+name = "heck" -+version = "0.4.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -+ -+[[package]] -+name = "hermit-abi" -+version = "0.2.6" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -+dependencies = [ -+ "libc", -+] -+ -+[[package]] -+name = "hermit-abi" -+version = "0.3.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" -+ -+[[package]] -+name = "hex" -+version = "0.4.3" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -+ -+[[package]] -+name = "hkdf" -+version = "0.12.3" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" -+dependencies = [ -+ "hmac", -+] -+ -+[[package]] -+name = "hmac" -+version = "0.12.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -+dependencies = [ -+ "digest", -+] -+ -+[[package]] -+name = "hmac-sha1-compact" -+version = "1.1.3" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "05e2440a0078e20c3b68ca01234cea4219f23e64b0c0bdb1200c5550d54239bb" -+ -+[[package]] -+name = "hmac-sha256" -+version = "1.1.6" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "fc736091aacb31ddaa4cd5f6988b3c21e99913ac846b41f32538c5fae5d71bfe" -+dependencies = [ -+ "digest", -+] -+ -+[[package]] -+name = "hmac-sha512" -+version = "1.1.4" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "520c9c3f6040661669bc5c91e551b605a520c8e0a63a766a91a65adef734d151" -+dependencies = [ -+ "digest", -+] -+ -+[[package]] -+name = "http" -+version = "0.2.9" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" -+dependencies = [ -+ "bytes", -+ "fnv", -+ "itoa", -+] -+ -+[[package]] -+name = "http-body" -+version = "0.4.5" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" -+dependencies = [ -+ "bytes", -+ "http", -+ "pin-project-lite", -+] -+ -+[[package]] -+name = "http-types" -+version = "2.12.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "6e9b187a72d63adbfba487f48095306ac823049cb504ee195541e91c7775f5ad" -+dependencies = [ -+ "anyhow", -+ "async-channel", -+ "base64 0.13.1", -+ "futures-lite", -+ "infer", -+ "pin-project-lite", -+ "rand 0.7.3", -+ "serde", -+ "serde_json", -+ "serde_qs", -+ "serde_urlencoded", -+ "url", -+] -+ -+[[package]] -+name = "httparse" -+version = "1.8.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" -+ -+[[package]] -+name = "httpdate" -+version = "1.0.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" -+ -+[[package]] -+name = "hyper" -+version = "0.14.24" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c" -+dependencies = [ -+ "bytes", -+ "futures-channel", -+ "futures-core", -+ "futures-util", -+ "h2", -+ "http", -+ "http-body", -+ "httparse", -+ "httpdate", -+ "itoa", -+ "pin-project-lite", -+ "socket2", -+ "tokio", -+ "tower-service", -+ "tracing", -+ "want", -+] -+ -+[[package]] -+name = "hyper-rustls" -+version = "0.23.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" -+dependencies = [ -+ "http", -+ "hyper", -+ "rustls", -+ "tokio", -+ "tokio-rustls", -+] -+ -+[[package]] -+name = "iana-time-zone" -+version = "0.1.53" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" -+dependencies = [ -+ "android_system_properties", -+ "core-foundation-sys", -+ "iana-time-zone-haiku", -+ "js-sys", -+ "wasm-bindgen", -+ "winapi", -+] -+ -+[[package]] -+name = "iana-time-zone-haiku" -+version = "0.1.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" -+dependencies = [ -+ "cxx", -+ "cxx-build", -+] -+ -+[[package]] -+name = "ident_case" -+version = "1.0.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -+ -+[[package]] -+name = "idna" -+version = "0.3.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" -+dependencies = [ -+ "unicode-bidi", -+ "unicode-normalization", -+] -+ -+[[package]] -+name = "indexmap" -+version = "1.6.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" -+dependencies = [ -+ "autocfg", -+ "hashbrown", -+] -+ -+[[package]] -+name = "infer" -+version = "0.2.3" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac" -+ -+[[package]] -+name = "instant" -+version = "0.1.12" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -+dependencies = [ -+ "cfg-if", -+ "js-sys", -+ "wasm-bindgen", -+ "web-sys", -+] -+ -+[[package]] -+name = "io-lifetimes" -+version = "1.0.5" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" -+dependencies = [ -+ "libc", -+ "windows-sys 0.45.0", -+] -+ -+[[package]] -+name = "ipnet" -+version = "2.7.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" -+ -+[[package]] -+name = "is-terminal" -+version = "0.4.4" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857" -+dependencies = [ -+ "hermit-abi 0.3.1", -+ "io-lifetimes", -+ "rustix", -+ "windows-sys 0.45.0", -+] -+ -+[[package]] -+name = "itertools" -+version = "0.10.5" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -+dependencies = [ -+ "either", -+] -+ -+[[package]] -+name = "itoa" -+version = "1.0.5" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" -+ -+[[package]] -+name = "josekit" -+version = "0.8.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "9ef871a7a5f162afa718c416e9cbdd54241a58c922e07870e898ebad2425d8d8" -+dependencies = [ -+ "anyhow", -+ "base64 0.13.1", -+ "flate2", -+ "once_cell", -+ "openssl", -+ "regex", -+ "serde", -+ "serde_json", -+ "thiserror", -+ "time 0.3.20", -+] -+ -+[[package]] -+name = "js-sys" -+version = "0.3.61" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" -+dependencies = [ -+ "wasm-bindgen", -+] -+ -+[[package]] -+name = "json-patch" -+version = "0.3.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "e712e62827c382a77b87f590532febb1f8b2fdbc3eefa1ee37fe7281687075ef" -+dependencies = [ -+ "serde", -+ "serde_json", -+ "thiserror", -+ "treediff", -+] -+ -+[[package]] -+name = "jwt-simple" -+version = "0.11.3" -+source = "git+https://github.com/wireapp/rust-jwt-simple?tag=v0.11.3-pre.core-crypto-0.6.0#15a69f82288d68b74a75c1364e5d4bf681f1c07b" -+dependencies = [ -+ "anyhow", -+ "binstring", -+ "coarsetime", -+ "ct-codecs", -+ "ed25519-compact", -+ "hmac-sha1-compact", -+ "hmac-sha256", -+ "hmac-sha512", -+ "k256", -+ "p256", -+ "p384", -+ "rand 0.8.5", -+ "rsa", -+ "serde", -+ "serde_json", -+ "spki", -+ "thiserror", -+ "zeroize", -+] -+ -+[[package]] -+name = "k256" -+version = "0.12.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "92a55e0ff3b72c262bcf041d9e97f1b84492b68f1c1a384de2323d3dc9403397" -+dependencies = [ -+ "cfg-if", -+ "ecdsa", -+ "elliptic-curve", -+ "once_cell", -+ "sha2", -+ "signature 2.0.0", -+] -+ -+[[package]] -+name = "lazy_static" -+version = "1.4.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -+dependencies = [ -+ "spin 0.5.2", -+] -+ -+[[package]] -+name = "libc" -+version = "0.2.139" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" -+ -+[[package]] -+name = "libm" -+version = "0.2.6" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" -+ -+[[package]] -+name = "link-cplusplus" -+version = "1.0.8" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" -+dependencies = [ -+ "cc", -+] -+ -+[[package]] -+name = "linux-raw-sys" -+version = "0.1.4" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" -+ -+[[package]] -+name = "lock_api" -+version = "0.4.9" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" -+dependencies = [ -+ "autocfg", -+ "scopeguard", -+] -+ -+[[package]] -+name = "log" -+version = "0.4.17" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -+dependencies = [ -+ "cfg-if", -+] -+ -+[[package]] -+name = "memchr" -+version = "2.5.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" -+ -+[[package]] -+name = "mime" -+version = "0.3.16" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" -+ -+[[package]] -+name = "minimal-lexical" -+version = "0.2.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -+ -+[[package]] -+name = "miniz_oxide" -+version = "0.6.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" -+dependencies = [ -+ "adler", -+] -+ -+[[package]] -+name = "mio" -+version = "0.8.6" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" -+dependencies = [ -+ "libc", -+ "log", -+ "wasi 0.11.0+wasi-snapshot-preview1", -+ "windows-sys 0.45.0", -+] -+ -+[[package]] -+name = "multimap" -+version = "0.8.3" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" -+ -+[[package]] -+name = "nom" -+version = "7.1.3" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -+dependencies = [ -+ "memchr", -+ "minimal-lexical", -+] -+ -+[[package]] -+name = "num-bigint" -+version = "0.4.3" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" -+dependencies = [ -+ "autocfg", -+ "num-integer", -+ "num-traits", -+] -+ -+[[package]] -+name = "num-bigint-dig" -+version = "0.8.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "2399c9463abc5f909349d8aa9ba080e0b88b3ce2885389b60b993f39b1a56905" -+dependencies = [ -+ "byteorder", -+ "lazy_static", -+ "libm", -+ "num-integer", -+ "num-iter", -+ "num-traits", -+ "rand 0.8.5", -+ "smallvec", -+ "zeroize", -+] -+ -+[[package]] -+name = "num-integer" -+version = "0.1.45" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -+dependencies = [ -+ "autocfg", -+ "num-traits", -+] -+ -+[[package]] -+name = "num-iter" -+version = "0.1.43" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" -+dependencies = [ -+ "autocfg", -+ "num-integer", -+ "num-traits", -+] -+ -+[[package]] -+name = "num-traits" -+version = "0.2.15" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -+dependencies = [ -+ "autocfg", -+ "libm", -+] -+ -+[[package]] -+name = "num_cpus" -+version = "1.15.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" -+dependencies = [ -+ "hermit-abi 0.2.6", -+ "libc", -+] -+ -+[[package]] -+name = "oid-registry" -+version = "0.6.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" -+dependencies = [ -+ "asn1-rs", -+] -+ -+[[package]] -+name = "once_cell" -+version = "1.17.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" -+ -+[[package]] -+name = "openssl" -+version = "0.10.45" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" -+dependencies = [ -+ "bitflags", -+ "cfg-if", -+ "foreign-types", -+ "libc", -+ "once_cell", -+ "openssl-macros", -+ "openssl-sys", -+] -+ -+[[package]] -+name = "openssl-macros" -+version = "0.1.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" -+dependencies = [ -+ "proc-macro2", -+ "quote", -+ "syn", -+] -+ -+[[package]] -+name = "openssl-sys" -+version = "0.9.80" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" -+dependencies = [ -+ "autocfg", -+ "cc", -+ "libc", -+ "pkg-config", -+ "vcpkg", -+] -+ -+[[package]] -+name = "os_str_bytes" -+version = "6.4.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" -+ -+[[package]] -+name = "p256" -+version = "0.12.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "49c124b3cbce43bcbac68c58ec181d98ed6cc7e6d0aa7c3ba97b2563410b0e55" -+dependencies = [ -+ "ecdsa", -+ "elliptic-curve", -+ "primeorder", -+ "sha2", -+] -+ -+[[package]] -+name = "p384" -+version = "0.12.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "630a4a9b2618348ececfae61a4905f564b817063bf2d66cdfc2ced523fe1d2d4" -+dependencies = [ -+ "ecdsa", -+ "elliptic-curve", -+ "primeorder", -+ "sha2", -+] -+ -+[[package]] -+name = "parking" -+version = "2.0.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" -+ -+[[package]] -+name = "parking_lot" -+version = "0.11.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -+dependencies = [ -+ "instant", -+ "lock_api", -+ "parking_lot_core", -+] -+ -+[[package]] -+name = "parking_lot_core" -+version = "0.8.6" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -+dependencies = [ -+ "cfg-if", -+ "instant", -+ "libc", -+ "redox_syscall", -+ "smallvec", -+ "winapi", -+] -+ -+[[package]] -+name = "pem" -+version = "1.1.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" -+dependencies = [ -+ "base64 0.13.1", -+] -+ -+[[package]] -+name = "pem-rfc7468" -+version = "0.6.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "24d159833a9105500e0398934e205e0773f0b27529557134ecfc51c27646adac" -+dependencies = [ -+ "base64ct", -+] -+ -+[[package]] -+name = "percent-encoding" -+version = "2.2.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" -+ -+[[package]] -+name = "petgraph" -+version = "0.6.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" -+dependencies = [ -+ "fixedbitset", -+ "indexmap", -+] -+ -+[[package]] -+name = "pin-project-lite" -+version = "0.2.9" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" -+ -+[[package]] -+name = "pin-utils" -+version = "0.1.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -+ -+[[package]] -+name = "pkcs1" -+version = "0.4.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "eff33bdbdfc54cc98a2eca766ebdec3e1b8fb7387523d5c9c9a2891da856f719" -+dependencies = [ -+ "der", -+ "pkcs8", -+ "spki", -+ "zeroize", -+] -+ -+[[package]] -+name = "pkcs8" -+version = "0.9.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" -+dependencies = [ -+ "der", -+ "spki", -+] -+ -+[[package]] -+name = "pkg-config" -+version = "0.3.26" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" -+ -+[[package]] -+name = "ppv-lite86" -+version = "0.2.17" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" -+ -+[[package]] -+name = "prettyplease" -+version = "0.1.23" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "e97e3215779627f01ee256d2fad52f3d95e8e1c11e9fc6fd08f7cd455d5d5c78" -+dependencies = [ -+ "proc-macro2", -+ "syn", -+] -+ -+[[package]] -+name = "primeorder" -+version = "0.12.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "0b54f7131b3dba65a2f414cf5bd25b66d4682e4608610668eae785750ba4c5b2" -+dependencies = [ -+ "elliptic-curve", -+] -+ -+[[package]] -+name = "proc-macro-error" -+version = "1.0.4" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -+dependencies = [ -+ "proc-macro-error-attr", -+ "proc-macro2", -+ "quote", -+ "syn", -+ "version_check", -+] -+ -+[[package]] -+name = "proc-macro-error-attr" -+version = "1.0.4" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -+dependencies = [ -+ "proc-macro2", -+ "quote", -+ "version_check", -+] -+ -+[[package]] -+name = "proc-macro2" -+version = "1.0.51" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" -+dependencies = [ -+ "unicode-ident", -+] -+ -+[[package]] -+name = "prost" -+version = "0.11.8" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "e48e50df39172a3e7eb17e14642445da64996989bc212b583015435d39a58537" -+dependencies = [ -+ "bytes", -+ "prost-derive", -+] -+ -+[[package]] -+name = "prost-build" -+version = "0.11.8" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "2c828f93f5ca4826f97fedcbd3f9a536c16b12cff3dbbb4a007f932bbad95b12" -+dependencies = [ -+ "bytes", -+ "heck", -+ "itertools", -+ "lazy_static", -+ "log", -+ "multimap", -+ "petgraph", -+ "prettyplease", -+ "prost", -+ "prost-types", -+ "regex", -+ "syn", -+ "tempfile", -+ "which", -+] -+ -+[[package]] -+name = "prost-derive" -+version = "0.11.8" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "4ea9b0f8cbe5e15a8a042d030bd96668db28ecb567ec37d691971ff5731d2b1b" -+dependencies = [ -+ "anyhow", -+ "itertools", -+ "proc-macro2", -+ "quote", -+ "syn", -+] -+ -+[[package]] -+name = "prost-types" -+version = "0.11.8" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "379119666929a1afd7a043aa6cf96fa67a6dce9af60c88095a4686dbce4c9c88" -+dependencies = [ -+ "prost", -+] -+ -+[[package]] -+name = "quote" -+version = "1.0.23" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" -+dependencies = [ -+ "proc-macro2", -+] -+ -+[[package]] -+name = "rand" -+version = "0.7.3" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -+dependencies = [ -+ "getrandom 0.1.16", -+ "libc", -+ "rand_chacha 0.2.2", -+ "rand_core 0.5.1", -+ "rand_hc", -+] -+ -+[[package]] -+name = "rand" -+version = "0.8.5" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -+dependencies = [ -+ "libc", -+ "rand_chacha 0.3.1", -+ "rand_core 0.6.4", -+] -+ -+[[package]] -+name = "rand_chacha" -+version = "0.2.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -+dependencies = [ -+ "ppv-lite86", -+ "rand_core 0.5.1", -+] -+ -+[[package]] -+name = "rand_chacha" -+version = "0.3.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -+dependencies = [ -+ "ppv-lite86", -+ "rand_core 0.6.4", -+] -+ -+[[package]] -+name = "rand_core" -+version = "0.5.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -+dependencies = [ -+ "getrandom 0.1.16", -+] -+ -+[[package]] -+name = "rand_core" -+version = "0.6.4" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -+dependencies = [ -+ "getrandom 0.2.8", -+] -+ -+[[package]] -+name = "rand_hc" -+version = "0.2.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -+dependencies = [ -+ "rand_core 0.5.1", -+] -+ -+[[package]] -+name = "rcgen" -+version = "0.9.2" -+source = "git+https://github.com/wireapp/rcgen?tag=v1.2.0-pre.core-crypto-0.6.0#1e893c3444a9a1625dc8f3fe7be6df025f64646a" -+dependencies = [ -+ "pem", -+ "ring 0.17.0-not-released-yet", -+ "time 0.3.20", -+ "yasna", -+] -+ -+[[package]] -+name = "redox_syscall" -+version = "0.2.16" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -+dependencies = [ -+ "bitflags", -+] -+ -+[[package]] -+name = "regex" -+version = "1.7.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" -+dependencies = [ -+ "aho-corasick", -+ "memchr", -+ "regex-syntax", -+] -+ -+[[package]] -+name = "regex-syntax" -+version = "0.6.28" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" -+ -+[[package]] -+name = "reqwest" -+version = "0.11.14" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" -+dependencies = [ -+ "base64 0.21.0", -+ "bytes", -+ "encoding_rs", -+ "futures-core", -+ "futures-util", -+ "h2", -+ "http", -+ "http-body", -+ "hyper", -+ "hyper-rustls", -+ "ipnet", -+ "js-sys", -+ "log", -+ "mime", -+ "once_cell", -+ "percent-encoding", -+ "pin-project-lite", -+ "rustls", -+ "rustls-pemfile", -+ "serde", -+ "serde_json", -+ "serde_urlencoded", -+ "tokio", -+ "tokio-rustls", -+ "tower-service", -+ "url", -+ "wasm-bindgen", -+ "wasm-bindgen-futures", -+ "web-sys", -+ "winreg", -+] -+ -+[[package]] -+name = "rfc6979" -+version = "0.3.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" -+dependencies = [ -+ "crypto-bigint", -+ "hmac", -+ "zeroize", -+] -+ -+[[package]] -+name = "ring" -+version = "0.16.20" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -+dependencies = [ -+ "cc", -+ "libc", -+ "once_cell", -+ "spin 0.5.2", -+ "untrusted 0.7.1", -+ "web-sys", -+ "winapi", -+] -+ -+[[package]] -+name = "ring" -+version = "0.17.0-not-released-yet" -+source = "git+https://github.com/briansmith/ring.git?rev=450ada28#450ada288f1805795140097ec96396b890bcf722" -+dependencies = [ -+ "cc", -+ "getrandom 0.2.8", -+ "libc", -+ "spin 0.9.5", -+ "untrusted 0.9.0", -+ "winapi", -+] -+ -+[[package]] -+name = "rsa" -+version = "0.7.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "094052d5470cbcef561cb848a7209968c9f12dfa6d668f4bca048ac5de51099c" -+dependencies = [ -+ "byteorder", -+ "digest", -+ "num-bigint-dig", -+ "num-integer", -+ "num-iter", -+ "num-traits", -+ "pkcs1", -+ "pkcs8", -+ "rand_core 0.6.4", -+ "signature 1.6.4", -+ "smallvec", -+ "subtle", -+ "zeroize", -+] -+ -+[[package]] -+name = "rstest" -+version = "0.16.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "b07f2d176c472198ec1e6551dc7da28f1c089652f66a7b722676c2238ebc0edf" -+dependencies = [ -+ "futures", -+ "futures-timer", -+ "rstest_macros", -+ "rustc_version", -+] -+ -+[[package]] -+name = "rstest_macros" -+version = "0.16.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "7229b505ae0706e64f37ffc54a9c163e11022a6636d58fe1f3f52018257ff9f7" -+dependencies = [ -+ "cfg-if", -+ "proc-macro2", -+ "quote", -+ "rustc_version", -+ "syn", -+ "unicode-ident", -+] -+ -+[[package]] -+name = "rstest_reuse" -+version = "0.5.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "45f80dcc84beab3a327bbe161f77db25f336a1452428176787c8c79ac79d7073" -+dependencies = [ -+ "quote", -+ "rand 0.8.5", -+ "rustc_version", -+ "syn", -+] -+ -+[[package]] -+name = "rustc_version" -+version = "0.4.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -+dependencies = [ -+ "semver", -+] -+ -+[[package]] -+name = "rusticata-macros" -+version = "4.1.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" -+dependencies = [ -+ "nom", -+] -+ -+[[package]] -+name = "rustix" -+version = "0.36.8" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" -+dependencies = [ -+ "bitflags", -+ "errno", -+ "io-lifetimes", -+ "libc", -+ "linux-raw-sys", -+ "windows-sys 0.45.0", -+] -+ -+[[package]] -+name = "rustls" -+version = "0.20.8" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" -+dependencies = [ -+ "log", -+ "ring 0.16.20", -+ "sct", -+ "webpki", -+] -+ -+[[package]] -+name = "rustls-pemfile" -+version = "1.0.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" -+dependencies = [ -+ "base64 0.21.0", -+] -+ -+[[package]] -+name = "rusty-acme" -+version = "0.2.0" -+dependencies = [ -+ "base64 0.21.0", -+ "jwt-simple", -+ "rcgen", -+ "reqwest", -+ "rusty-jwt-tools", -+ "serde", -+ "serde_json", -+ "testcontainers", -+ "thiserror", -+ "time 0.3.20", -+ "url", -+ "wasm-bindgen-test", -+ "x509-parser", -+] -+ -+[[package]] -+name = "rusty-jwt-cli" -+version = "0.2.0" -+dependencies = [ -+ "anyhow", -+ "clap", -+ "console", -+ "jwt-simple", -+ "rusty-jwt-tools", -+ "serde_json", -+] -+ -+[[package]] -+name = "rusty-jwt-tools" -+version = "0.2.0" -+dependencies = [ -+ "base64 0.21.0", -+ "biscuit", -+ "chrono", -+ "ed25519-compact", -+ "either", -+ "fluvio-wasm-timer", -+ "indexmap", -+ "josekit", -+ "json-patch", -+ "jwt-simple", -+ "p256", -+ "p384", -+ "rand 0.8.5", -+ "rand_chacha 0.3.1", -+ "reqwest", -+ "rstest", -+ "rstest_reuse", -+ "sec1", -+ "serde", -+ "serde_json", -+ "sha2", -+ "thiserror", -+ "time 0.3.20", -+ "url", -+ "uuid", -+ "wasm-bindgen-test", -+ "zeroize", -+] -+ -+[[package]] -+name = "rusty-jwt-tools-ffi" -+version = "0.2.0" -+dependencies = [ -+ "rusty-jwt-tools", -+ "uuid", -+] -+ -+[[package]] -+name = "ryu" -+version = "1.0.12" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" -+ -+[[package]] -+name = "scoped-tls" -+version = "1.0.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" -+ -+[[package]] -+name = "scopeguard" -+version = "1.1.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -+ -+[[package]] -+name = "scratch" -+version = "1.0.3" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" -+ -+[[package]] -+name = "sct" -+version = "0.7.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" -+dependencies = [ -+ "ring 0.16.20", -+ "untrusted 0.7.1", -+] -+ -+[[package]] -+name = "sec1" -+version = "0.3.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" -+dependencies = [ -+ "base16ct", -+ "der", -+ "generic-array", -+ "pkcs8", -+ "subtle", -+ "zeroize", -+] -+ -+[[package]] -+name = "semver" -+version = "1.0.16" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" -+ -+[[package]] -+name = "serde" -+version = "1.0.152" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" -+dependencies = [ -+ "serde_derive", -+] -+ -+[[package]] -+name = "serde_derive" -+version = "1.0.152" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" -+dependencies = [ -+ "proc-macro2", -+ "quote", -+ "syn", -+] -+ -+[[package]] -+name = "serde_json" -+version = "1.0.93" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" -+dependencies = [ -+ "indexmap", -+ "itoa", -+ "ryu", -+ "serde", -+] -+ -+[[package]] -+name = "serde_qs" -+version = "0.8.5" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6" -+dependencies = [ -+ "percent-encoding", -+ "serde", -+ "thiserror", -+] -+ -+[[package]] -+name = "serde_urlencoded" -+version = "0.7.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -+dependencies = [ -+ "form_urlencoded", -+ "itoa", -+ "ryu", -+ "serde", -+] -+ -+[[package]] -+name = "serde_with" -+version = "1.14.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" -+dependencies = [ -+ "serde", -+ "serde_with_macros", -+] -+ -+[[package]] -+name = "serde_with_macros" -+version = "1.5.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" -+dependencies = [ -+ "darling", -+ "proc-macro2", -+ "quote", -+ "syn", -+] -+ -+[[package]] -+name = "sha2" -+version = "0.10.6" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" -+dependencies = [ -+ "cfg-if", -+ "cpufeatures", -+ "digest", -+] -+ -+[[package]] -+name = "signature" -+version = "1.6.4" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" -+dependencies = [ -+ "digest", -+ "rand_core 0.6.4", -+] -+ -+[[package]] -+name = "signature" -+version = "2.0.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "8fe458c98333f9c8152221191a77e2a44e8325d0193484af2e9421a53019e57d" -+dependencies = [ -+ "digest", -+ "rand_core 0.6.4", -+] -+ -+[[package]] -+name = "slab" -+version = "0.4.8" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" -+dependencies = [ -+ "autocfg", -+] -+ -+[[package]] -+name = "smallvec" -+version = "1.10.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" -+ -+[[package]] -+name = "socket2" -+version = "0.4.7" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" -+dependencies = [ -+ "libc", -+ "winapi", -+] -+ -+[[package]] -+name = "spin" -+version = "0.5.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -+ -+[[package]] -+name = "spin" -+version = "0.9.5" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "7dccf47db1b41fa1573ed27ccf5e08e3ca771cb994f776668c5ebda893b248fc" -+ -+[[package]] -+name = "spki" -+version = "0.6.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" -+dependencies = [ -+ "base64ct", -+ "der", -+] -+ -+[[package]] -+name = "strsim" -+version = "0.10.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" -+ -+[[package]] -+name = "subtle" -+version = "2.4.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" -+ -+[[package]] -+name = "syn" -+version = "1.0.109" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -+dependencies = [ -+ "proc-macro2", -+ "quote", -+ "unicode-ident", -+] -+ -+[[package]] -+name = "synstructure" -+version = "0.12.6" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" -+dependencies = [ -+ "proc-macro2", -+ "quote", -+ "syn", -+ "unicode-xid", -+] -+ -+[[package]] -+name = "tempfile" -+version = "3.4.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" -+dependencies = [ -+ "cfg-if", -+ "fastrand", -+ "redox_syscall", -+ "rustix", -+ "windows-sys 0.42.0", -+] -+ -+[[package]] -+name = "termcolor" -+version = "1.2.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" -+dependencies = [ -+ "winapi-util", -+] -+ -+[[package]] -+name = "testcontainers" -+version = "0.14.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "0e2b1567ca8a2b819ea7b28c92be35d9f76fb9edb214321dcc86eb96023d1f87" -+dependencies = [ -+ "bollard-stubs", -+ "futures", -+ "hex", -+ "hmac", -+ "log", -+ "rand 0.8.5", -+ "serde", -+ "serde_json", -+ "sha2", -+] -+ -+[[package]] -+name = "thiserror" -+version = "1.0.38" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" -+dependencies = [ -+ "thiserror-impl", -+] -+ -+[[package]] -+name = "thiserror-impl" -+version = "1.0.38" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" -+dependencies = [ -+ "proc-macro2", -+ "quote", -+ "syn", -+] -+ -+[[package]] -+name = "time" -+version = "0.1.45" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -+dependencies = [ -+ "libc", -+ "wasi 0.10.0+wasi-snapshot-preview1", -+ "winapi", -+] -+ -+[[package]] -+name = "time" -+version = "0.3.20" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" -+dependencies = [ -+ "itoa", -+ "js-sys", -+ "serde", -+ "time-core", -+ "time-macros", -+] -+ -+[[package]] -+name = "time-core" -+version = "0.1.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" -+ -+[[package]] -+name = "time-macros" -+version = "0.2.8" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" -+dependencies = [ -+ "time-core", -+] -+ -+[[package]] -+name = "tinyvec" -+version = "1.6.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -+dependencies = [ -+ "tinyvec_macros", -+] -+ -+[[package]] -+name = "tinyvec_macros" -+version = "0.1.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" -+ -+[[package]] -+name = "tokio" -+version = "1.25.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" -+dependencies = [ -+ "autocfg", -+ "bytes", -+ "libc", -+ "memchr", -+ "mio", -+ "num_cpus", -+ "pin-project-lite", -+ "socket2", -+ "tokio-macros", -+ "windows-sys 0.42.0", -+] -+ -+[[package]] -+name = "tokio-macros" -+version = "1.8.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" -+dependencies = [ -+ "proc-macro2", -+ "quote", -+ "syn", -+] -+ -+[[package]] -+name = "tokio-rustls" -+version = "0.23.4" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" -+dependencies = [ -+ "rustls", -+ "tokio", -+ "webpki", -+] -+ -+[[package]] -+name = "tokio-util" -+version = "0.7.7" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" -+dependencies = [ -+ "bytes", -+ "futures-core", -+ "futures-sink", -+ "pin-project-lite", -+ "tokio", -+ "tracing", -+] -+ -+[[package]] -+name = "tonic-build" -+version = "0.8.4" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4" -+dependencies = [ -+ "prettyplease", -+ "proc-macro2", -+ "prost-build", -+ "quote", -+ "syn", -+] -+ -+[[package]] -+name = "tower-service" -+version = "0.3.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" -+ -+[[package]] -+name = "tracing" -+version = "0.1.37" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" -+dependencies = [ -+ "cfg-if", -+ "pin-project-lite", -+ "tracing-core", -+] -+ -+[[package]] -+name = "tracing-core" -+version = "0.1.30" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" -+dependencies = [ -+ "once_cell", -+] -+ -+[[package]] -+name = "treediff" -+version = "4.0.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "52984d277bdf2a751072b5df30ec0377febdb02f7696d64c2d7d54630bac4303" -+dependencies = [ -+ "serde_json", -+] -+ -+[[package]] -+name = "try-lock" -+version = "0.2.4" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" -+ -+[[package]] -+name = "typenum" -+version = "1.16.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" -+ -+[[package]] -+name = "unicode-bidi" -+version = "0.3.10" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" -+ -+[[package]] -+name = "unicode-ident" -+version = "1.0.6" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" -+ -+[[package]] -+name = "unicode-normalization" -+version = "0.1.22" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -+dependencies = [ -+ "tinyvec", -+] -+ -+[[package]] -+name = "unicode-width" -+version = "0.1.10" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" -+ -+[[package]] -+name = "unicode-xid" -+version = "0.2.4" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" -+ -+[[package]] -+name = "untrusted" -+version = "0.7.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" -+ -+[[package]] -+name = "untrusted" -+version = "0.9.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -+ -+[[package]] -+name = "url" -+version = "2.3.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" -+dependencies = [ -+ "form_urlencoded", -+ "idna", -+ "percent-encoding", -+ "serde", -+] -+ -+[[package]] -+name = "uuid" -+version = "1.3.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79" -+dependencies = [ -+ "getrandom 0.2.8", -+] -+ -+[[package]] -+name = "vcpkg" -+version = "0.2.15" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -+ -+[[package]] -+name = "version_check" -+version = "0.9.4" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -+ -+[[package]] -+name = "waker-fn" -+version = "1.1.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" -+ -+[[package]] -+name = "want" -+version = "0.3.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -+dependencies = [ -+ "log", -+ "try-lock", -+] -+ -+[[package]] -+name = "wasi" -+version = "0.9.0+wasi-snapshot-preview1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" -+ -+[[package]] -+name = "wasi" -+version = "0.10.0+wasi-snapshot-preview1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" -+ -+[[package]] -+name = "wasi" -+version = "0.11.0+wasi-snapshot-preview1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -+ -+[[package]] -+name = "wasm-bindgen" -+version = "0.2.84" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" -+dependencies = [ -+ "cfg-if", -+ "wasm-bindgen-macro", -+] -+ -+[[package]] -+name = "wasm-bindgen-backend" -+version = "0.2.84" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" -+dependencies = [ -+ "bumpalo", -+ "log", -+ "once_cell", -+ "proc-macro2", -+ "quote", -+ "syn", -+ "wasm-bindgen-shared", -+] -+ -+[[package]] -+name = "wasm-bindgen-futures" -+version = "0.4.34" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" -+dependencies = [ -+ "cfg-if", -+ "js-sys", -+ "wasm-bindgen", -+ "web-sys", -+] -+ -+[[package]] -+name = "wasm-bindgen-macro" -+version = "0.2.84" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" -+dependencies = [ -+ "quote", -+ "wasm-bindgen-macro-support", -+] -+ -+[[package]] -+name = "wasm-bindgen-macro-support" -+version = "0.2.84" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" -+dependencies = [ -+ "proc-macro2", -+ "quote", -+ "syn", -+ "wasm-bindgen-backend", -+ "wasm-bindgen-shared", -+] -+ -+[[package]] -+name = "wasm-bindgen-shared" -+version = "0.2.84" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" -+ -+[[package]] -+name = "wasm-bindgen-test" -+version = "0.3.34" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "6db36fc0f9fb209e88fb3642590ae0205bb5a56216dabd963ba15879fe53a30b" -+dependencies = [ -+ "console_error_panic_hook", -+ "js-sys", -+ "scoped-tls", -+ "wasm-bindgen", -+ "wasm-bindgen-futures", -+ "wasm-bindgen-test-macro", -+] -+ -+[[package]] -+name = "wasm-bindgen-test-macro" -+version = "0.3.34" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "0734759ae6b3b1717d661fe4f016efcfb9828f5edb4520c18eaee05af3b43be9" -+dependencies = [ -+ "proc-macro2", -+ "quote", -+] -+ -+[[package]] -+name = "web-sys" -+version = "0.3.61" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" -+dependencies = [ -+ "js-sys", -+ "wasm-bindgen", -+] -+ -+[[package]] -+name = "webpki" -+version = "0.22.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" -+dependencies = [ -+ "ring 0.16.20", -+ "untrusted 0.7.1", -+] -+ -+[[package]] -+name = "which" -+version = "4.4.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" -+dependencies = [ -+ "either", -+ "libc", -+ "once_cell", -+] -+ -+[[package]] -+name = "winapi" -+version = "0.3.9" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -+dependencies = [ -+ "winapi-i686-pc-windows-gnu", -+ "winapi-x86_64-pc-windows-gnu", -+] -+ -+[[package]] -+name = "winapi-i686-pc-windows-gnu" -+version = "0.4.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -+ -+[[package]] -+name = "winapi-util" -+version = "0.1.5" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -+dependencies = [ -+ "winapi", -+] -+ -+[[package]] -+name = "winapi-x86_64-pc-windows-gnu" -+version = "0.4.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -+ -+[[package]] -+name = "windows-sys" -+version = "0.42.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -+dependencies = [ -+ "windows_aarch64_gnullvm", -+ "windows_aarch64_msvc", -+ "windows_i686_gnu", -+ "windows_i686_msvc", -+ "windows_x86_64_gnu", -+ "windows_x86_64_gnullvm", -+ "windows_x86_64_msvc", -+] -+ -+[[package]] -+name = "windows-sys" -+version = "0.45.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -+dependencies = [ -+ "windows-targets", -+] -+ -+[[package]] -+name = "windows-targets" -+version = "0.42.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" -+dependencies = [ -+ "windows_aarch64_gnullvm", -+ "windows_aarch64_msvc", -+ "windows_i686_gnu", -+ "windows_i686_msvc", -+ "windows_x86_64_gnu", -+ "windows_x86_64_gnullvm", -+ "windows_x86_64_msvc", -+] -+ -+[[package]] -+name = "windows_aarch64_gnullvm" -+version = "0.42.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" -+ -+[[package]] -+name = "windows_aarch64_msvc" -+version = "0.42.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" -+ -+[[package]] -+name = "windows_i686_gnu" -+version = "0.42.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" -+ -+[[package]] -+name = "windows_i686_msvc" -+version = "0.42.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" -+ -+[[package]] -+name = "windows_x86_64_gnu" -+version = "0.42.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" -+ -+[[package]] -+name = "windows_x86_64_gnullvm" -+version = "0.42.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" -+ -+[[package]] -+name = "windows_x86_64_msvc" -+version = "0.42.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" -+ -+[[package]] -+name = "winreg" -+version = "0.10.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" -+dependencies = [ -+ "winapi", -+] -+ -+[[package]] -+name = "wire-e2e-identity" -+version = "0.2.0" -+dependencies = [ -+ "asserhttp", -+ "base64 0.21.0", -+ "derive_more", -+ "hex", -+ "hyper", -+ "itertools", -+ "jwt-simple", -+ "rand 0.8.5", -+ "reqwest", -+ "rusty-acme", -+ "rusty-jwt-tools", -+ "serde", -+ "serde_json", -+ "testcontainers", -+ "thiserror", -+ "tokio", -+ "url", -+ "uuid", -+ "wasm-bindgen-test", -+ "x509-parser", -+] -+ -+[[package]] -+name = "x509-parser" -+version = "0.14.0" -+source = "git+https://github.com/wireapp/x509-parser?tag=v1.0.2-pre.core-crypto-0.6.0#4fbc4c795fc84537ba24eb91e563c9e80d3bd5d4" -+dependencies = [ -+ "asn1-rs", -+ "base64 0.21.0", -+ "chrono", -+ "data-encoding", -+ "der-parser", -+ "lazy_static", -+ "nom", -+ "oid-registry", -+ "ring 0.17.0-not-released-yet", -+ "rusticata-macros", -+ "thiserror", -+] -+ -+[[package]] -+name = "yasna" -+version = "0.5.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "aed2e7a52e3744ab4d0c05c20aa065258e84c49fd4226f5191b2ed29712710b4" -+dependencies = [ -+ "time 0.3.20", -+] -+ -+[[package]] -+name = "zeroize" -+version = "1.5.7" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" -+dependencies = [ -+ "zeroize_derive", -+] -+ -+[[package]] -+name = "zeroize_derive" -+version = "1.3.3" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" -+dependencies = [ -+ "proc-macro2", -+ "quote", -+ "syn", -+ "synstructure", -+] --- -2.34.1 - diff --git a/nix/pkgs/rusty_jwt_tools_ffi/default.nix b/nix/pkgs/rusty_jwt_tools_ffi/default.nix index 04cd3597c7..7bb8570965 100644 --- a/nix/pkgs/rusty_jwt_tools_ffi/default.nix +++ b/nix/pkgs/rusty_jwt_tools_ffi/default.nix @@ -6,35 +6,34 @@ , gitMinimal }: -rustPlatform.buildRustPackage rec { - name = "rusty_jwt-tools_ffi-${version}"; - version = "0.2.0"; - nativeBuildInputs = [ pkg-config perl gitMinimal ]; +let + version = "0.3.4"; src = fetchFromGitHub { owner = "wireapp"; repo = "rusty-jwt-tools"; - # if you update this, please generate a new Cargo.lock file es described below at `cargoPatches` - rev = "a68ed483f7e98613c0d5c3608c684f25225a58d3"; - sha256 = "sha256-+2fjwtG80l8Vt48QWKm4wevY7MQRAwuo4YFbjB+6w9I="; + rev = "v${version}"; + sha256 = "sha256-awfpyMmDGWLViKI8Pr/BjbfnmFKo4JAcUB0+o6/prOA="; }; - doCheck = false; - cargoSha256 = "sha256-BHq28U3OzYCPNmfnxlmXsz9XYEy1kRiNrFM9OTnAkk0="; - cargoDepsHook = '' - mkdir -p rusty_jwt-tools_ffi-${version}-vendor.tar.gz/ring/.git + cargoLockFile = builtins.toFile "cargo.lock" (builtins.readFile "${src}/ffi/Cargo.lock"); + +in +rustPlatform.buildRustPackage { + name = "rusty_jwt-tools_ffi-${version}"; + inherit version src; + + cargoLock = { + lockFile = cargoLockFile; + outputHashes = { + # if any of these need updating, replace / create new key with + # lib.fakeSha256, rebuild, and replace with actual hash. + "biscuit-0.6.0-beta1" = "sha256-j8Pxi2nHgsKz6umroYjwR8sr1xLQAaWdnej5U9+L5ko="; + "jwt-simple-0.11.3" = "sha256-kVBTXYtBW9SE6F6nmH71iVc0KKxvpX/axCvMAP1cZvY="; + "ring-0.17.0-not-released-yet" = "sha256-9M4lR68r8phscSFw9Xh+CVHnOkilDI0brAdU0tW3xaA="; + }; + }; + + postPatch = '' + cp ${cargoLockFile} Cargo.lock ''; - cargoPatches = [ - # a patch file to add/update Cargo.lock in the source code - # it's good practice not to add Cargo.lock to the source code for libraries - # see: https://doc.rust-lang.org/cargo/faq.html#why-do-binaries-have-cargolock-in-version-control-but-not-libraries - # however, because it is required by nix, we need to manually generate and add it: - # - `git clone git@github.com:wireapp/rusty-jwt-tools.git` - # - checkout the commit specified in `rev` - # - create a new branch: `git checkout -b patch-cargo-lock-` (replace `` with the commit hash) - # - `cargo clean && cargo build --release` - # - `git add -f Cargo.lock` - # - `git commit -am "generate new cargo.lock"` - # - `git format-patch main` - # - copy contents of `0001-generate-new-cargo.lock.patch` into `nix/pkgs/rusty_jwt_tools/add-Cargo.lock.patch` - ./add-Cargo.lock.patch - ]; + doCheck = false; } diff --git a/nix/pkgs/zauth/default.nix b/nix/pkgs/zauth/default.nix index ef1248b73c..969a76fc33 100644 --- a/nix/pkgs/zauth/default.nix +++ b/nix/pkgs/zauth/default.nix @@ -16,7 +16,7 @@ rustPlatform.buildRustPackage rec { src = nix-gitignore.gitignoreSourcePure [ ../../../.gitignore ] ../../../libs/libzauth; sourceRoot = "libzauth/libzauth-c"; - cargoSha256 = "sha256-od+O5dhAVC1KhDUz8U2fhjyqjXkqHjeEEhvVE0N9orI="; + cargoSha256 = "sha256-f/MNUrEQaPzSUHtnZ0jARMwBswS+Sh0Swe+2D+hpHF4="; patchLibs = lib.optionalString stdenv.isDarwin '' install_name_tool -id $out/lib/libzauth.dylib $out/lib/libzauth.dylib diff --git a/nix/sources.json b/nix/sources.json index 4ab28f3ee1..80e326af49 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -5,10 +5,10 @@ "homepage": "https://github.com/NixOS/nixpkgs", "owner": "NixOS", "repo": "nixpkgs", - "rev": "8c619a1f3cedd16ea172146e30645e703d21bfc1", - "sha256": "1zgm94b79yfqfdcqcj8bxfjzcl7gp9pd7jph9jnlxx8m36cgsn3a", + "rev": "402cc3633cc60dfc50378197305c984518b30773", + "sha256": "0yvlprdkqg1kizg83j7nivlc58zk7llrbf82jqvgjimrwhsva1m9", "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/8c619a1f3cedd16ea172146e30645e703d21bfc1.tar.gz", + "url": "https://github.com/NixOS/nixpkgs/archive/402cc3633cc60dfc50378197305c984518b30773.tar.gz", "url_template": "https://github.com///archive/.tar.gz" } } diff --git a/nix/wire-server.nix b/nix/wire-server.nix index 8fc1b22edd..686e3da773 100644 --- a/nix/wire-server.nix +++ b/nix/wire-server.nix @@ -27,7 +27,7 @@ # 3.2: Version overrides: These are very similar to nix/haskell-pins.nix, but # the package set itself sometimes contains newer versions of a few packages # along with the old versions, e.g., the package set contains aeson and -# aeson_2_1_1_0. We use the latest version provided by the pacakge set, so we +# aeson_2_1_1_0. We use the latest version provided by the package set, so we # don't have to remember to update the version here, nixpkgs will take care of # giving us the latest version. # @@ -79,7 +79,7 @@ let gundeck = [ "gundeck" "gundeck-integration" "gundeck-schema" ]; proxy = [ "proxy" ]; spar = [ "spar" "spar-integration" "spar-schema" "spar-migrate-data" ]; - stern = [ "stern" ]; + stern = [ "stern" "stern-integration"]; billing-team-member-backfill = [ "billing-team-member-backfill" ]; inconsistencies = [ "inconsistencies" ]; @@ -300,18 +300,20 @@ let pkgs.gnused pkgs.parallel pkgs.ripgrep - pkgs.helm + pkgs.kubernetes-helm pkgs.helmfile pkgs.hlint (hlib.justStaticExecutables pkgs.haskellPackages.apply-refact) pkgs.jq pkgs.kubectl + pkgs.kubelogin-oidc pkgs.nixpkgs-fmt pkgs.ormolu pkgs.shellcheck pkgs.treefmt pkgs.gawk pkgs.cfssl + pkgs.awscli2 (hlib.justStaticExecutables pkgs.haskellPackages.cabal-fmt) ] ++ pkgs.lib.optionals pkgs.stdenv.isLinux [ pkgs.skopeo @@ -373,7 +375,7 @@ in pkgs.netcat pkgs.niv (pkgs.python3.withPackages - (ps: with ps; [ pyyaml ])) + (ps: with ps; [ pyyaml ipdb requests ])) pkgs.rsync pkgs.wget pkgs.yq diff --git a/package-defaults.yaml b/package-defaults.yaml deleted file mode 100644 index eec3552b4a..0000000000 --- a/package-defaults.yaml +++ /dev/null @@ -1,49 +0,0 @@ -ghc-options: -- -O2 -- -Wall -- -Wincomplete-uni-patterns -- -Wincomplete-record-updates -- -Wpartial-fields -- -fwarn-tabs -# These errors pop up from time to time but haven't caused any serious trouble yet -- -optP-Wno-nonportable-include-path - -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 -- TupleSections -- TypeApplications -- TypeFamilies -- TypeFamilyDependencies -- TypeOperators -- UndecidableInstances -- ViewPatterns diff --git a/services/brig/brig.cabal b/services/brig/brig.cabal index 3e4d9ef358..d5492dea42 100644 --- a/services/brig/brig.cabal +++ b/services/brig/brig.cabal @@ -31,6 +31,7 @@ library Brig.API.MLS.KeyPackages Brig.API.MLS.KeyPackages.Validation Brig.API.MLS.Util + Brig.API.OAuth Brig.API.Properties Brig.API.Public Brig.API.Public.Swagger @@ -91,7 +92,6 @@ library Brig.IO.Journal Brig.Locale Brig.Options - Brig.Password Brig.Phone Brig.Provider.API Brig.Provider.DB @@ -148,6 +148,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -159,6 +160,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -230,6 +232,7 @@ library , HsOpenSSL >=0.10 , HsOpenSSL-x509-system >=0.1 , html-entities >=1.1 + , http-api-data , http-client >=0.5 , http-client-openssl >=0.2 , http-media @@ -238,6 +241,7 @@ library , insert-ordered-containers , iproute >=1.5 , iso639 >=0.1 + , jose , jwt-tools , lens >=3.8 , lens-aeson >=1.0 @@ -330,6 +334,7 @@ executable brig DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -341,6 +346,7 @@ executable brig MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -389,6 +395,7 @@ executable brig-index DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -400,6 +407,7 @@ executable brig-index MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -443,11 +451,13 @@ executable brig-integration API.Metrics API.MLS API.MLS.Util + API.OAuth API.Provider API.RichInfo.Util API.Search API.Search.Util API.Settings + API.Swagger API.SystemSettings API.Team API.Team.Util @@ -486,6 +496,7 @@ executable brig-integration DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -497,6 +508,7 @@ executable brig-integration MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -555,6 +567,7 @@ executable brig-integration , http-reverse-proxy , http-types , imports + , jose , lens >=3.9 , lens-aeson , metrics-wai @@ -563,6 +576,7 @@ executable brig-integration , MonadRandom >=0.5 , mtl , network + , network-uri , optparse-applicative , pem , pipes @@ -651,6 +665,8 @@ executable brig-schema V71_AddTableVCodesThrottle V72_AddNonceTable V73_ReplaceNonceTable + V74_AddOAuthTables + V75_AddOAuthCodeChallenge V_FUTUREWORK hs-source-dirs: schema/src @@ -667,6 +683,7 @@ executable brig-schema DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -678,6 +695,7 @@ executable brig-schema MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -741,6 +759,7 @@ test-suite brig-tests DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -752,6 +771,7 @@ test-suite brig-tests MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/services/brig/brig.integration.yaml b/services/brig/brig.integration.yaml index ae6441ab88..8d8b67ba30 100644 --- a/services/brig/brig.integration.yaml +++ b/services/brig/brig.integration.yaml @@ -199,6 +199,13 @@ optSettings: # To only allow specific phone number prefixes to register uncomment and update the settings below # setAllowlistPhonePrefixes: # - "+1555555" + # needs to be kept in sync with services/nginz/integration-test/resources/oauth/ed25519_public.jwk + setOAuthJwkKeyPair: test/resources/oauth/ed25519.jwk + setOAuthAuthCodeExpirationTimeSecs: 3 # 3 secs + setOAuthAccessTokenExpirationTimeSecs: 3 # 3 secs + setOAuthEnabled: true + setOAuthRefreshTokenExpirationTimeSecs: 14515200 # 24 weeks + setOAuthMaxActiveRefreshTokens: 10 logLevel: Warn logNetStrings: false diff --git a/services/brig/default.nix b/services/brig/default.nix index c5c3f34187..7701498ff8 100644 --- a/services/brig/default.nix +++ b/services/brig/default.nix @@ -69,6 +69,7 @@ , insert-ordered-containers , iproute , iso639 +, jose , jwt-tools , lens , lens-aeson @@ -83,6 +84,7 @@ , mwc-random , network , network-conduit-tls +, network-uri , optparse-applicative , pem , pipes @@ -214,6 +216,7 @@ mkDerivation { HsOpenSSL HsOpenSSL-x509-system html-entities + http-api-data http-client http-client-openssl http-media @@ -222,6 +225,7 @@ mkDerivation { insert-ordered-containers iproute iso639 + jose jwt-tools lens lens-aeson @@ -332,6 +336,7 @@ mkDerivation { http-reverse-proxy http-types imports + jose lens lens-aeson metrics-wai @@ -340,6 +345,7 @@ mkDerivation { MonadRandom mtl network + network-uri optparse-applicative pem pipes diff --git a/services/brig/docs/swagger.md b/services/brig/docs/swagger.md index 28f214eae9..3a7136a7f6 100644 --- a/services/brig/docs/swagger.md +++ b/services/brig/docs/swagger.md @@ -1,4 +1,17 @@ -## General +## Authentication / Authorization + +The end-points in this API support differing authorization protocols: +some are unauthenticated (`/api-version`, `/login`), some require +[zauth](), and some support both [zauth]() and [oauth](). + +The end-points that require zauth are labelled so in the description +below. The end-points that support oauth as an alternative to zauth +have the required oauth scopes listed in the same description. + +Futher reading: +- https://docs.wire.com/developer/reference/oauth.html +- https://github.com/wireapp/wire-server/blob/develop/libs/wire-api/src/Wire/API/Routes/Public.hs (search for HasSwagger instances) +- `curl https://staging-nginz-https.zinfra.io/v4/api/swagger.json | jq '.security, .securityDefinitions` ### SSO Endpoints diff --git a/services/brig/schema/src/Main.hs b/services/brig/schema/src/Main.hs index e04a47faf6..f1f35ccd37 100644 --- a/services/brig/schema/src/Main.hs +++ b/services/brig/schema/src/Main.hs @@ -53,6 +53,8 @@ import qualified V70_UserEmailUnvalidated import qualified V71_AddTableVCodesThrottle import qualified V72_AddNonceTable import qualified V73_ReplaceNonceTable +import qualified V74_AddOAuthTables +import qualified V75_AddOAuthCodeChallenge main :: IO () main = do @@ -93,7 +95,9 @@ main = do V70_UserEmailUnvalidated.migration, V71_AddTableVCodesThrottle.migration, V72_AddNonceTable.migration, - V73_ReplaceNonceTable.migration + V73_ReplaceNonceTable.migration, + V74_AddOAuthTables.migration, + V75_AddOAuthCodeChallenge.migration -- When adding migrations here, don't forget to update -- 'schemaVersion' in Brig.App diff --git a/services/brig/schema/src/V74_AddOAuthTables.hs b/services/brig/schema/src/V74_AddOAuthTables.hs new file mode 100644 index 0000000000..45cdc018be --- /dev/null +++ b/services/brig/schema/src/V74_AddOAuthTables.hs @@ -0,0 +1,68 @@ +{-# 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 V74_AddOAuthTables + ( migration, + ) +where + +import Cassandra.Schema +import Imports +import Text.RawString.QQ + +migration :: Migration +migration = + Migration 74 "Add table for OAuth clients" $ do + schema' + [r| + CREATE TABLE IF NOT EXISTS oauth_client + ( id uuid PRIMARY KEY + , name text + , redirect_uri blob + , secret blob + ) + |] + schema' + [r| + CREATE TABLE IF NOT EXISTS oauth_auth_code + ( code ascii PRIMARY KEY + , client uuid + , user uuid + , scope set + , redirect_uri blob + ) WITH default_time_to_live = 300; + |] + schema' + [r| + CREATE TABLE IF NOT EXISTS oauth_refresh_token + ( id uuid PRIMARY KEY + , client uuid + , user uuid + , scope set + , created_at timestamp + ) WITH default_time_to_live = 14515200; -- 24 weeks + |] + schema' + [r| + CREATE TABLE IF NOT EXISTS oauth_user_refresh_token + ( user uuid + , token_id uuid + , PRIMARY KEY (user, token_id) + ) WITH default_time_to_live = 14515200; -- 24 weeks + |] diff --git a/services/brig/schema/src/V75_AddOAuthCodeChallenge.hs b/services/brig/schema/src/V75_AddOAuthCodeChallenge.hs new file mode 100644 index 0000000000..ebead11e8c --- /dev/null +++ b/services/brig/schema/src/V75_AddOAuthCodeChallenge.hs @@ -0,0 +1,36 @@ +{-# 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 V75_AddOAuthCodeChallenge + ( migration, + ) +where + +import Cassandra.Schema +import Imports +import Text.RawString.QQ + +migration :: Migration +migration = + Migration 75 "Add PKCE code_challenge to oauth_auth_code table" $ + schema' + [r| ALTER TABLE oauth_auth_code ADD ( + code_challenge blob + ) + |] diff --git a/services/brig/src/Brig/API/Client.hs b/services/brig/src/Brig/API/Client.hs index 3ac4f7e255..937f87a24f 100644 --- a/services/brig/src/Brig/API/Client.hs +++ b/services/brig/src/Brig/API/Client.hs @@ -42,6 +42,7 @@ module Brig.API.Client claimLocalPrekey, claimPrekeyBundle, claimMultiPrekeyBundles, + claimMultiPrekeyBundlesV3, Data.lookupClientIds, ) where @@ -82,7 +83,7 @@ import Data.Id (ClientId, ConnId, UserId) import Data.List.Split (chunksOf) import Data.Map.Strict (traverseWithKey) import qualified Data.Map.Strict as Map -import Data.Misc (PlainTextPassword (..)) +import Data.Misc (PlainTextPassword6) import Data.Qualified import qualified Data.Set as Set import Data.String.Conversions (cs) @@ -223,7 +224,7 @@ updateClient u c r = do -- 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 (AppT r) () +rmClient :: UserId -> ConnId -> ClientId -> Maybe PlainTextPassword6 -> ExceptT ClientError (AppT r) () rmClient u con clt pw = maybe (throwE ClientNotFound) fn =<< lift (wrapClient $ Data.lookupClient u clt) where @@ -288,13 +289,15 @@ claimRemotePrekeyBundle :: Qualified UserId -> ExceptT ClientError (AppT r) Prek claimRemotePrekeyBundle quser = do Federation.claimPrekeyBundle quser !>> ClientFederationError -claimMultiPrekeyBundles :: - forall r. - (Member (Concurrency 'Unsafe) r) => +claimMultiPrekeyBundlesInternal :: + Member (Concurrency 'Unsafe) r => LegalholdProtectee -> QualifiedUserClients -> - ExceptT ClientError (AppT r) QualifiedUserClientPrekeyMap -claimMultiPrekeyBundles protectee quc = do + ExceptT + ClientError + (AppT r) + ([Qualified UserClientPrekeyMap], [Remote UserClients]) +claimMultiPrekeyBundlesInternal protectee quc = do loc <- qualifyLocal () let (locals, remotes) = partitionQualifiedAndTag @@ -304,6 +307,23 @@ claimMultiPrekeyBundles protectee quc = do (Map.assocs (qualifiedUserClients quc)) ) localPrekeys <- traverse claimLocal locals + pure (localPrekeys, remotes) + where + claimLocal :: + Member (Concurrency 'Unsafe) r => + Local UserClients -> + ExceptT ClientError (AppT r) (Qualified UserClientPrekeyMap) + claimLocal luc = + tUntagged . qualifyAs luc + <$> claimLocalMultiPrekeyBundles protectee (tUnqualified luc) + +claimMultiPrekeyBundlesV3 :: + Member (Concurrency 'Unsafe) r => + LegalholdProtectee -> + QualifiedUserClients -> + ExceptT ClientError (AppT r) QualifiedUserClientPrekeyMap +claimMultiPrekeyBundlesV3 protectee quc = do + (localPrekeys, remotes) <- claimMultiPrekeyBundlesInternal protectee quc remotePrekeys <- mapExceptT wrapHttpClient $ traverseConcurrentlyWithErrors @@ -323,10 +343,38 @@ claimMultiPrekeyBundles protectee quc = do tUntagged . qualifyAs ruc <$> Federation.claimMultiPrekeyBundle (tDomain ruc) (tUnqualified ruc) - claimLocal :: Local UserClients -> ExceptT ClientError (AppT r) (Qualified UserClientPrekeyMap) - claimLocal luc = - tUntagged . qualifyAs luc - <$> claimLocalMultiPrekeyBundles protectee (tUnqualified luc) +-- Similar to claimMultiPrekeyBundles except for the following changes +-- 1) A new return type that contains both the client map and a list of +-- users that prekeys couldn't be fetched for. +-- 2) A semantic change on federation errors when gathering remote clients. +-- Remote federation errors at this step no-longer cause the entire call +-- to fail, allowing partial results to be returned. +claimMultiPrekeyBundles :: + forall r. + Member (Concurrency 'Unsafe) r => + LegalholdProtectee -> + QualifiedUserClients -> + ExceptT ClientError (AppT r) QualifiedUserClientPrekeyMapV4 +claimMultiPrekeyBundles protectee quc = do + (localPrekeys, remotes) <- claimMultiPrekeyBundlesInternal protectee quc + remotePrekeys <- mapExceptT wrapHttpClient $ lift $ traverseConcurrentlySem claimRemote remotes + let prekeys = + getQualifiedUserClientPrekeyMap $ + qualifiedUserClientPrekeyMapFromList $ + localPrekeys <> rights remotePrekeys + failed = lefts remotePrekeys >>= toQualifiedUser . fst + pure $ + QualifiedUserClientPrekeyMapV4 prekeys $ + if null failed + then Nothing + else pure failed + where + toQualifiedUser :: Remote UserClients -> [Qualified UserId] + toQualifiedUser r = fmap (\u -> Qualified u $ tDomain r) . Map.keys . userClients . qUnqualified $ tUntagged r + claimRemote :: Remote UserClients -> ExceptT FederationError HttpClientIO (Qualified UserClientPrekeyMap) + claimRemote ruc = + tUntagged . qualifyAs ruc + <$> Federation.claimMultiPrekeyBundle (tDomain ruc) (tUnqualified ruc) claimLocalMultiPrekeyBundles :: forall r. diff --git a/services/brig/src/Brig/API/Internal.hs b/services/brig/src/Brig/API/Internal.hs index f0e8eee1ef..f08c9cd651 100644 --- a/services/brig/src/Brig/API/Internal.hs +++ b/services/brig/src/Brig/API/Internal.hs @@ -28,6 +28,7 @@ import qualified Brig.API.Connection as API import Brig.API.Error import Brig.API.Handler import Brig.API.MLS.KeyPackages.Validation +import Brig.API.OAuth (internalOauthAPI) import Brig.API.Types import qualified Brig.API.User as API import qualified Brig.API.User as Api @@ -103,6 +104,7 @@ import Wire.API.User.RichInfo -- Sitemap (servant) servantSitemap :: + forall r p. ( Member BlacklistStore r, Member GalleyProvider r, Member (UserPendingActivationStore p) r @@ -116,6 +118,7 @@ servantSitemap = :<|> teamsAPI :<|> userAPI :<|> authAPI + :<|> internalOauthAPI ejpdAPI :: Member GalleyProvider r => diff --git a/services/brig/src/Brig/API/OAuth.hs b/services/brig/src/Brig/API/OAuth.hs new file mode 100644 index 0000000000..84df200afc --- /dev/null +++ b/services/brig/src/Brig/API/OAuth.hs @@ -0,0 +1,377 @@ +-- 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 Brig.API.OAuth + ( internalOauthAPI, + oauthAPI, + lookupOAuthRefreshTokens, + verifyRefreshToken, + ) +where + +import Brig.API.Error (throwStd) +import Brig.API.Handler (Handler) +import Brig.App +import qualified Brig.Options as Opt +import Cassandra hiding (Set) +import qualified Cassandra as C +import Control.Error (assertMay, failWith, failWithM) +import Control.Lens (view, (?~), (^?)) +import Control.Monad.Except +import Crypto.JWT hiding (params, uri) +import Data.ByteString.Conversion +import Data.Domain +import Data.Id +import Data.Misc +import qualified Data.Set as Set +import Data.String.Conversions (cs) +import Data.Text.Ascii +import Data.Time +import Imports hiding (exp) +import OpenSSL.Random (randBytes) +import Polysemy (Member) +import Servant hiding (Handler, Tagged) +import Wire.API.Error +import Wire.API.OAuth as OAuth +import Wire.API.Password (Password, mkSafePassword) +import qualified Wire.API.Routes.Internal.Brig.OAuth as I +import Wire.API.Routes.Named (Named (..)) +import Wire.API.Routes.Public.Brig.OAuth +import Wire.Sem.Jwk +import qualified Wire.Sem.Jwk as Jwk +import Wire.Sem.Now (Now) +import qualified Wire.Sem.Now as Now + +-------------------------------------------------------------------------------- +-- API Internal + +internalOauthAPI :: ServerT I.OAuthAPI (Handler r) +internalOauthAPI = + Named @"create-oauth-client" registerOAuthClient + +-------------------------------------------------------------------------------- +-- API Public + +oauthAPI :: (Member Now r, Member Jwk r) => ServerT OAuthAPI (Handler r) +oauthAPI = + Named @"get-oauth-client" getOAuthClient + :<|> Named @"create-oauth-auth-code" createNewOAuthAuthorizationCode + :<|> Named @"create-oauth-access-token" createAccessTokenWith + :<|> Named @"revoke-oauth-refresh-token" revokeRefreshToken + :<|> Named @"get-oauth-applications" getOAuthApplications + :<|> Named @"revoke-oauth-account-access" revokeOAuthAccountAccess + +-------------------------------------------------------------------------------- +-- Handlers + +registerOAuthClient :: RegisterOAuthClientRequest -> (Handler r) OAuthClientCredentials +registerOAuthClient (RegisterOAuthClientRequest name uri) = do + unlessM (Opt.setOAuthEnabled <$> view settings) $ throwStd $ errorToWai @'OAuthFeatureDisabled + credentials@(OAuthClientCredentials cid secret) <- OAuthClientCredentials <$> randomId <*> createSecret + safeSecret <- liftIO $ hashClientSecret secret + lift $ wrapClient $ insertOAuthClient cid name uri safeSecret + pure credentials + where + createSecret :: MonadIO m => m OAuthClientPlainTextSecret + createSecret = OAuthClientPlainTextSecret <$> rand32Bytes + + hashClientSecret :: MonadIO m => OAuthClientPlainTextSecret -> m Password + hashClientSecret = mkSafePassword . plainTextPassword8Unsafe . toText . unOAuthClientPlainTextSecret + +rand32Bytes :: MonadIO m => m AsciiBase16 +rand32Bytes = liftIO . fmap encodeBase16 $ randBytes 32 + +-------------------------------------------------------------------------------- + +getOAuthClient :: UserId -> OAuthClientId -> (Handler r) (Maybe OAuthClient) +getOAuthClient _ cid = do + unlessM (Opt.setOAuthEnabled <$> view settings) $ throwStd $ errorToWai @'OAuthFeatureDisabled + lift $ wrapClient $ lookupOauthClient cid + +createNewOAuthAuthorizationCode :: UserId -> CreateOAuthAuthorizationCodeRequest -> (Handler r) CreateOAuthCodeResponse +createNewOAuthAuthorizationCode uid code = do + runExceptT (validateAndCreateAuthorizationCode uid code) >>= \case + Right oauthCode -> + pure $ CreateOAuthCodeSuccess $ code.redirectUri & addParams [("code", toByteString' oauthCode), ("state", cs code.state)] + Left CreateNewOAuthCodeErrorFeatureDisabled -> + pure $ CreateOAuthCodeFeatureDisabled $ code.redirectUri & addParams [("error", "access_denied"), ("error_description", "OAuth is not enabled"), ("state", cs code.state)] + Left CreateNewOAuthCodeErrorClientNotFound -> + pure $ CreateOAuthCodeClientNotFound $ code.redirectUri & addParams [("error", "access_denied"), ("error_description", "The client ID was not found"), ("state", cs code.state)] + Left CreateNewOAuthCodeErrorUnsupportedResponseType -> + pure $ CreateOAuthCodeUnsupportedResponseType $ code.redirectUri & addParams [("error", "access_denied"), ("error_description", "The client ID was not found"), ("state", cs code.state)] + Left CreateNewOAuthCodeErrorRedirectUrlMissMatch -> + pure CreateOAuthCodeRedirectUrlMissMatch + +data CreateNewOAuthCodeError + = CreateNewOAuthCodeErrorFeatureDisabled + | CreateNewOAuthCodeErrorClientNotFound + | CreateNewOAuthCodeErrorUnsupportedResponseType + | CreateNewOAuthCodeErrorRedirectUrlMissMatch + +validateAndCreateAuthorizationCode :: UserId -> CreateOAuthAuthorizationCodeRequest -> ExceptT CreateNewOAuthCodeError (Handler r) OAuthAuthorizationCode +validateAndCreateAuthorizationCode uid (CreateOAuthAuthorizationCodeRequest cid scope responseType redirectUrl _state _ chal) = do + failWithM CreateNewOAuthCodeErrorFeatureDisabled (assertMay . Opt.setOAuthEnabled <$> view settings) + failWith CreateNewOAuthCodeErrorUnsupportedResponseType (assertMay $ responseType == OAuthResponseTypeCode) + client <- failWithM CreateNewOAuthCodeErrorClientNotFound $ getOAuthClient uid cid + failWith CreateNewOAuthCodeErrorRedirectUrlMissMatch (assertMay $ client.redirectUrl == redirectUrl) + lift mkAuthorizationCode + where + mkAuthorizationCode :: (Handler r) OAuthAuthorizationCode + mkAuthorizationCode = do + oauthCode <- OAuthAuthorizationCode <$> rand32Bytes + ttl <- Opt.setOAuthAuthorizationCodeExpirationTimeSecs <$> view settings + lift $ wrapClient $ insertOAuthAuthorizationCode ttl oauthCode cid uid scope redirectUrl chal + pure oauthCode + +-------------------------------------------------------------------------------- + +createAccessTokenWith :: (Member Now r, Member Jwk r) => Either OAuthAccessTokenRequest OAuthRefreshAccessTokenRequest -> (Handler r) OAuthAccessTokenResponse +createAccessTokenWith req = do + unlessM (Opt.setOAuthEnabled <$> view settings) $ throwStd $ errorToWai @'OAuthFeatureDisabled + case req of + Left reqAC -> createAccessTokenWithAuthorizationCode reqAC + Right reqRT -> createAccessTokenWithRefreshToken reqRT + +createAccessTokenWithRefreshToken :: (Member Now r, Member Jwk r) => OAuthRefreshAccessTokenRequest -> (Handler r) OAuthAccessTokenResponse +createAccessTokenWithRefreshToken req = do + unless (req.grantType == OAuthGrantTypeRefreshToken) $ throwStd $ errorToWai @'OAuthInvalidGrantType + key <- signingKey + (OAuthRefreshTokenInfo _ cid uid scope _) <- lookupVerifyAndDeleteToken key req.refreshToken + void $ getOAuthClient uid cid >>= maybe (throwStd $ errorToWai @'OAuthClientNotFound) pure + unless (cid == req.clientId) $ throwStd $ errorToWai @'OAuthInvalidClientCredentials + createAccessToken key uid cid scope + +lookupVerifyAndDeleteToken :: JWK -> OAuthRefreshToken -> (Handler r) OAuthRefreshTokenInfo +lookupVerifyAndDeleteToken key = + verifyRefreshToken key + >=> lift . wrapClient . lookupAndDeleteOAuthRefreshToken + >=> maybe (throwStd $ errorToWai @'OAuthInvalidRefreshToken) pure + +verifyRefreshToken :: JWK -> OAuthRefreshToken -> (Handler r) OAuthRefreshTokenId +verifyRefreshToken key rt = do + eClaims <- liftIO (OAuth.verify' key (unOAuthToken rt)) + case eClaims of + Left _ -> throwStd $ errorToWai @'OAuthInvalidRefreshToken + Right claims -> maybe (throwStd $ errorToWai @'OAuthInvalidRefreshToken) pure $ hcsSub claims + +createAccessTokenWithAuthorizationCode :: (Member Now r, Member Jwk r) => OAuthAccessTokenRequest -> (Handler r) OAuthAccessTokenResponse +createAccessTokenWithAuthorizationCode req = do + unless (req.grantType == OAuthGrantTypeAuthorizationCode) $ throwStd $ errorToWai @'OAuthInvalidGrantType + (cid, uid, scope, uri, mChal) <- + lift (wrapClient $ lookupAndDeleteByOAuthAuthorizationCode req.code) + >>= maybe (throwStd $ errorToWai @'OAuthAuthorizationCodeNotFound) pure + oauthClient <- getOAuthClient uid req.clientId >>= maybe (throwStd $ errorToWai @'OAuthClientNotFound) pure + + unless (uri == req.redirectUri) $ throwStd $ errorToWai @'OAuthRedirectUrlMissMatch + unless (oauthClient.redirectUrl == req.redirectUri) $ throwStd $ errorToWai @'OAuthRedirectUrlMissMatch + unless (maybe False (verifyCodeChallenge req.codeVerifier) mChal) $ throwStd $ errorToWai @'OAuthInvalidGrant + + key <- signingKey + createAccessToken key uid cid scope + +signingKey :: Member Jwk r => (Handler r) JWK +signingKey = do + fp <- view settings >>= maybe (throwStd $ errorToWai @'OAuthJwtError) pure . Opt.setOAuthJwkKeyPair + lift (liftSem $ Jwk.get fp) >>= maybe (throwStd $ errorToWai @'OAuthJwtError) pure + +createAccessToken :: (Member Now r) => JWK -> UserId -> OAuthClientId -> OAuthScopes -> (Handler r) OAuthAccessTokenResponse +createAccessToken key uid cid scope = do + exp <- fromIntegral . Opt.setOAuthAccessTokenExpirationTimeSecs <$> view settings + accessToken <- mkAccessToken + (rid, refreshToken) <- mkRefreshToken + now <- lift (liftSem Now.get) + let refreshTokenInfo = OAuthRefreshTokenInfo rid cid uid scope now + refreshTokenExpiration <- Opt.setOAuthRefreshTokenExpirationTimeSecs <$> view settings + maxActiveTokens <- Opt.setOAuthMaxActiveRefreshTokens <$> view settings + lift $ wrapClient $ insertOAuthRefreshToken maxActiveTokens refreshTokenExpiration refreshTokenInfo + pure $ OAuthAccessTokenResponse accessToken OAuthAccessTokenTypeBearer exp refreshToken + where + mkRefreshToken :: (Handler r) (OAuthRefreshTokenId, OAuthRefreshToken) + mkRefreshToken = do + rid :: OAuthRefreshTokenId <- randomId + sub <- maybe (throwStd $ errorToWai @'OAuthJwtError) pure $ idToText rid ^? stringOrUri + let claims = emptyClaimsSet & claimSub ?~ sub + (rid,) . OAuthToken <$> signRefreshToken claims + + mkAccessToken :: Member Now r => (Handler r) OAuthAccessToken + mkAccessToken = do + domain <- Opt.setFederationDomain <$> view settings + exp <- fromIntegral . Opt.setOAuthAccessTokenExpirationTimeSecs <$> view settings + claims <- mkAccessTokenClaims uid domain scope exp + OAuthToken <$> signAccessToken claims + + mkAccessTokenClaims :: Member Now r => UserId -> Domain -> OAuthScopes -> NominalDiffTime -> (Handler r) OAuthClaimsSet + mkAccessTokenClaims u domain scopes ttl = do + iat <- lift (liftSem Now.get) + uri <- maybe (throwStd $ errorToWai @'OAuthJwtError) pure $ domainText domain ^? stringOrUri + sub <- maybe (throwStd $ errorToWai @'OAuthJwtError) pure $ idToText u ^? stringOrUri + let exp = addUTCTime ttl iat + let claimSet = + emptyClaimsSet + & claimIss ?~ uri + & claimAud ?~ Audience [uri] + & claimIat ?~ NumericDate iat + & claimSub ?~ sub + & claimExp ?~ NumericDate exp + pure $ OAuthClaimsSet claimSet scopes + + signAccessToken :: OAuthClaimsSet -> (Handler r) SignedJWT + signAccessToken claims = do + jwtOrError <- liftIO $ doSignClaims + either (const $ throwStd $ errorToWai @'OAuthJwtError) pure jwtOrError + where + doSignClaims :: IO (Either JWTError SignedJWT) + doSignClaims = runJOSE $ do + algo <- bestJWSAlg key + signJWT key (newJWSHeader ((), algo)) claims + + signRefreshToken :: ClaimsSet -> (Handler r) SignedJWT + signRefreshToken claims = do + jwtOrError <- liftIO $ doSignClaims + either (const $ throwStd $ errorToWai @'OAuthJwtError) pure jwtOrError + where + doSignClaims :: IO (Either JWTError SignedJWT) + doSignClaims = runJOSE $ do + algo <- bestJWSAlg key + signClaims key (newJWSHeader ((), algo)) claims + +-------------------------------------------------------------------------------- + +revokeRefreshToken :: Member Jwk r => OAuthRevokeRefreshTokenRequest -> (Handler r) () +revokeRefreshToken req = do + key <- signingKey + info <- lookupAndVerifyToken key req.refreshToken + void $ getOAuthClient info.userId info.clientId >>= maybe (throwStd $ errorToWai @'OAuthClientNotFound) pure + lift $ wrapClient $ deleteOAuthRefreshToken info + +lookupAndVerifyToken :: JWK -> OAuthRefreshToken -> (Handler r) OAuthRefreshTokenInfo +lookupAndVerifyToken key = + verifyRefreshToken key + >=> lift . wrapClient . lookupOAuthRefreshTokenInfo + >=> maybe (throwStd $ errorToWai @'OAuthInvalidRefreshToken) pure + +-------------------------------------------------------------------------------- + +getOAuthApplications :: UserId -> (Handler r) [OAuthApplication] +getOAuthApplications uid = do + activeRefreshTokens <- lift $ wrapClient $ lookupOAuthRefreshTokens uid + nub . catMaybes <$> for activeRefreshTokens oauthApp + where + oauthApp :: OAuthRefreshTokenInfo -> (Handler r) (Maybe OAuthApplication) + oauthApp info = (OAuthApplication info.clientId . (.name)) <$$> getOAuthClient info.userId info.clientId + +-------------------------------------------------------------------------------- + +revokeOAuthAccountAccess :: UserId -> OAuthClientId -> (Handler r) () +revokeOAuthAccountAccess uid cid = do + rts <- lift $ wrapClient $ lookupOAuthRefreshTokens uid + for_ rts $ \rt -> when (rt.clientId == cid) $ lift $ wrapClient $ deleteOAuthRefreshToken rt + +-------------------------------------------------------------------------------- +-- DB + +insertOAuthClient :: (MonadClient m) => OAuthClientId -> OAuthApplicationName -> RedirectUrl -> Password -> m () +insertOAuthClient cid name uri pw = retry x5 . write q $ params LocalQuorum (cid, name, uri, pw) + where + q :: PrepQuery W (OAuthClientId, OAuthApplicationName, RedirectUrl, Password) () + q = "INSERT INTO oauth_client (id, name, redirect_uri, secret) VALUES (?, ?, ?, ?)" + +lookupOauthClient :: (MonadClient m) => OAuthClientId -> m (Maybe OAuthClient) +lookupOauthClient cid = do + mNameUrl <- retry x5 . query1 q $ params LocalQuorum (Identity cid) + pure $ mNameUrl <&> uncurry (OAuthClient cid) + where + q :: PrepQuery R (Identity OAuthClientId) (OAuthApplicationName, RedirectUrl) + q = "SELECT name, redirect_uri FROM oauth_client WHERE id = ?" + +insertOAuthAuthorizationCode :: (MonadClient m) => Word64 -> OAuthAuthorizationCode -> OAuthClientId -> UserId -> OAuthScopes -> RedirectUrl -> OAuthCodeChallenge -> m () +insertOAuthAuthorizationCode ttl code cid uid scope uri chal = do + let cqlScope = C.Set (Set.toList (unOAuthScopes scope)) + retry x5 . write q $ params LocalQuorum (code, cid, uid, cqlScope, uri, chal, fromIntegral ttl) + where + q :: PrepQuery W (OAuthAuthorizationCode, OAuthClientId, UserId, C.Set OAuthScope, RedirectUrl, OAuthCodeChallenge, Int32) () + q = fromString $ "INSERT INTO oauth_auth_code (code, client, user, scope, redirect_uri, code_challenge) VALUES (?, ?, ?, ?, ?, ?) USING TTL ?" + +lookupAndDeleteByOAuthAuthorizationCode :: (MonadClient m) => OAuthAuthorizationCode -> m (Maybe (OAuthClientId, UserId, OAuthScopes, RedirectUrl, Maybe OAuthCodeChallenge)) +lookupAndDeleteByOAuthAuthorizationCode code = lookupOAuthAuthorizationCode <* deleteOAuthAuthorizationCode + where + lookupOAuthAuthorizationCode :: (MonadClient m) => m (Maybe (OAuthClientId, UserId, OAuthScopes, RedirectUrl, Maybe OAuthCodeChallenge)) + lookupOAuthAuthorizationCode = do + mTuple <- retry x5 . query1 q $ params LocalQuorum (Identity code) + pure $ mTuple <&> \(cid, uid, C.Set scope, uri, mChal) -> (cid, uid, OAuthScopes (Set.fromList scope), uri, mChal) + where + q :: PrepQuery R (Identity OAuthAuthorizationCode) (OAuthClientId, UserId, C.Set OAuthScope, RedirectUrl, Maybe OAuthCodeChallenge) + q = "SELECT client, user, scope, redirect_uri, code_challenge FROM oauth_auth_code WHERE code = ?" + + deleteOAuthAuthorizationCode :: (MonadClient m) => m () + deleteOAuthAuthorizationCode = retry x5 . write q $ params LocalQuorum (Identity code) + where + q :: PrepQuery W (Identity OAuthAuthorizationCode) () + q = "DELETE FROM oauth_auth_code WHERE code = ?" + +insertOAuthRefreshToken :: (MonadClient m) => Word32 -> Word64 -> OAuthRefreshTokenInfo -> m () +insertOAuthRefreshToken maxActiveTokens ttl info = do + let rid = info.refreshTokenId + oldTokes <- determineOldestTokensToBeDeleted <$> lookupOAuthRefreshTokens info.userId + for_ oldTokes deleteOAuthRefreshToken + retry x5 . write qInsertId $ params LocalQuorum (info.userId, rid, fromIntegral ttl) + retry x5 . write qInsertInfo $ params LocalQuorum (rid, info.clientId, info.userId, C.Set (Set.toList (unOAuthScopes info.scopes)), info.createdAt, fromIntegral ttl) + where + qInsertInfo :: PrepQuery W (OAuthRefreshTokenId, OAuthClientId, UserId, C.Set OAuthScope, UTCTime, Int32) () + qInsertInfo = fromString $ "INSERT INTO oauth_refresh_token (id, client, user, scope, created_at) VALUES (?, ?, ?, ?, ?) USING TTL ?" + + qInsertId :: PrepQuery W (UserId, OAuthRefreshTokenId, Int32) () + qInsertId = fromString $ "INSERT INTO oauth_user_refresh_token (user, token_id) VALUES (?, ?) USING TTL ?" + + determineOldestTokensToBeDeleted :: [OAuthRefreshTokenInfo] -> [OAuthRefreshTokenInfo] + determineOldestTokensToBeDeleted tokens = + take (length sorted - fromIntegral maxActiveTokens + 1) sorted + where + sorted = sortOn createdAt tokens + +lookupOAuthRefreshTokens :: (MonadClient m) => UserId -> m [OAuthRefreshTokenInfo] +lookupOAuthRefreshTokens uid = do + ids <- runIdentity <$$> (retry x5 . query q $ params LocalQuorum (Identity uid)) + catMaybes <$> for ids lookupOAuthRefreshTokenInfo + where + q :: PrepQuery R (Identity UserId) (Identity OAuthRefreshTokenId) + q = "SELECT token_id FROM oauth_user_refresh_token WHERE user = ?" + +lookupOAuthRefreshTokenInfo :: (MonadClient m) => OAuthRefreshTokenId -> m (Maybe OAuthRefreshTokenInfo) +lookupOAuthRefreshTokenInfo rid = do + mTuple <- retry x5 . query1 q $ params LocalQuorum (Identity rid) + pure $ mTuple <&> \(cid, uid, C.Set scope, createdAt) -> OAuthRefreshTokenInfo rid cid uid (OAuthScopes (Set.fromList scope)) createdAt + where + q :: PrepQuery R (Identity OAuthRefreshTokenId) (OAuthClientId, UserId, C.Set OAuthScope, UTCTime) + q = "SELECT client, user, scope, created_at FROM oauth_refresh_token WHERE id = ?" + +deleteOAuthRefreshToken :: (MonadClient m) => OAuthRefreshTokenInfo -> m () +deleteOAuthRefreshToken info = do + let rid = info.refreshTokenId + retry x5 . write qDeleteId $ params LocalQuorum (info.userId, rid) + retry x5 . write qDeleteInfo $ params LocalQuorum (Identity rid) + where + qDeleteId :: PrepQuery W (UserId, OAuthRefreshTokenId) () + qDeleteId = "DELETE FROM oauth_user_refresh_token WHERE user = ? AND token_id = ?" + + qDeleteInfo :: PrepQuery W (Identity OAuthRefreshTokenId) () + qDeleteInfo = "DELETE FROM oauth_refresh_token WHERE id = ?" + +lookupAndDeleteOAuthRefreshToken :: (MonadClient m) => OAuthRefreshTokenId -> m (Maybe OAuthRefreshTokenInfo) +lookupAndDeleteOAuthRefreshToken rid = do + mInfo <- lookupOAuthRefreshTokenInfo rid + for_ mInfo deleteOAuthRefreshToken $> mInfo diff --git a/services/brig/src/Brig/API/Public.hs b/services/brig/src/Brig/API/Public.hs index 008ca4d5c2..f063d40daa 100644 --- a/services/brig/src/Brig/API/Public.hs +++ b/services/brig/src/Brig/API/Public.hs @@ -32,6 +32,7 @@ import qualified Brig.API.Connection as API import Brig.API.Error import Brig.API.Handler import Brig.API.MLS.KeyPackages +import Brig.API.OAuth (oauthAPI) import qualified Brig.API.Properties as API import Brig.API.Public.Swagger import Brig.API.Types @@ -72,6 +73,7 @@ import qualified Cassandra as Data import Control.Error hiding (bool) import Control.Lens (view, (.~), (?~), (^.)) import Control.Monad.Catch (throwM) +import Control.Monad.Except import Data.Aeson hiding (json) import Data.Bifunctor import qualified Data.ByteString.Lazy as Lazy @@ -81,6 +83,7 @@ import Data.Domain import Data.FileEmbed import Data.Handle (Handle, parseHandle) import Data.Id as Id +import Data.List.NonEmpty (nonEmpty) import qualified Data.Map.Strict as Map import Data.Misc (IpAddr (..)) import Data.Nonce (Nonce, randomNonce) @@ -108,6 +111,7 @@ import qualified Wire.API.Connection as Public import Wire.API.Error import qualified Wire.API.Error.Brig as E import Wire.API.Federation.API +import Wire.API.Federation.Error import qualified Wire.API.Properties as Public import qualified Wire.API.Routes.Internal.Brig as BrigInternalAPI import qualified Wire.API.Routes.Internal.Cannon as CannonInternalAPI @@ -117,6 +121,7 @@ import qualified Wire.API.Routes.Internal.Spar as SparInternalAPI import qualified Wire.API.Routes.MultiTablePaging as Public import Wire.API.Routes.Named (Named (Named)) import Wire.API.Routes.Public.Brig +import qualified Wire.API.Routes.Public.Brig.OAuth as OAuth import qualified Wire.API.Routes.Public.Cannon as CannonAPI import qualified Wire.API.Routes.Public.Cargohold as CargoholdAPI import qualified Wire.API.Routes.Public.Galley as GalleyAPI @@ -142,6 +147,7 @@ import qualified Wire.API.User.RichInfo as Public import qualified Wire.API.UserMap as Public import qualified Wire.API.Wrapped as Public import Wire.Sem.Concurrency +import Wire.Sem.Jwk (Jwk) import Wire.Sem.Now (Now) -- User API ----------------------------------------------------------- @@ -160,7 +166,7 @@ docsAPI = -- -- Dual to `internalEndpointsSwaggerDocsAPI`. versionedSwaggerDocsAPI :: Servant.Server VersionedSwaggerDocsAPI -versionedSwaggerDocsAPI (Just V4) = +versionedSwaggerDocsAPI (Just (VersionNumber V4)) = swaggerSchemaUIServer $ ( brigSwagger <> versionSwagger @@ -170,14 +176,15 @@ versionedSwaggerDocsAPI (Just V4) = <> CannonAPI.swaggerDoc <> GundeckAPI.swaggerDoc <> ProxyAPI.swaggerDoc + <> OAuth.swaggerDoc ) & S.info . S.title .~ "Wire-Server API" & S.info . S.description ?~ $(embedText =<< makeRelativeToProject "docs/swagger.md") & cleanupSwagger -versionedSwaggerDocsAPI (Just V0) = swaggerPregenUIServer $(pregenSwagger V0) -versionedSwaggerDocsAPI (Just V1) = swaggerPregenUIServer $(pregenSwagger V1) -versionedSwaggerDocsAPI (Just V2) = swaggerPregenUIServer $(pregenSwagger V2) -versionedSwaggerDocsAPI (Just V3) = swaggerPregenUIServer $(pregenSwagger V3) +versionedSwaggerDocsAPI (Just (VersionNumber V0)) = swaggerPregenUIServer $(pregenSwagger V0) +versionedSwaggerDocsAPI (Just (VersionNumber V1)) = swaggerPregenUIServer $(pregenSwagger V1) +versionedSwaggerDocsAPI (Just (VersionNumber V2)) = swaggerPregenUIServer $(pregenSwagger V2) +versionedSwaggerDocsAPI (Just (VersionNumber V3)) = swaggerPregenUIServer $(pregenSwagger V3) versionedSwaggerDocsAPI Nothing = versionedSwaggerDocsAPI (Just maxBound) -- | Serves Swagger docs for internal endpoints @@ -191,15 +198,15 @@ internalEndpointsSwaggerDocsAPI :: PortNumber -> S.Swagger -> Servant.Server (VersionedSwaggerDocsAPIBase service) -internalEndpointsSwaggerDocsAPI service examplePort swagger (Just V4) = +internalEndpointsSwaggerDocsAPI service examplePort swagger (Just (VersionNumber V4)) = swaggerSchemaUIServer $ swagger & adjustSwaggerForInternalEndpoint service examplePort & cleanupSwagger -internalEndpointsSwaggerDocsAPI _ _ _ (Just V0) = emptySwagger -internalEndpointsSwaggerDocsAPI _ _ _ (Just V1) = emptySwagger -internalEndpointsSwaggerDocsAPI _ _ _ (Just V2) = emptySwagger -internalEndpointsSwaggerDocsAPI _ _ _ (Just V3) = emptySwagger +internalEndpointsSwaggerDocsAPI _ _ _ (Just (VersionNumber V0)) = emptySwagger +internalEndpointsSwaggerDocsAPI _ _ _ (Just (VersionNumber V1)) = emptySwagger +internalEndpointsSwaggerDocsAPI _ _ _ (Just (VersionNumber V2)) = emptySwagger +internalEndpointsSwaggerDocsAPI _ _ _ (Just (VersionNumber V3)) = emptySwagger internalEndpointsSwaggerDocsAPI service examplePort swagger Nothing = internalEndpointsSwaggerDocsAPI service examplePort swagger (Just maxBound) @@ -214,7 +221,8 @@ servantSitemap :: Member Now r, Member PasswordResetStore r, Member PublicKeyBundle r, - Member (UserPendingActivationStore p) r + Member (UserPendingActivationStore p) r, + Member Jwk r ) => ServerT BrigAPI (Handler r) servantSitemap = @@ -233,6 +241,7 @@ servantSitemap = :<|> callingAPI :<|> Team.servantAPI :<|> systemSettingsAPI + :<|> oauthAPI where userAPI :: ServerT UserAPI (Handler r) userAPI = @@ -243,6 +252,7 @@ servantSitemap = :<|> Named @"get-user-by-handle-qualified" (callsFed (exposeAnnotations Handle.getHandleInfo)) :<|> Named @"list-users-by-unqualified-ids-or-handles" (callsFed (exposeAnnotations listUsersByUnqualifiedIdsOrHandles)) :<|> Named @"list-users-by-ids-or-handles" (callsFed (exposeAnnotations listUsersByIdsOrHandles)) + :<|> Named @"list-users-by-ids-or-handles@V3" (callsFed (exposeAnnotations listUsersByIdsOrHandlesV3)) :<|> Named @"send-verification-code" sendVerificationCode :<|> Named @"get-rich-info" getRichInfo @@ -288,6 +298,7 @@ servantSitemap = :<|> Named @"get-users-prekey-bundle-unqualified" (callsFed (exposeAnnotations getPrekeyBundleUnqualifiedH)) :<|> Named @"get-users-prekey-bundle-qualified" (callsFed (exposeAnnotations getPrekeyBundleH)) :<|> Named @"get-multi-user-prekey-bundle-unqualified" getMultiUserPrekeyBundleUnqualifiedH + :<|> Named @"get-multi-user-prekey-bundle-qualified@v3" (callsFed (exposeAnnotations getMultiUserPrekeyBundleHV3)) :<|> Named @"get-multi-user-prekey-bundle-qualified" (callsFed (exposeAnnotations getMultiUserPrekeyBundleH)) userClientAPI :: ServerT UserClientAPI (Handler r) @@ -466,12 +477,11 @@ getMultiUserPrekeyBundleUnqualifiedH zusr userClients = do throwStd (errorToWai @'E.TooManyClients) API.claimLocalMultiPrekeyBundles (ProtectedUser zusr) userClients !>> clientError -getMultiUserPrekeyBundleH :: - (Member (Concurrency 'Unsafe) r) => - UserId -> +getMultiUserPrekeyBundleHInternal :: + (MonadReader Env m, MonadError Brig.API.Error.Error m) => Public.QualifiedUserClients -> - (Handler r) Public.QualifiedUserClientPrekeyMap -getMultiUserPrekeyBundleH zusr qualUserClients = do + m () +getMultiUserPrekeyBundleHInternal qualUserClients = do maxSize <- fromIntegral . setMaxConvSize <$> view settings let Sum (size :: Int) = Map.foldMapWithKey @@ -479,6 +489,23 @@ getMultiUserPrekeyBundleH zusr qualUserClients = do (Public.qualifiedUserClients qualUserClients) when (size > maxSize) $ throwStd (errorToWai @'E.TooManyClients) + +getMultiUserPrekeyBundleHV3 :: + Member (Concurrency 'Unsafe) r => + UserId -> + Public.QualifiedUserClients -> + (Handler r) Public.QualifiedUserClientPrekeyMap +getMultiUserPrekeyBundleHV3 zusr qualUserClients = do + getMultiUserPrekeyBundleHInternal qualUserClients + API.claimMultiPrekeyBundlesV3 (ProtectedUser zusr) qualUserClients !>> clientError + +getMultiUserPrekeyBundleH :: + Member (Concurrency 'Unsafe) r => + UserId -> + Public.QualifiedUserClients -> + (Handler r) Public.QualifiedUserClientPrekeyMapV4 +getMultiUserPrekeyBundleH zusr qualUserClients = do + getMultiUserPrekeyBundleHInternal qualUserClients API.claimMultiPrekeyBundles (ProtectedUser zusr) qualUserClients !>> clientError addClient :: @@ -699,7 +726,7 @@ listUsersByUnqualifiedIdsOrHandles :: listUsersByUnqualifiedIdsOrHandles self mUids mHandles = do domain <- viewFederationDomain case (mUids, mHandles) of - (Just uids, _) -> listUsersByIdsOrHandles self (Public.ListUsersByIds ((`Qualified` domain) <$> fromCommaSeparatedList uids)) + (Just uids, _) -> listUsersByIdsOrHandlesV3 self (Public.ListUsersByIds ((`Qualified` domain) <$> fromCommaSeparatedList uids)) (_, Just handles) -> let normalRangedList = fromCommaSeparatedList $ fromRange handles qualifiedList = (`Qualified` domain) <$> normalRangedList @@ -708,10 +735,21 @@ listUsersByUnqualifiedIdsOrHandles self mUids mHandles = do -- annotation here otherwise a change in 'Public.ListUsersByHandles' -- could cause this code to break. qualifiedRangedList :: Range 1 4 [Qualified Handle] = unsafeRange qualifiedList - in listUsersByIdsOrHandles self (Public.ListUsersByHandles qualifiedRangedList) + in listUsersByIdsOrHandlesV3 self (Public.ListUsersByHandles qualifiedRangedList) (Nothing, Nothing) -> throwStd $ badRequest "at least one ids or handles must be provided" -listUsersByIdsOrHandles :: +listUsersByIdsOrHandlesGetIds :: [Handle] -> (Handler r) [Qualified UserId] +listUsersByIdsOrHandlesGetIds localHandles = do + localUsers <- catMaybes <$> traverse (lift . wrapClient . API.lookupHandle) localHandles + domain <- viewFederationDomain + pure $ map (`Qualified` domain) localUsers + +listUsersByIdsOrHandlesGetUsers :: Local x -> Range n m [Qualified Handle] -> Handler r [Qualified UserId] +listUsersByIdsOrHandlesGetUsers lself hs = do + let (localHandles, _) = partitionQualified lself (fromRange hs) + listUsersByIdsOrHandlesGetIds localHandles + +listUsersByIdsOrHandlesV3 :: forall r. ( Member GalleyProvider r, Member (Concurrency 'Unsafe) r @@ -719,27 +757,49 @@ listUsersByIdsOrHandles :: UserId -> Public.ListUsersQuery -> (Handler r) [Public.UserProfile] -listUsersByIdsOrHandles self q = do +listUsersByIdsOrHandlesV3 self q = do lself <- qualifyLocal self foundUsers <- case q of Public.ListUsersByIds us -> byIds lself us Public.ListUsersByHandles hs -> do - let (localHandles, _) = partitionQualified lself (fromRange hs) - us <- getIds localHandles + us <- listUsersByIdsOrHandlesGetUsers lself hs Handle.filterHandleResults lself =<< byIds lself us case foundUsers of [] -> throwStd $ notFound "None of the specified ids or handles match any users" _ -> pure foundUsers where - getIds :: [Handle] -> (Handler r) [Qualified UserId] - getIds localHandles = do - localUsers <- catMaybes <$> traverse (lift . wrapClient . API.lookupHandle) localHandles - domain <- viewFederationDomain - pure $ map (`Qualified` domain) localUsers byIds :: Local UserId -> [Qualified UserId] -> (Handler r) [Public.UserProfile] byIds lself uids = API.lookupProfiles lself uids !>> fedError +-- Similar to listUsersByIdsOrHandlesV3, except that it allows partial successes +-- using a new return type +listUsersByIdsOrHandles :: + forall r. + ( Member GalleyProvider r, + Member (Concurrency 'Unsafe) r + ) => + UserId -> + Public.ListUsersQuery -> + Handler r ListUsersById +listUsersByIdsOrHandles self q = do + lself <- qualifyLocal self + (errors, foundUsers) <- case q of + Public.ListUsersByIds us -> + byIds lself us + Public.ListUsersByHandles hs -> do + us <- listUsersByIdsOrHandlesGetUsers lself hs + (l, r) <- byIds lself us + r' <- Handle.filterHandleResults lself r + pure (l, r') + pure $ ListUsersById foundUsers $ fst <$$> nonEmpty errors + where + byIds :: + Local UserId -> + [Qualified UserId] -> + Handler r ([(Qualified UserId, FederationError)], [Public.UserProfile]) + byIds lself uids = lift (API.lookupProfilesV3 lself uids) !>> fedError + newtype GetActivationCodeResp = GetActivationCodeResp (Public.ActivationKey, Public.ActivationCode) diff --git a/services/brig/src/Brig/API/Public/Swagger.hs b/services/brig/src/Brig/API/Public/Swagger.hs index 0e75164817..bb7b179aee 100644 --- a/services/brig/src/Brig/API/Public/Swagger.hs +++ b/services/brig/src/Brig/API/Public/Swagger.hs @@ -36,11 +36,11 @@ import Wire.API.Routes.Version type SwaggerDocsAPIBase = SwaggerSchemaUI "swagger-ui" "swagger.json" -type VersionedSwaggerDocsAPI = "api" :> Header VersionHeader Version :> SwaggerDocsAPIBase +type VersionedSwaggerDocsAPI = "api" :> Header VersionHeader VersionNumber :> SwaggerDocsAPIBase type ServiceSwaggerDocsAPIBase service = SwaggerSchemaUI service (AppendSymbol service "-swagger.json") -type VersionedSwaggerDocsAPIBase service = Header VersionHeader Version :> ServiceSwaggerDocsAPIBase service +type VersionedSwaggerDocsAPIBase service = Header VersionHeader VersionNumber :> ServiceSwaggerDocsAPIBase service type InternalEndpointsSwaggerDocsAPI = "api-internal" @@ -60,7 +60,7 @@ pregenSwagger :: Version -> Q Exp pregenSwagger v = embedLazyByteString =<< makeRelativeToProject - ("docs/swagger-v" <> T.unpack (toUrlPiece v) <> ".json") + ("docs/swagger-v" <> T.unpack (toUrlPiece (VersionNumber v)) <> ".json") swaggerPregenUIServer :: LByteString -> Server SwaggerDocsAPIBase swaggerPregenUIServer = diff --git a/services/brig/src/Brig/API/Types.hs b/services/brig/src/Brig/API/Types.hs index 8a7814718b..142831aef2 100644 --- a/services/brig/src/Brig/API/Types.hs +++ b/services/brig/src/Brig/API/Types.hs @@ -27,6 +27,7 @@ module Brig.API.Types ReAuthError (..), LegalHoldLoginError (..), RetryAfter (..), + ListUsersById (..), foldKey, ) where diff --git a/services/brig/src/Brig/API/User.hs b/services/brig/src/Brig/API/User.hs index d8944a4ea4..405e9a7f29 100644 --- a/services/brig/src/Brig/API/User.hs +++ b/services/brig/src/Brig/API/User.hs @@ -40,6 +40,7 @@ module Brig.API.User lookupAccountsByIdentity, lookupProfile, lookupProfiles, + lookupProfilesV3, lookupLocalProfiles, getLegalHoldStatus, Data.lookupName, @@ -124,7 +125,6 @@ import qualified Brig.Federation.Client as Federation import qualified Brig.IO.Intra as Intra import qualified Brig.InternalEvent.Types as Internal import Brig.Options hiding (Timeout, internalEvents) -import Brig.Password import qualified Brig.Queue as Queue import qualified Brig.Team.DB as Team import Brig.Team.Types (ShowOrHideInvitationUrl (..)) @@ -156,7 +156,7 @@ import Data.List.Extra import Data.List1 as List1 (List1, singleton) import qualified Data.Map.Strict as Map import qualified Data.Metrics as Metrics -import Data.Misc (PlainTextPassword (..)) +import Data.Misc import Data.Qualified import Data.Time.Clock (addUTCTime, diffUTCTime) import Data.UUID.V4 (nextRandom) @@ -172,6 +172,7 @@ import Wire.API.Connection import Wire.API.Error import qualified Wire.API.Error.Brig as E import Wire.API.Federation.Error +import Wire.API.Password import Wire.API.Routes.Internal.Brig.Connection import qualified Wire.API.Routes.Internal.Galley.TeamsIntra as Team import Wire.API.Team hiding (newTeam) @@ -1105,7 +1106,7 @@ completePasswordReset :: ) => PasswordResetIdentity -> PasswordResetCode -> - PlainTextPassword -> + PlainTextPassword8 -> ExceptT PasswordResetError (AppT r) () completePasswordReset ident code pw = do key <- mkPasswordResetKey ident @@ -1122,7 +1123,7 @@ completePasswordReset ident code pw = do -- | Pull the current password of a user and compare it against the one about to be installed. -- If the two are the same, throw an error. If no current password can be found, do nothing. -checkNewIsDifferent :: UserId -> PlainTextPassword -> ExceptT PasswordResetError (AppT r) () +checkNewIsDifferent :: UserId -> PlainTextPassword' t -> ExceptT PasswordResetError (AppT r) () checkNewIsDifferent uid pw = do mcurrpw <- lift . wrapClient $ Data.lookupPassword uid case mcurrpw of @@ -1159,7 +1160,7 @@ deleteSelfUser :: forall r. (Member GalleyProvider r) => UserId -> - Maybe PlainTextPassword -> + Maybe PlainTextPassword6 -> ExceptT DeleteUserError (AppT r) (Maybe Timeout) deleteSelfUser uid pwd = do account <- lift . wrapClient $ Data.lookupAccount uid @@ -1437,6 +1438,28 @@ lookupProfiles self others = (lookupProfilesFromDomain self) (bucketQualified others) +-- | Similar to lookupProfiles except it returns all results and all errors +-- allowing for partial success. +lookupProfilesV3 :: + ( Member GalleyProvider r, + Member (Concurrency 'Unsafe) r + ) => + -- | User 'self' on whose behalf the profiles are requested. + Local UserId -> + -- | The users ('others') for which to obtain the profiles. + [Qualified UserId] -> + AppT r ([(Qualified UserId, FederationError)], [UserProfile]) +lookupProfilesV3 self others = do + t <- + traverseConcurrentlyAppT + (lookupProfilesFromDomain self) + (bucketQualified others) + let (l, r) = partitionEithers t + pure (l >>= flattenUsers, join r) + where + flattenUsers :: (Qualified [UserId], FederationError) -> [(Qualified UserId, FederationError)] + flattenUsers (l, e) = (,e) <$> sequenceA l + lookupProfilesFromDomain :: (Member GalleyProvider r) => Local UserId -> diff --git a/services/brig/src/Brig/API/Util.hs b/services/brig/src/Brig/API/Util.hs index b94ae259d4..46f286d6a1 100644 --- a/services/brig/src/Brig/API/Util.hs +++ b/services/brig/src/Brig/API/Util.hs @@ -22,6 +22,8 @@ module Brig.API.Util logInvitationCode, validateHandle, logEmail, + traverseConcurrentlyAppT, + traverseConcurrentlySem, traverseConcurrentlyWithErrors, traverseConcurrentlyWithErrorsSem, traverseConcurrentlyWithErrorsAppT, @@ -44,6 +46,7 @@ import Brig.Types.Intra (accountUser) import Control.Lens (view) import Control.Monad.Catch (throwM) import Control.Monad.Trans.Except +import Data.Bifunctor import Data.Domain (Domain) import Data.Handle (Handle, parseHandle) import Data.Id @@ -96,6 +99,21 @@ logEmail email = logInvitationCode :: InvitationCode -> (Msg -> Msg) logInvitationCode code = Log.field "invitation_code" (toText $ fromInvitationCode code) +-- | Traverse concurrently and collect errors. +traverseConcurrentlyAppT :: + (Traversable t, Member (C.Concurrency 'C.Unsafe) r) => + (a -> ExceptT e (AppT r) b) -> + t a -> + AppT r [Either (a, e) b] +traverseConcurrentlyAppT f t = do + env <- temporaryGetEnv + AppT $ + lift $ + C.unsafePooledMapConcurrentlyN + 8 + (\a -> first (a,) <$> lowerAppT env (runExceptT $ f a)) + t + -- | Traverse concurrently and fail on first error. traverseConcurrentlyWithErrors :: (Traversable t, Exception e, MonadUnliftIO m) => @@ -109,6 +127,14 @@ traverseConcurrentlyWithErrors f = <=< pooledMapConcurrentlyN 8 (runExceptT . f) ) +traverseConcurrentlySem :: + (Traversable t, MonadUnliftIO m) => + (a -> ExceptT e m b) -> + t a -> + m (t (Either (a, e) b)) +traverseConcurrentlySem f = + pooledMapConcurrentlyN 8 $ \a -> first (a,) <$> runExceptT (f a) + -- | Traverse concurrently and fail on first error. traverseConcurrentlyWithErrorsSem :: forall t e a r b. diff --git a/services/brig/src/Brig/App.hs b/services/brig/src/Brig/App.hs index 50742a9faf..cb24915bd4 100644 --- a/services/brig/src/Brig/App.hs +++ b/services/brig/src/Brig/App.hs @@ -146,10 +146,11 @@ import System.Logger.Class hiding (Settings, settings) import qualified System.Logger.Class as LC import qualified System.Logger.Extended as Log import Util.Options -import Wire.API.User +import Wire.API.User.Identity (Email) +import Wire.API.User.Profile (Locale) schemaVersion :: Int32 -schemaVersion = 73 +schemaVersion = 75 ------------------------------------------------------------------------------- -- Environment diff --git a/services/brig/src/Brig/CanonicalInterpreter.hs b/services/brig/src/Brig/CanonicalInterpreter.hs index d6076ce31a..d380b6d229 100644 --- a/services/brig/src/Brig/CanonicalInterpreter.hs +++ b/services/brig/src/Brig/CanonicalInterpreter.hs @@ -29,13 +29,15 @@ import Polysemy.Error (Error, mapError, runError) import Polysemy.TinyLog (TinyLog) import Wire.Sem.Concurrency import Wire.Sem.Concurrency.IO +import Wire.Sem.Jwk import Wire.Sem.Logger.TinyLog (loggerToTinyLog) import Wire.Sem.Now (Now) import Wire.Sem.Now.IO (nowToIOAction) import Wire.Sem.Paging.Cassandra (InternalPaging) type BrigCanonicalEffects = - '[ PublicKeyBundle, + '[ Jwk, + PublicKeyBundle, JwtTools, BlacklistPhonePrefixStore, BlacklistStore, @@ -76,6 +78,7 @@ runBrigToIO e (AppT ma) = do . interpretBlacklistPhonePrefixStoreToCassandra @Cas.Client . interpretJwtTools . interpretPublicKeyBundle + . interpretJwk ) ) $ runReaderT ma e diff --git a/services/brig/src/Brig/Data/User.hs b/services/brig/src/Brig/Data/User.hs index 947eb5ee23..7f4b0ee846 100644 --- a/services/brig/src/Brig/Data/User.hs +++ b/services/brig/src/Brig/Data/User.hs @@ -77,7 +77,6 @@ where import Brig.App (Env, currentTime, settings, viewFederationDomain, zauthEnv) import Brig.Data.Instances () import Brig.Options -import Brig.Password import Brig.Types.Intra import Brig.Types.User (HavePendingInvitations (NoPendingInvitations, WithPendingInvitations)) import qualified Brig.ZAuth as ZAuth @@ -89,12 +88,13 @@ import Data.Domain import Data.Handle (Handle) import Data.Id import Data.Json.Util (UTCTimeMillis, toUTCTimeMillis) -import Data.Misc (PlainTextPassword (..)) +import Data.Misc import Data.Qualified import Data.Range (fromRange) import Data.Time (addUTCTime) import Data.UUID.V4 import Imports +import Wire.API.Password import Wire.API.Provider.Service import qualified Wire.API.Team.Feature as ApiFt import Wire.API.User @@ -185,7 +185,7 @@ newAccountInviteViaScim uid tid locale name email = do ManagedByScim -- | Mandatory password authentication. -authenticate :: MonadClient m => UserId -> PlainTextPassword -> ExceptT AuthError m () +authenticate :: MonadClient m => UserId -> PlainTextPassword6 -> ExceptT AuthError m () authenticate u pw = lift (lookupAuth u) >>= \case Nothing -> throwE AuthInvalidUser @@ -201,7 +201,7 @@ authenticate u pw = -- | Password reauthentication. If the account has a password, reauthentication -- is mandatory. If the account has no password, or is an SSO user, and no password is given, -- reauthentication is a no-op. -reauthenticate :: (MonadClient m, MonadReader Env m) => UserId -> Maybe PlainTextPassword -> ExceptT ReAuthError m () +reauthenticate :: (MonadClient m, MonadReader Env m) => UserId -> Maybe PlainTextPassword6 -> ExceptT ReAuthError m () reauthenticate u pw = lift (lookupAuth u) >>= \case Nothing -> throwE (ReAuthError AuthInvalidUser) @@ -313,7 +313,7 @@ updateManagedBy u h = retry x5 $ write userManagedByUpdate (params LocalQuorum ( updateHandle :: MonadClient m => UserId -> Handle -> m () updateHandle u h = retry x5 $ write userHandleUpdate (params LocalQuorum (h, u)) -updatePassword :: MonadClient m => UserId -> PlainTextPassword -> m () +updatePassword :: MonadClient m => UserId -> PlainTextPassword8 -> m () updatePassword u t = do p <- liftIO $ mkSafePassword t retry x5 $ write userPasswordUpdate (params LocalQuorum (p, u)) diff --git a/services/brig/src/Brig/Effects/GalleyProvider/RPC.hs b/services/brig/src/Brig/Effects/GalleyProvider/RPC.hs index 65ee53fead..f028516bf3 100644 --- a/services/brig/src/Brig/Effects/GalleyProvider/RPC.hs +++ b/services/brig/src/Brig/Effects/GalleyProvider/RPC.hs @@ -99,7 +99,7 @@ createSelfConv u = do void $ ServiceRPC.request @'Galley POST req where req = - paths ["v" <> toHeader (maxBound :: Version), "conversations", "self"] + paths [toHeader (maxBound :: Version), "conversations", "self"] . zUser u . expect2xx @@ -125,7 +125,7 @@ getConv usr lcnv = do where req = paths - [ "v" <> toHeader (maxBound :: Version), + [ toHeader (maxBound :: Version), "conversations", toByteString' (tDomain lcnv), toByteString' (tUnqualified lcnv) @@ -155,7 +155,7 @@ getTeamConv usr tid cnv = do where req = paths - [ "v" <> toHeader (maxBound :: Version), + [ toHeader (maxBound :: Version), "teams", toByteString' tid, "conversations", diff --git a/services/brig/src/Brig/Options.hs b/services/brig/src/Brig/Options.hs index 71b3c09dfe..66904d3ce0 100644 --- a/services/brig/src/Brig/Options.hs +++ b/services/brig/src/Brig/Options.hs @@ -556,7 +556,7 @@ data Settings = Settings -- It should also match the SRV DNS records under which other wire-server installations can find this backend: -- _wire-server-federator._tcp. -- Once set, DO NOT change it: if you do, existing users may have a broken experience and/or stop working - -- Remember to keep it the same in Galley. + -- Remember to keep it the same in all services. -- Example: -- allowedDomains: -- - wire.com @@ -609,7 +609,24 @@ data Settings = Settings setDpopTokenExpirationTimeSecsInternal :: !(Maybe Word64), -- | Path to a .pem file containing the server's public key and private key -- e.g. to sign JWT tokens - setPublicKeyBundle :: !(Maybe FilePath) + setPublicKeyBundle :: !(Maybe FilePath), + -- | Path to the public and private JSON web key pair used to sign OAuth access tokens + setOAuthJwkKeyPair :: !(Maybe FilePath), + -- | The expiration time of an OAuth access token in seconds. + -- use `setOAuthAccessTokenExpirationTimeSecs` as the getter function which always provides a default value + setOAuthAccessTokenExpirationTimeSecsInternal :: !(Maybe Word64), + -- | The expiration time of an OAuth authorization code in seconds. + -- use `setOAuthAuthorizationCodeExpirationTimeSecs` as the getter function which always provides a default value + setOAuthAuthorizationCodeExpirationTimeSecsInternal :: !(Maybe Word64), + -- | En-/Disable OAuth + -- use `setOAuthEnabled` as the getter function which always provides a default value + setOAuthEnabledInternal :: !(Maybe Bool), + -- | The expiration time of an OAuth refresh token in seconds. + -- use `setOAuthRefreshTokenExpirationTimeSecs` as the getter function which always provides a default value + setOAuthRefreshTokenExpirationTimeSecsInternal :: !(Maybe Word64), + -- | The maximum number of active OAuth refresh tokens a user is allowed to have. + -- use `setOAuthMaxActiveRefreshTokens` as the getter function which always provides a default value + setOAuthMaxActiveRefreshTokensInternal :: !(Maybe Word32) } deriving (Show, Generic) @@ -655,11 +672,41 @@ defaultDpopTokenExpirationTimeSecs = 30 setDpopTokenExpirationTimeSecs :: Settings -> Word64 setDpopTokenExpirationTimeSecs = fromMaybe defaultDpopTokenExpirationTimeSecs . setDpopTokenExpirationTimeSecsInternal +defaultOAuthAccessTokenExpirationTimeSecs :: Word64 +defaultOAuthAccessTokenExpirationTimeSecs = 60 * 60 * 24 * 7 * 3 -- 3 weeks + +setOAuthAccessTokenExpirationTimeSecs :: Settings -> Word64 +setOAuthAccessTokenExpirationTimeSecs = fromMaybe defaultOAuthAccessTokenExpirationTimeSecs . setOAuthAccessTokenExpirationTimeSecsInternal + +defaultOAuthAuthorizationCodeExpirationTimeSecs :: Word64 +defaultOAuthAuthorizationCodeExpirationTimeSecs = 300 -- 5 minutes + +setOAuthAuthorizationCodeExpirationTimeSecs :: Settings -> Word64 +setOAuthAuthorizationCodeExpirationTimeSecs = fromMaybe defaultOAuthAuthorizationCodeExpirationTimeSecs . setOAuthAuthorizationCodeExpirationTimeSecsInternal + +defaultOAuthEnabled :: Bool +defaultOAuthEnabled = False + +setOAuthEnabled :: Settings -> Bool +setOAuthEnabled = fromMaybe defaultOAuthEnabled . setOAuthEnabledInternal + +defaultOAuthRefreshTokenExpirationTimeSecs :: Word64 +defaultOAuthRefreshTokenExpirationTimeSecs = 60 * 60 * 24 * 7 * 4 * 6 -- 24 weeks + +setOAuthRefreshTokenExpirationTimeSecs :: Settings -> Word64 +setOAuthRefreshTokenExpirationTimeSecs = fromMaybe defaultOAuthRefreshTokenExpirationTimeSecs . setOAuthRefreshTokenExpirationTimeSecsInternal + +defaultOAuthMaxActiveRefreshTokens :: Word32 +defaultOAuthMaxActiveRefreshTokens = 10 + +setOAuthMaxActiveRefreshTokens :: Settings -> Word32 +setOAuthMaxActiveRefreshTokens = fromMaybe defaultOAuthMaxActiveRefreshTokens . setOAuthMaxActiveRefreshTokensInternal + -- | The analog to `GT.FeatureFlags`. This type tracks only the things that we need to -- express our current cloud business logic. -- -- FUTUREWORK: it would be nice to have a system of feature configs that allows to coherently --- express arbitrary logic accross personal and team accounts, teams, and instances; including +-- express arbitrary logic across personal and team accounts, teams, and instances; including -- default values for new records, default for records that have a NULL value (eg., because -- they are grandfathered), and feature-specific extra data (eg., TLL for self-deleting -- messages). For now, we have something quick & simple. @@ -838,6 +885,11 @@ instance FromJSON Settings where "setNonceTtlSecsInternal" -> "setNonceTtlSecs" "setDpopMaxSkewSecsInternal" -> "setDpopMaxSkewSecs" "setDpopTokenExpirationTimeSecsInternal" -> "setDpopTokenExpirationTimeSecs" + "setOAuthAuthorizationCodeExpirationTimeSecsInternal" -> "setOAuthAuthorizationCodeExpirationTimeSecs" + "setOAuthAccessTokenExpirationTimeSecsInternal" -> "setOAuthAccessTokenExpirationTimeSecs" + "setOAuthEnabledInternal" -> "setOAuthEnabled" + "setOAuthRefreshTokenExpirationTimeSecsInternal" -> "setOAuthRefreshTokenExpirationTimeSecs" + "setOAuthMaxActiveRefreshTokensInternal" -> "setOAuthMaxActiveRefreshTokens" other -> other } @@ -866,7 +918,12 @@ Lens.makeLensesFor ("setEnableDevelopmentVersions", "enableDevelopmentVersions"), ("setRestrictUserCreation", "restrictUserCreation"), ("setEnableMLS", "enableMLS"), - ("setDisabledAPIVersions", "disabledAPIVersions") + ("setOAuthEnabledInternal", "oauthEnabledInternal"), + ("setOAuthAuthorizationCodeExpirationTimeSecsInternal", "oauthAuthorizationCodeExpirationTimeSecsInternal"), + ("setOAuthAccessTokenExpirationTimeSecsInternal", "oauthAccessTokenExpirationTimeSecsInternal"), + ("setDisabledAPIVersions", "disabledAPIVersions"), + ("setOAuthRefreshTokenExpirationTimeSecsInternal", "oauthRefreshTokenExpirationTimeSecsInternal"), + ("setOAuthMaxActiveRefreshTokensInternal", "oauthMaxActiveRefreshTokensInternal") ] ''Settings diff --git a/services/brig/src/Brig/Provider/API.hs b/services/brig/src/Brig/Provider/API.hs index 23872d077f..3fa18bc303 100644 --- a/services/brig/src/Brig/Provider/API.hs +++ b/services/brig/src/Brig/Provider/API.hs @@ -42,7 +42,6 @@ import Brig.Email (mkEmailKey) import qualified Brig.InternalEvent.Types as Internal import Brig.Options (Settings (..)) import qualified Brig.Options as Opt -import Brig.Password import Brig.Provider.DB (ServiceConn (..)) import qualified Brig.Provider.DB as DB import Brig.Provider.Email @@ -105,6 +104,7 @@ import Wire.API.Conversation.Role import Wire.API.Error import qualified Wire.API.Error.Brig as E import qualified Wire.API.Event.Conversation as Public (Event) +import Wire.API.Password import Wire.API.Provider import qualified Wire.API.Provider as Public import qualified Wire.API.Provider.Bot as Ext diff --git a/services/brig/src/Brig/Provider/DB.hs b/services/brig/src/Brig/Provider/DB.hs index 0c08f483c0..a7d1f50a04 100644 --- a/services/brig/src/Brig/Provider/DB.hs +++ b/services/brig/src/Brig/Provider/DB.hs @@ -19,9 +19,6 @@ module Brig.Provider.DB where import Brig.Data.Instances () import Brig.Email (EmailKey, emailKeyOrig, emailKeyUniq) -import Brig.Password --- import Brig.Provider.DB.Instances () - import Brig.Types.Instances () import Brig.Types.Provider.Tag import Cassandra as C @@ -34,6 +31,7 @@ import qualified Data.Set as Set import qualified Data.Text as Text import Imports import UnliftIO (mapConcurrently) +import Wire.API.Password import Wire.API.Provider import Wire.API.Provider.Service hiding (updateServiceTags) import Wire.API.Provider.Service.Tag @@ -131,7 +129,7 @@ deleteAccount pid = retry x5 $ write cql $ params LocalQuorum (Identity pid) updateAccountPassword :: MonadClient m => ProviderId -> - PlainTextPassword -> + PlainTextPassword6 -> m () updateAccountPassword pid pwd = do p <- liftIO $ mkSafePassword pwd diff --git a/services/brig/src/Brig/User/Auth.hs b/services/brig/src/Brig/User/Auth.hs index 8aa2d04302..125e844446 100644 --- a/services/brig/src/Brig/User/Auth.hs +++ b/services/brig/src/Brig/User/Auth.hs @@ -71,7 +71,7 @@ import Data.Id import qualified Data.List.NonEmpty as NE import Data.List1 (List1) import qualified Data.List1 as List1 -import Data.Misc (PlainTextPassword (..)) +import Data.Misc (PlainTextPassword6) import qualified Data.ZAuth.Token as ZAuth import Imports import Network.Wai.Utilities.Error ((!>>)) @@ -269,7 +269,7 @@ renewAccess uts at mcid = do revokeAccess :: (MonadClient m, Log.MonadLogger m, MonadReader Env m) => UserId -> - PlainTextPassword -> + PlainTextPassword6 -> [CookieId] -> [CookieLabel] -> ExceptT AuthError m () @@ -466,8 +466,8 @@ legalHoldLogin :: LegalHoldLogin -> CookieType -> ExceptT LegalHoldLoginError (AppT r) (Access ZAuth.LegalHoldUser) -legalHoldLogin (LegalHoldLogin uid plainTextPassword label) typ = do - wrapHttpClientE (Data.reauthenticate uid plainTextPassword) !>> LegalHoldReAuthError +legalHoldLogin (LegalHoldLogin uid pw label) typ = do + wrapHttpClientE (Data.reauthenticate uid pw) !>> 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/Version.hs b/services/brig/src/Brig/Version.hs index acf7603d76..f3d6a5ca89 100644 --- a/services/brig/src/Brig/Version.hs +++ b/services/brig/src/Brig/Version.hs @@ -40,8 +40,8 @@ versionAPI = Named $ do | otherwise = Set.difference allVersions devVersions pure $ VersionInfo - { vinfoSupported = toList supported, - vinfoDevelopment = toList devVersions, + { vinfoSupported = VersionNumber <$> toList supported, + vinfoDevelopment = VersionNumber <$> toList devVersions, vinfoFederation = isJust fed, vinfoDomain = dom } diff --git a/services/brig/test/integration/API/Metrics.hs b/services/brig/test/integration/API/Metrics.hs index 22dffa10f9..352d60a387 100644 --- a/services/brig/test/integration/API/Metrics.hs +++ b/services/brig/test/integration/API/Metrics.hs @@ -60,8 +60,8 @@ testMetricsEndpoint opts brig0 = withSettingsOverrides opts $ do beforeSelf <- getCount "/self" "GET" beforeClients <- getCount "/users/:uid/clients" "GET" beforeProperties <- getCount "/login" "POST" - (uid, Just email) <- (\u -> (userId u, userEmail u)) <$> randomUser brig - uid' <- userId <$> randomUser brig + (uid, Just email) <- (\u -> (userId u, userEmail u)) <$> randomUser brig0 + uid' <- userId <$> randomUser brig0 _ <- get (brig . path p1 . zAuthAccess uid "conn" . expect2xx) _ <- get (brig . path (p2 $ toByteString' uid) . zAuthAccess uid "conn" . expect2xx) _ <- get (brig . path (p2 $ toByteString' uid') . zAuthAccess uid "conn" . expect2xx) diff --git a/services/brig/test/integration/API/OAuth.hs b/services/brig/test/integration/API/OAuth.hs new file mode 100644 index 0000000000..3efdd15fa4 --- /dev/null +++ b/services/brig/test/integration/API/OAuth.hs @@ -0,0 +1,869 @@ +-- 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 . +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module API.OAuth where + +import qualified API.Team.Util as Team +import Bilge +import Bilge.Assert +import Brig.API.OAuth hiding (verifyRefreshToken) +import Brig.Options +import qualified Brig.Options as Opt +import qualified Cassandra as C +import Control.Lens +import Control.Monad.Catch (MonadCatch) +import Crypto.JOSE (JOSE, JWK, bestJWSAlg, newJWSHeader, runJOSE) +import Crypto.JWT (Audience (Audience), ClaimsSet, JWTError, NumericDate (NumericDate), SignedJWT, claimAud, claimExp, claimIat, claimIss, claimSub, defaultJWTValidationSettings, emptyClaimsSet, signClaims, signJWT, stringOrUri, verifyClaims) +import qualified Data.Aeson as A +import qualified Data.ByteString.Char8 as BS +import Data.ByteString.Conversion (fromByteString, toByteString') +import Data.Domain (domainText) +import Data.Id +import Data.Qualified (Qualified (qUnqualified)) +import Data.Range +import Data.Set as Set hiding (delete, null, (\\)) +import Data.String.Conversions (cs) +import Data.Text.Ascii (encodeBase16) +import qualified Data.Text.Encoding as T +import Data.Time +import qualified Data.UUID as UUID +import Data.UUID.V4 (nextRandom) +import Imports +import Network.HTTP.Types (HeaderName) +import qualified Network.Wai.Utilities as Error +import Servant.API +import Test.Tasty +import Test.Tasty.HUnit +import Text.RawString.QQ +import URI.ByteString +import Util +import Web.FormUrlEncoded +import Wire.API.Conversation (Access (..), Conversation (cnvQualifiedId)) +import qualified Wire.API.Conversation as Conv +import Wire.API.Conversation.Code (CreateConversationCodeRequest (CreateConversationCodeRequest)) +import Wire.API.Conversation.Protocol (ProtocolTag (ProtocolProteusTag)) +import qualified Wire.API.Conversation.Role as Role +import Wire.API.OAuth +import Wire.API.Routes.Bearer (Bearer (Bearer, unBearer)) +import Wire.API.User +import Wire.API.User.Auth (CookieType (PersistentCookie)) +import Wire.Sem.Jwk (readJwk) + +tests :: Manager -> C.ClientState -> Brig -> Nginz -> Opts -> TestTree +tests m db b n o = do + testGroup + "oauth" + [ test m "register new oauth client" $ testRegisterNewOAuthClient b, + testGroup + "create oauth code" + [ test m "success" $ testCreateOAuthCodeSuccess b, + test m "oauth client not found" $ testCreateOAuthCodeClientNotFound b, + test m "redirect url mismatch" $ testCreateOAuthCodeRedirectUrlMismatch b + ], + testGroup + "create access token" + [ test m "success" $ testCreateAccessTokenSuccess o b, + test m "wrong client id fail" $ testCreateAccessTokenWrongClientId b, + test m "wrong code fail" $ testCreateAccessTokenWrongAuthorizationCode b, + test m "wrong redirect url fail" $ testCreateAccessTokenWrongUrl b, + test m "expired code fail" $ testCreateAccessTokenExpiredCode o b, + test m "wrong grant type fail" $ testCreateAccessTokenWrongGrantType b, + test m "wrong code challenge fail" $ testCreateAccessTokenWrongCodeChallenge b, + test m "wrong code verifier fail" $ testCreateAccessTokenWrongCodeVerifier b + ], + testGroup + "access denied when disabled" + [ test m "register" $ testRegisterOAuthClientAccessDeniedWhenDisabled o b, + test m "get client info" $ testGetOAuthClientInfoAccessDeniedWhenDisabled o b, + test m "create code" $ testCreateCodeOAuthClientAccessDeniedWhenDisabled o b, + test m "create token" $ testCreateAccessTokenAccessDeniedWhenDisabled o b, + test m "refresh access token" $ testRefreshAccessTokenAccessDeniedWhenDisabled o b + ], + testGroup + "accessing a resource" + [ test m "success" $ testAccessResourceSuccessNginz b n, + test m "failure - insufficient scope" $ testAccessResourceInsufficientScope b n, + test m "failure - expired token" $ testAccessResourceExpiredToken b n, + test m "failure - nonsense token" $ testAccessResourceNonsenseToken n, + test m "failure - no token" $ testAccessResourceNoToken n, + test m "failure - invalid signature" $ testAccessResourceInvalidSignature o b n + ], + testGroup + "accessing resources (only testing happy path to ensure scopes are valid)" + [ test m "write:conversations" $ testWriteConversationsSuccessNginz b n, + test m "read:feature_configs" $ testReadFeatureConfigsSuccessNginz b n, + test m "write:conversations_code" $ testWriteConversationsCodeSuccessNginz b n + ], + testGroup + "refresh tokens" + [ test m "max active tokens" $ testRefreshTokenMaxActiveTokens o db b, + test m "refresh access token - success" $ testRefreshTokenRetrieveAccessToken b n, + test m "wrong signature - fail" $ testRefreshTokenWrongSignature o b, + test m "no token id - fail" $ testRefreshTokenNoTokenId o b, + test m "non-existing id - fail" $ testRefreshTokenNonExistingId o b, + test m "wrong client id - fail" $ testRefreshTokenWrongClientId b, + test m "wrong grant type - fail" $ testRefreshTokenWrongGrantType b, + test m "expired token - fail" $ testRefreshTokenExpiredToken o b, + test m "revoked token - fail" $ testRefreshTokenRevokedToken b + ], + testGroup + "oauth applications" + [ test m "list applications with account access" $ testListApplicationsWithAccountAccess b, + test m "revoke application account access" $ testRevokeApplicationAccountAccess b + ] + ] + +testRegisterNewOAuthClient :: Brig -> Http () +testRegisterNewOAuthClient brig = do + let newOAuthClient@(RegisterOAuthClientRequest expectedAppName expectedUrl) = newOAuthClientRequestBody "E Corp" "https://example.com" + c <- registerNewOAuthClient brig newOAuthClient + uid <- randomId + oauthClientInfo <- getOAuthClientInfo brig uid c.clientId + liftIO $ do + expectedAppName @?= oauthClientInfo.name + expectedUrl @?= oauthClientInfo.redirectUrl + +testCreateOAuthCodeSuccess :: Brig -> Http () +testCreateOAuthCodeSuccess brig = do + let newOAuthClient@(RegisterOAuthClientRequest _ redirectUrl) = newOAuthClientRequestBody "E Corp" "https://example.com" + c <- registerNewOAuthClient brig newOAuthClient + uid <- randomId + let scope = OAuthScopes $ Set.fromList [WriteConversations, WriteConversationsCode] + state <- UUID.toText <$> liftIO nextRandom + + createOAuthCode brig uid (CreateOAuthAuthorizationCodeRequest c.clientId scope OAuthResponseTypeCode redirectUrl state S256 challenge) !!! do + const 201 === statusCode + const (Just $ unRedirectUrl redirectUrl ^. pathL) === (fmap getPath . getLocation) + const (Just $ ["code", "state"]) === (fmap (fmap fst . getQueryParams) . getLocation) + const (Just $ cs state) === (getLocation >=> getQueryParamValue "state") + const (Just True) === (getLocation >=> getQueryParamValue "code" >=> checkCode) + where + checkCode :: ByteString -> Maybe Bool + checkCode bs = Just $ BS.all isHexDigit bs && BS.length bs == 64 + +testCreateOAuthCodeRedirectUrlMismatch :: Brig -> Http () +testCreateOAuthCodeRedirectUrlMismatch brig = do + let newOAuthClient = newOAuthClientRequestBody "E Corp" "https://example.com" + c <- registerNewOAuthClient brig newOAuthClient + uid <- randomId + state <- UUID.toText <$> liftIO nextRandom + let differentUrl = mkUrl "https://wire.com" + createOAuthCode brig uid (CreateOAuthAuthorizationCodeRequest c.clientId mempty OAuthResponseTypeCode differentUrl state S256 challenge) !!! do + const 400 === statusCode + const Nothing === (fmap getPath . getLocation) + const (Just "redirect-url-miss-match") === fmap Error.label . responseJsonMaybe + +testCreateOAuthCodeClientNotFound :: Brig -> Http () +testCreateOAuthCodeClientNotFound brig = do + cid <- randomId + uid <- randomId + let redirectUrl = mkUrl "https://example.com" + state <- UUID.toText <$> liftIO nextRandom + createOAuthCode brig uid (CreateOAuthAuthorizationCodeRequest cid mempty OAuthResponseTypeCode redirectUrl state S256 challenge) !!! do + const 404 === statusCode + const (Just $ "access_denied") === (getLocation >=> getQueryParamValue "error") + const (Just $ cs state) === (getLocation >=> getQueryParamValue "state") + const (Just $ unRedirectUrl redirectUrl ^. pathL) === (fmap getPath . getLocation) + const Nothing === (getLocation >=> getQueryParamValue "code") + +testCreateAccessTokenSuccess :: Opt.Opts -> Brig -> Http () +testCreateAccessTokenSuccess opts brig = do + now <- liftIO getCurrentTime + user <- createUser "alice" brig + let redirectUrl = mkUrl "https://example.com" + let scopes = OAuthScopes $ Set.singleton ReadSelf + (cid, code) <- generateOAuthClientAndAuthorizationCode brig user.userId scopes redirectUrl + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid verifier code redirectUrl + resp <- createOAuthAccessToken brig accessTokenRequest + -- authorization code should be deleted and can only be used once + createOAuthAccessToken' brig accessTokenRequest !!! do + const 404 === statusCode + const (Just "not-found") === fmap Error.label . responseJsonMaybe + k <- liftIO $ readJwk (fromMaybe "path to jwk not set" (Opt.setOAuthJwkKeyPair $ Opt.optSettings opts)) <&> fromMaybe (error "invalid key") + verifiedOrError <- liftIO $ verify k (unOAuthToken $ resp.accessToken) + verifiedOrErrorWithotherKey <- liftIO $ verify badKey (unOAuthToken $ resp.accessToken) + let expectedDomain = domainText $ Opt.setFederationDomain $ Opt.optSettings opts + liftIO $ do + isRight verifiedOrError @?= True + isLeft verifiedOrErrorWithotherKey @?= True + let claims = either (error "invalid token") id verifiedOrError + claims.scope @?= scopes + (view claimIss $ claims) @?= (expectedDomain ^? stringOrUri @Text) + (view claimAud $ claims) @?= (Audience . (: []) <$> expectedDomain ^? stringOrUri @Text) + (view claimSub $ claims) @?= (idToText user.userId ^? stringOrUri) + let expTime = (\(NumericDate x) -> x) . fromMaybe (error "exp claim missing") . view claimExp $ claims + diffUTCTime expTime now > 0 @?= True + let issuingTime = (\(NumericDate x) -> x) . fromMaybe (error "iat claim missing") . view claimIat $ claims + abs (diffUTCTime issuingTime now) < 5 @?= True -- allow for some generous clock skew + +testCreateAccessTokenWrongClientId :: Brig -> Http () +testCreateAccessTokenWrongClientId brig = do + uid <- randomId + let redirectUrl = mkUrl "https://example.com" + let scopes = OAuthScopes $ Set.fromList [WriteConversations, WriteConversationsCode] + (_, code) <- generateOAuthClientAndAuthorizationCode brig uid scopes redirectUrl + cid <- randomId + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid verifier code redirectUrl + createOAuthAccessToken' brig accessTokenRequest !!! do + const 404 === statusCode + const (Just "not-found") === fmap Error.label . responseJsonMaybe + +testCreateAccessTokenWrongAuthorizationCode :: Brig -> Http () +testCreateAccessTokenWrongAuthorizationCode brig = do + uid <- randomId + let redirectUrl = mkUrl "https://example.com" + let scopes = OAuthScopes $ Set.fromList [WriteConversations, WriteConversationsCode] + (cid, _) <- generateOAuthClientAndAuthorizationCode brig uid scopes redirectUrl + let code = OAuthAuthorizationCode $ encodeBase16 "eb32eb9e2aa36c081c89067dddf81bce83c1c57e0b74cfb14c9f026f145f2b1f" + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid verifier code redirectUrl + createOAuthAccessToken' brig accessTokenRequest !!! do + const 404 === statusCode + const (Just "not-found") === fmap Error.label . responseJsonMaybe + +testCreateAccessTokenWrongUrl :: Brig -> Http () +testCreateAccessTokenWrongUrl brig = do + uid <- randomId + let redirectUrl = mkUrl "https://wire.com" + let scopes = OAuthScopes $ Set.fromList [WriteConversations, WriteConversationsCode] + (cid, code) <- generateOAuthClientAndAuthorizationCode brig uid scopes redirectUrl + let wrongUrl = mkUrl "https://example.com" + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid verifier code wrongUrl + createOAuthAccessToken' brig accessTokenRequest !!! do + const 400 === statusCode + const (Just "redirect-url-miss-match") === fmap Error.label . responseJsonMaybe + +testCreateAccessTokenExpiredCode :: Opt.Opts -> Brig -> Http () +testCreateAccessTokenExpiredCode opts brig = + withSettingsOverrides (opts & Opt.optionSettings . Opt.oauthAuthorizationCodeExpirationTimeSecsInternal ?~ 1) $ do + uid <- randomId + let redirectUrl = mkUrl "https://example.com" + let scopes = OAuthScopes $ Set.fromList [WriteConversations, WriteConversationsCode] + (cid, code) <- generateOAuthClientAndAuthorizationCode brig uid scopes redirectUrl + liftIO $ threadDelay (1 * 1200 * 1000) + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid verifier code redirectUrl + createOAuthAccessToken' brig accessTokenRequest !!! do + const 404 === statusCode + const (Just "not-found") === fmap Error.label . responseJsonMaybe + +testCreateAccessTokenWrongGrantType :: Brig -> Http () +testCreateAccessTokenWrongGrantType brig = do + uid <- randomId + let redirectUrl = mkUrl "https://example.com" + let scopes = OAuthScopes $ Set.fromList [WriteConversations, WriteConversationsCode] + (cid, code) <- generateOAuthClientAndAuthorizationCode brig uid scopes redirectUrl + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeRefreshToken cid verifier code redirectUrl + createOAuthAccessToken' brig accessTokenRequest !!! assertAccessDenied + +testCreateAccessTokenWrongCodeChallenge :: Brig -> Http () +testCreateAccessTokenWrongCodeChallenge brig = do + uid <- randomId + let redirectUrl = mkUrl "https://example.com" + let scopes = OAuthScopes $ Set.fromList [WriteConversations, WriteConversationsCode] + (cid, code) <- generateOAuthClientAndAuthorizationCode' wrongCodeChallenge brig uid scopes redirectUrl + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid verifier code redirectUrl + createOAuthAccessToken' brig accessTokenRequest !!! do + const 403 === statusCode + const (Just "invalid_grant") === fmap Error.label . responseJsonMaybe + where + wrongCodeChallenge :: OAuthCodeChallenge + wrongCodeChallenge = fromMaybe (error $ "invalid code challenge") $ A.decode "\"kw8DtStRIz2MTWyG59pd9h2Kyfhoa8SM4aU8CUWM1DU\"" + +testCreateAccessTokenWrongCodeVerifier :: Brig -> Http () +testCreateAccessTokenWrongCodeVerifier brig = do + uid <- randomId + let redirectUrl = mkUrl "https://example.com" + let scopes = OAuthScopes $ Set.fromList [WriteConversations, WriteConversationsCode] + (cid, code) <- generateOAuthClientAndAuthorizationCode brig uid scopes redirectUrl + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid wrongCodeVerifier code redirectUrl + createOAuthAccessToken' brig accessTokenRequest !!! do + const 403 === statusCode + const (Just "invalid_grant") === fmap Error.label . responseJsonMaybe + where + wrongCodeVerifier :: OAuthCodeVerifier + wrongCodeVerifier = fromMaybe (error "invalid code verifier") $ A.decode "\"x9xpNj_TNfXY5h-CggZozno7ldzPmbEh8al~HJQmfiZtvvx0uxlDa~mNCZrH37XZnClD71Vx_Edx8FU1XU2mt38.o49Wnca~at75RBoxHn..F-_n5kveOSCpc_Oemyap\"" + +testGetOAuthClientInfoAccessDeniedWhenDisabled :: Opt.Opts -> Brig -> Http () +testGetOAuthClientInfoAccessDeniedWhenDisabled opts brig = + withSettingsOverrides (opts & Opt.optionSettings . Opt.oauthEnabledInternal ?~ False) $ do + cid <- randomId + uid <- randomId + getOAuthClientInfo' brig uid cid !!! assertAccessDenied + +testCreateCodeOAuthClientAccessDeniedWhenDisabled :: Opt.Opts -> Brig -> Http () +testCreateCodeOAuthClientAccessDeniedWhenDisabled opts brig = + withSettingsOverrides (opts & Opt.optionSettings . Opt.oauthEnabledInternal ?~ False) $ do + cid <- randomId + uid <- randomId + state <- UUID.toText <$> liftIO nextRandom + let redirectUrl = mkUrl "https://example.com" + createOAuthCode brig uid (CreateOAuthAuthorizationCodeRequest cid mempty OAuthResponseTypeCode redirectUrl state S256 challenge) !!! do + const 403 === statusCode + const (Just $ "access_denied") === (getLocation >=> getQueryParamValue "error") + const (Just $ cs state) === (getLocation >=> getQueryParamValue "state") + const (Just $ unRedirectUrl redirectUrl ^. pathL) === (fmap getPath . getLocation) + const Nothing === (getLocation >=> getQueryParamValue "code") + +testCreateAccessTokenAccessDeniedWhenDisabled :: Opt.Opts -> Brig -> Http () +testCreateAccessTokenAccessDeniedWhenDisabled opts brig = + withSettingsOverrides (opts & Opt.optionSettings . Opt.oauthEnabledInternal ?~ False) $ do + cid <- randomId + let code = OAuthAuthorizationCode $ encodeBase16 "eb32eb9e2aa36c081c89067dddf81bce83c1c57e0b74cfb14c9f026f145f2b1f" + let url = mkUrl "https://example.com" + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid verifier code url + createOAuthAccessToken' brig accessTokenRequest !!! assertAccessDenied + +testRefreshAccessTokenAccessDeniedWhenDisabled :: Opt.Opts -> Brig -> Http () +testRefreshAccessTokenAccessDeniedWhenDisabled opts brig = do + uid <- randomId + let redirectUrl = mkUrl "https://example.com" + let scopes = OAuthScopes $ Set.fromList [ReadSelf] + (cid, code) <- generateOAuthClientAndAuthorizationCode brig uid scopes redirectUrl + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid verifier code redirectUrl + resp <- createOAuthAccessToken brig accessTokenRequest + withSettingsOverrides (opts & Opt.optionSettings . Opt.oauthEnabledInternal ?~ False) $ do + let refreshAccessTokenRequest = OAuthRefreshAccessTokenRequest OAuthGrantTypeRefreshToken cid resp.refreshToken + refreshOAuthAccessToken' brig refreshAccessTokenRequest !!! assertAccessDenied + +testRegisterOAuthClientAccessDeniedWhenDisabled :: Opt.Opts -> Brig -> Http () +testRegisterOAuthClientAccessDeniedWhenDisabled opts brig = + withSettingsOverrides (opts & Opt.optionSettings . Opt.oauthEnabledInternal ?~ False) $ do + let newOAuthClient = newOAuthClientRequestBody "E Corp" "https://example.com" + registerNewOAuthClient' brig newOAuthClient !!! assertAccessDenied + +assertAccessDenied :: Assertions () +assertAccessDenied = do + const 403 === statusCode + const (Just "forbidden") === fmap Error.label . responseJsonMaybe + +testAccessResourceSuccessNginz :: Brig -> Nginz -> Http () +testAccessResourceSuccessNginz brig nginz = do + -- with ZAuth header + user <- createUser "alice" brig + let email = fromMaybe (error "no email") $ userEmail user + zauthToken <- decodeToken <$> (login nginz (defEmailLogin email) PersistentCookie toByteString' zauthToken)) Nginz -> Http () +testAccessResourceInsufficientScope brig nginz = do + user <- createUser "alice" brig + let redirectUrl = mkUrl "https://example.com" + let scopes = OAuthScopes $ Set.fromList [WriteConversations] + (cid, code) <- generateOAuthClientAndAuthorizationCode brig user.userId scopes redirectUrl + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid verifier code redirectUrl + resp <- createOAuthAccessToken brig accessTokenRequest + get (nginz . paths ["self"] . authHeader resp.accessToken) !!! do + const 403 === statusCode + const "Forbidden" === statusMessage + +testAccessResourceExpiredToken :: Brig -> Nginz -> Http () +testAccessResourceExpiredToken brig nginz = do + user <- createUser "alice" brig + let redirectUrl = mkUrl "https://example.com" + let scopes = OAuthScopes $ Set.fromList [ReadSelf] + (cid, code) <- generateOAuthClientAndAuthorizationCode brig user.userId scopes redirectUrl + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid verifier code redirectUrl + resp <- createOAuthAccessToken brig accessTokenRequest + liftIO $ threadDelay (5 * 1000 * 1000) + get (nginz . paths ["self"] . authHeader resp.accessToken) !!! do + const 401 === statusCode + const "Unauthorized" === statusMessage + +testAccessResourceNonsenseToken :: Nginz -> Http () +testAccessResourceNonsenseToken nginz = do + get (nginz . paths ["self"] . authHeader @Text "foo") !!! do + const 401 === statusCode + const "Unauthorized" === statusMessage + +testAccessResourceNoToken :: Nginz -> Http () +testAccessResourceNoToken nginz = + get (nginz . paths ["self"]) !!! do + const 401 === statusCode + const "Unauthorized" === statusMessage + +testAccessResourceInvalidSignature :: Opt.Opts -> Brig -> Nginz -> Http () +testAccessResourceInvalidSignature opts brig nginz = do + user <- createUser "alice" brig + let redirectUrl = mkUrl "https://example.com" + let scopes = OAuthScopes $ Set.fromList [ReadSelf] + (cid, code) <- generateOAuthClientAndAuthorizationCode brig user.userId scopes redirectUrl + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid verifier code redirectUrl + resp <- createOAuthAccessToken brig accessTokenRequest + key <- liftIO $ readJwk (fromMaybe "path to jwk not set" (Opt.setOAuthJwkKeyPair $ Opt.optSettings opts)) <&> fromMaybe (error "invalid key") + claimSet <- fromRight (error "token invalid") <$> liftIO (verify key (unOAuthToken $ resp.accessToken)) + tokenSignedWithotherKey <- signAccessToken badKey claimSet + get (nginz . paths ["self"] . authHeader (OAuthToken tokenSignedWithotherKey)) !!! do + const 401 === statusCode + const "Unauthorized" === statusMessage + +testRefreshTokenMaxActiveTokens :: Opts -> C.ClientState -> Brig -> Http () +testRefreshTokenMaxActiveTokens opts db brig = + withSettingsOverrides (opts & Opt.optionSettings . Opt.oauthMaxActiveRefreshTokensInternal ?~ 2) $ do + uid <- randomId + jwk <- liftIO $ readJwk (fromMaybe "path to jwk not set" (Opt.setOAuthJwkKeyPair $ Opt.optSettings opts)) <&> fromMaybe (error "invalid key") + let redirectUrl = mkUrl "https://example.com" + let scopes = OAuthScopes $ Set.fromList [WriteConversations, WriteConversationsCode] + let delayOneSec = + -- we have to wait ~1 sec before we create the next token, to make sure it is created with a different timestamp + -- this is due to the interpreter of the `Now` effect which auto-updates every second + -- FUTUREWORK: once the interpreter of the `Now` effect is changed to use a monotonic clock, we can remove this delay + threadDelay $ 1000 * 1000 + (rid1, cid, _) <- do + let testMsg = "0 active refresh tokens - 1st requested token will be active" + (cid, code) <- generateOAuthClientAndAuthorizationCode brig uid scopes redirectUrl + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid verifier code redirectUrl + resp <- createOAuthAccessToken brig accessTokenRequest + rid <- extractRefreshTokenId jwk resp.refreshToken + tokens <- C.runClient db (lookupOAuthRefreshTokens uid) + liftIO $ assertBool testMsg $ [rid] `hasSameElems` (refreshTokenId <$> tokens) + pure (rid, cid, secret) + delayOneSec + rid2 <- do + let testMsg = "1 active refresh token - 2nd requested token will added to active tokens" + code <- generateOAuthAuthorizationCode brig uid cid scopes redirectUrl + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid verifier code redirectUrl + resp <- createOAuthAccessToken brig accessTokenRequest + rid <- extractRefreshTokenId jwk resp.refreshToken + tokens <- C.runClient db (lookupOAuthRefreshTokens uid) + liftIO $ assertBool testMsg $ [rid1, rid] `hasSameElems` (refreshTokenId <$> tokens) + pure rid + delayOneSec + rid3 <- do + let testMsg = "2 active refresh tokens - 3rd token requested replaces the 1st one" + code <- generateOAuthAuthorizationCode brig uid cid scopes redirectUrl + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid verifier code redirectUrl + resp <- createOAuthAccessToken brig accessTokenRequest + rid <- extractRefreshTokenId jwk resp.refreshToken + recoverN 3 $ do + tokens <- C.runClient db (lookupOAuthRefreshTokens uid) + liftIO $ assertBool testMsg $ [rid2, rid] `hasSameElems` (refreshTokenId <$> tokens) + pure rid + delayOneSec + do + let testMsg = "2 active refresh tokens - 4th token requests replaces the 2nd one" + code <- generateOAuthAuthorizationCode brig uid cid scopes redirectUrl + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid verifier code redirectUrl + resp <- createOAuthAccessToken brig accessTokenRequest + rid <- extractRefreshTokenId jwk resp.refreshToken + tokens <- C.runClient db (lookupOAuthRefreshTokens uid) + liftIO $ assertBool testMsg $ [rid3, rid] `hasSameElems` (refreshTokenId <$> tokens) + where + extractRefreshTokenId :: MonadIO m => JWK -> OAuthRefreshToken -> m OAuthRefreshTokenId + extractRefreshTokenId jwk rt = do + fromMaybe (error "invalid sub") . hcsSub <$> liftIO (verifyRefreshToken jwk (unOAuthToken rt)) + + hasSameElems :: (Ord a) => [a] -> [a] -> Bool + hasSameElems (Set.fromList -> x) (Set.fromList -> y) = x == y + +testRefreshTokenRetrieveAccessToken :: Brig -> Nginz -> Http () +testRefreshTokenRetrieveAccessToken brig nginz = do + user <- createUser "alice" brig + let redirectUrl = mkUrl "https://example.com" + let scopes = OAuthScopes $ Set.fromList [ReadSelf] + (cid, code) <- generateOAuthClientAndAuthorizationCode brig user.userId scopes redirectUrl + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid verifier code redirectUrl + resp <- createOAuthAccessToken brig accessTokenRequest + get (nginz . paths ["self"] . authHeader (resp.accessToken)) !!! const 200 === statusCode + threadDelay $ 5 * 1000 * 1000 -- wait 5 seconds for access token to expire + get (nginz . paths ["self"] . authHeader (resp.accessToken)) !!! const 401 === statusCode + let refreshAccessTokenRequest = OAuthRefreshAccessTokenRequest OAuthGrantTypeRefreshToken cid resp.refreshToken + resp' <- refreshOAuthAccessToken brig refreshAccessTokenRequest + get (nginz . paths ["self"] . authHeader resp'.accessToken) !!! const 200 === statusCode + +testRefreshTokenWrongSignature :: Opts -> Brig -> Http () +testRefreshTokenWrongSignature opts brig = do + user <- createUser "alice" brig + let redirectUrl = mkUrl "https://example.com" + let scopes = OAuthScopes $ Set.fromList [ReadSelf] + (cid, code) <- generateOAuthClientAndAuthorizationCode brig user.userId scopes redirectUrl + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid verifier code redirectUrl + resp <- createOAuthAccessToken brig accessTokenRequest + key <- liftIO $ readJwk (fromMaybe "path to jwk not set" (Opt.setOAuthJwkKeyPair $ Opt.optSettings opts)) <&> fromMaybe (error "invalid key") + badRefreshToken <- liftIO $ do + claims <- verifyRefreshToken key (unOAuthToken $ resp.refreshToken) + OAuthToken <$> signRefreshToken badKey claims + let refreshAccessTokenRequest = OAuthRefreshAccessTokenRequest OAuthGrantTypeRefreshToken cid badRefreshToken + refreshOAuthAccessToken' brig refreshAccessTokenRequest !!! do + const 403 === statusCode + const "Forbidden" === statusMessage + +testRefreshTokenNoTokenId :: Opts -> Brig -> Http () +testRefreshTokenNoTokenId opts brig = do + user <- createUser "alice" brig + let redirectUrl = mkUrl "https://example.com" + let scopes = OAuthScopes $ Set.fromList [ReadSelf] + (cid, _) <- generateOAuthClientAndAuthorizationCode brig user.userId scopes redirectUrl + key <- liftIO $ readJwk (fromMaybe "path to jwk not set" (Opt.setOAuthJwkKeyPair $ Opt.optSettings opts)) <&> fromMaybe (error "invalid key") + badRefreshToken <- liftIO $ OAuthToken <$> signRefreshToken key emptyClaimsSet + let refreshAccessTokenRequest = OAuthRefreshAccessTokenRequest OAuthGrantTypeRefreshToken cid badRefreshToken + refreshOAuthAccessToken' brig refreshAccessTokenRequest !!! do + const 403 === statusCode + const "Forbidden" === statusMessage + +testRefreshTokenNonExistingId :: Opts -> Brig -> Http () +testRefreshTokenNonExistingId opts brig = do + user <- createUser "alice" brig + let redirectUrl = mkUrl "https://example.com" + let scopes = OAuthScopes $ Set.fromList [ReadSelf] + (cid, code) <- generateOAuthClientAndAuthorizationCode brig user.userId scopes redirectUrl + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid verifier code redirectUrl + resp <- createOAuthAccessToken brig accessTokenRequest + key <- liftIO $ readJwk (fromMaybe "path to jwk not set" (Opt.setOAuthJwkKeyPair $ Opt.optSettings opts)) <&> fromMaybe (error "invalid key") + badRefreshToken <- + liftIO $ + OAuthToken <$> do + claims <- verifyRefreshToken key (unOAuthToken $ resp.refreshToken) + rid :: OAuthRefreshTokenId <- randomId + sub <- maybe (error "creating sub claim failed") pure $ idToText rid ^? stringOrUri + let invalidClaims = claims & claimSub ?~ sub + signRefreshToken key invalidClaims + let refreshAccessTokenRequest = OAuthRefreshAccessTokenRequest OAuthGrantTypeRefreshToken cid badRefreshToken + refreshOAuthAccessToken' brig refreshAccessTokenRequest !!! do + const 403 === statusCode + const "Forbidden" === statusMessage + +testRefreshTokenWrongClientId :: Brig -> Http () +testRefreshTokenWrongClientId brig = do + user <- createUser "alice" brig + let redirectUrl = mkUrl "https://example.com" + let scopes = OAuthScopes $ Set.fromList [ReadSelf] + (cid, code) <- generateOAuthClientAndAuthorizationCode brig user.userId scopes redirectUrl + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid verifier code redirectUrl + resp <- createOAuthAccessToken brig accessTokenRequest + badCid <- randomId + let refreshAccessTokenRequest = OAuthRefreshAccessTokenRequest OAuthGrantTypeRefreshToken badCid resp.refreshToken + refreshOAuthAccessToken' brig refreshAccessTokenRequest !!! do + const 403 === statusCode + const "Forbidden" === statusMessage + +testRefreshTokenWrongGrantType :: Brig -> Http () +testRefreshTokenWrongGrantType brig = do + user <- createUser "alice" brig + let redirectUrl = mkUrl "https://example.com" + let scopes = OAuthScopes $ Set.fromList [ReadSelf] + (cid, code) <- generateOAuthClientAndAuthorizationCode brig user.userId scopes redirectUrl + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid verifier code redirectUrl + resp <- createOAuthAccessToken brig accessTokenRequest + let refreshAccessTokenRequest = OAuthRefreshAccessTokenRequest OAuthGrantTypeAuthorizationCode cid resp.refreshToken + refreshOAuthAccessToken' brig refreshAccessTokenRequest !!! do + const 403 === statusCode + const "Forbidden" === statusMessage + +testRefreshTokenExpiredToken :: Opts -> Brig -> Http () +testRefreshTokenExpiredToken opts brig = + -- overriding settings and set refresh token to expire in 2 seconds + withSettingsOverrides (opts & Opt.optionSettings . Opt.oauthRefreshTokenExpirationTimeSecsInternal ?~ 2) $ do + user <- createUser "alice" brig + let redirectUrl = mkUrl "https://example.com" + let scopes = OAuthScopes $ Set.fromList [ReadSelf] + (cid, code) <- generateOAuthClientAndAuthorizationCode brig user.userId scopes redirectUrl + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid verifier code redirectUrl + resp <- createOAuthAccessToken brig accessTokenRequest + let refreshAccessTokenRequest = OAuthRefreshAccessTokenRequest OAuthGrantTypeRefreshToken cid resp.refreshToken + threadDelay $ 2 * 1010 * 1000 -- wait for 2 seconds for the token to expire + refreshOAuthAccessToken' brig refreshAccessTokenRequest !!! do + const 403 === statusCode + const "Forbidden" === statusMessage + +testRefreshTokenRevokedToken :: Brig -> Http () +testRefreshTokenRevokedToken brig = do + user <- createUser "alice" brig + let redirectUrl = mkUrl "https://example.com" + let scopes = OAuthScopes $ Set.fromList [ReadSelf] + (cid, code) <- generateOAuthClientAndAuthorizationCode brig user.userId scopes redirectUrl + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid verifier code redirectUrl + resp <- createOAuthAccessToken brig accessTokenRequest + let refreshAccessTokenRequest = OAuthRefreshAccessTokenRequest OAuthGrantTypeRefreshToken cid resp.refreshToken + revokeOAuthRefreshToken brig (OAuthRevokeRefreshTokenRequest cid resp.refreshToken) !!! const 200 === statusCode + refreshOAuthAccessToken' brig refreshAccessTokenRequest !!! do + const 403 === statusCode + const "Forbidden" === statusMessage + +testListApplicationsWithAccountAccess :: Brig -> Http () +testListApplicationsWithAccountAccess brig = do + alice <- createUser "alice" brig + bob <- createUser "bob" brig + do + apps <- listOAuthApplications brig alice.userId + liftIO $ assertEqual "apps" 0 (length apps) + void $ createOAuthApplicationWithAccountAccess brig alice.userId + void $ createOAuthApplicationWithAccountAccess brig alice.userId + do + aliceApps <- listOAuthApplications brig alice.userId + liftIO $ assertEqual "apps" 2 (length aliceApps) + bobsApps <- listOAuthApplications brig bob.userId + liftIO $ assertEqual "apps" 0 (length bobsApps) + void $ createOAuthApplicationWithAccountAccess brig alice.userId + void $ createOAuthApplicationWithAccountAccess brig bob.userId + do + aliceApps <- listOAuthApplications brig alice.userId + liftIO $ assertEqual "apps" 3 (length aliceApps) + bobsApps <- listOAuthApplications brig bob.userId + liftIO $ assertEqual "apps" 1 (length bobsApps) + +testRevokeApplicationAccountAccess :: Brig -> Http () +testRevokeApplicationAccountAccess brig = do + user <- createUser "alice" brig + do + apps <- listOAuthApplications brig user.userId + liftIO $ assertEqual "apps" 0 (length apps) + for_ [1 .. 3 :: Int] $ const $ createOAuthApplicationWithAccountAccess brig user.userId + cids <- fmap applicationId <$> listOAuthApplications brig user.userId + liftIO $ assertEqual "apps" 3 (length cids) + case cids of + [cid1, cid2, cid3] -> do + revokeOAuthApplicationAccess brig user.userId cid1 + do + apps <- listOAuthApplications brig user.userId + liftIO $ assertEqual "apps" 2 (length apps) + revokeOAuthApplicationAccess brig user.userId cid2 + do + apps <- listOAuthApplications brig user.userId + liftIO $ assertEqual "apps" 1 (length apps) + revokeOAuthApplicationAccess brig user.userId cid3 + do + apps <- listOAuthApplications brig user.userId + liftIO $ assertEqual "apps" 0 (length apps) + _ -> liftIO $ assertFailure "unexpected number of apps" + +testWriteConversationsSuccessNginz :: Brig -> Nginz -> Http () +testWriteConversationsSuccessNginz brig nginz = do + (uid, tid) <- Team.createUserWithTeam brig + resp <- getAccessTokenForScope brig uid [WriteConversations] + createTeamConv nginz authHeader resp.accessToken tid "oauth test group" !!! do + const 201 === statusCode + +testReadFeatureConfigsSuccessNginz :: Brig -> Nginz -> Http () +testReadFeatureConfigsSuccessNginz brig nginz = do + (uid, _) <- Team.createUserWithTeam brig + resp <- getAccessTokenForScope brig uid [ReadFeatureConfigs] + getFeatureConfigs nginz authHeader resp.accessToken !!! do + const 200 === statusCode + +testWriteConversationsCodeSuccessNginz :: Brig -> Nginz -> Http () +testWriteConversationsCodeSuccessNginz brig nginz = do + (uid, tid) <- Team.createUserWithTeam brig + resp <- getAccessTokenForScope brig uid [WriteConversations, WriteConversationsCode] + conv <- + responseJsonError + =<< createTeamConv nginz authHeader resp.accessToken tid "oauth test group" Request) -> (OAuthAccessToken -> Request -> Request) -> OAuthAccessToken -> ConvId -> Http ResponseLBS +postConvCode svc mkHeader token c = do + post $ + svc + . paths ["conversations", toByteString' c, "code"] + . mkHeader token + . json (CreateConversationCodeRequest Nothing) + +getAccessTokenForScope :: Brig -> UserId -> [OAuthScope] -> Http OAuthAccessTokenResponse +getAccessTokenForScope brig uid scopes = do + let redirectUrl = mkUrl "https://example.com" + (cid, code) <- generateOAuthClientAndAuthorizationCode brig uid (OAuthScopes $ Set.fromList scopes) redirectUrl + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid verifier code redirectUrl + createOAuthAccessToken brig accessTokenRequest + +createTeamConv :: + (Request -> Request) -> + (OAuthAccessToken -> Request -> Request) -> + OAuthAccessToken -> + TeamId -> + Text -> + Http ResponseLBS +createTeamConv svc mkHeader token tid name = do + let tinfo = Conv.ConvTeamInfo tid + let conv = Conv.NewConv [] [] (checked name) (Set.fromList [CodeAccess]) Nothing (Just tinfo) Nothing Nothing Role.roleNameWireAdmin ProtocolProteusTag + post $ + svc + . path "conversations" + . mkHeader token + . json conv + +getFeatureConfigs :: + (Request -> Request) -> + (OAuthAccessToken -> Request -> Request) -> + OAuthAccessToken -> + Http ResponseLBS +getFeatureConfigs svc mkHeader token = do + get $ + svc + . path "feature-configs" + . mkHeader token + +createOAuthApplicationWithAccountAccess :: Brig -> UserId -> Http OAuthAccessTokenResponse +createOAuthApplicationWithAccountAccess brig uid = do + let redirectUrl = mkUrl "https://example.com" + (cid, code) <- generateOAuthClientAndAuthorizationCode brig uid (OAuthScopes $ mempty) redirectUrl + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid verifier code redirectUrl + createOAuthAccessToken brig accessTokenRequest + +verifyRefreshToken :: JWK -> SignedJWT -> IO ClaimsSet +verifyRefreshToken jwk jwt = + fromRight (error "invalid jwt or jwk") + <$> runJOSE (verifyClaims (defaultJWTValidationSettings (const True)) jwk jwt :: JOSE JWTError IO ClaimsSet) + +authHeader :: ToHttpApiData a => a -> Request -> Request +authHeader = bearer "Authorization" + +bearer :: ToHttpApiData a => HeaderName -> a -> Request -> Request +bearer name = header name . toHeader . Bearer + +newOAuthClientRequestBody :: Text -> Text -> RegisterOAuthClientRequest +newOAuthClientRequestBody name url = + let redirectUrl = mkUrl (cs url) + applicationName = OAuthApplicationName (unsafeRange name) + in RegisterOAuthClientRequest applicationName redirectUrl + +registerNewOAuthClient :: (MonadIO m, MonadHttp m, MonadCatch m, HasCallStack) => Brig -> RegisterOAuthClientRequest -> m OAuthClientCredentials +registerNewOAuthClient brig reqBody = + responseJsonError =<< registerNewOAuthClient' brig reqBody Brig -> RegisterOAuthClientRequest -> m ResponseLBS +registerNewOAuthClient' brig reqBody = + post (brig . paths ["i", "oauth", "clients"] . json reqBody) + +getOAuthClientInfo :: (MonadIO m, MonadHttp m, MonadCatch m, HasCallStack) => Brig -> UserId -> OAuthClientId -> m OAuthClient +getOAuthClientInfo brig uid cid = + responseJsonError =<< getOAuthClientInfo' brig uid cid Brig -> UserId -> OAuthClientId -> m ResponseLBS +getOAuthClientInfo' brig uid cid = + get (brig . paths ["oauth", "clients", toByteString' cid] . zUser uid) + +createOAuthCode :: (MonadHttp m) => Brig -> UserId -> CreateOAuthAuthorizationCodeRequest -> m ResponseLBS +createOAuthCode brig uid reqBody = post (brig . paths ["oauth", "authorization", "codes"] . zUser uid . json reqBody . noRedirect) + +createOAuthAccessToken :: (MonadIO m, MonadHttp m, MonadCatch m, HasCallStack) => Brig -> OAuthAccessTokenRequest -> m OAuthAccessTokenResponse +createOAuthAccessToken brig reqBody = responseJsonError =<< createOAuthAccessToken' brig reqBody Brig -> OAuthAccessTokenRequest -> m ResponseLBS +createOAuthAccessToken' brig reqBody = do + post (brig . paths ["oauth", "token"] . content "application/x-www-form-urlencoded" . body (RequestBodyLBS $ urlEncodeAsForm reqBody)) + +refreshOAuthAccessToken :: (MonadIO m, MonadHttp m, MonadCatch m, HasCallStack) => Brig -> OAuthRefreshAccessTokenRequest -> m OAuthAccessTokenResponse +refreshOAuthAccessToken brig reqBody = + responseJsonError =<< refreshOAuthAccessToken' brig reqBody Brig -> OAuthRefreshAccessTokenRequest -> m ResponseLBS +refreshOAuthAccessToken' brig reqBody = + post (brig . paths ["oauth", "token"] . content "application/x-www-form-urlencoded" . body (RequestBodyLBS $ urlEncodeAsForm reqBody)) + +listOAuthApplications' :: (MonadHttp m) => Brig -> UserId -> m ResponseLBS +listOAuthApplications' brig uid = + get (brig . paths ["oauth", "applications"] . zUser uid) + +listOAuthApplications :: (MonadIO m, MonadHttp m, MonadCatch m, HasCallStack) => Brig -> UserId -> m [OAuthApplication] +listOAuthApplications brig uid = + responseJsonError =<< listOAuthApplications' brig uid Brig -> UserId -> OAuthClientId -> m ResponseLBS +revokeOAuthApplicationAccess' brig uid cid = + delete (brig . paths ["oauth", "applications", toByteString' cid] . zUser uid) + +revokeOAuthApplicationAccess :: (MonadIO m, MonadHttp m, MonadCatch m, HasCallStack) => Brig -> UserId -> OAuthClientId -> m () +revokeOAuthApplicationAccess brig uid cid = + void $ revokeOAuthApplicationAccess' brig uid cid Brig -> UserId -> OAuthScopes -> RedirectUrl -> m (OAuthClientId, OAuthAuthorizationCode) +generateOAuthClientAndAuthorizationCode = generateOAuthClientAndAuthorizationCode' challenge + +generateOAuthClientAndAuthorizationCode' :: (MonadIO m, MonadHttp m, MonadCatch m, HasCallStack) => OAuthCodeChallenge -> Brig -> UserId -> OAuthScopes -> RedirectUrl -> m (OAuthClientId, OAuthAuthorizationCode) +generateOAuthClientAndAuthorizationCode' chal brig uid scope url = do + let newOAuthClient = RegisterOAuthClientRequest (OAuthApplicationName (unsafeRange "E Corp")) url + OAuthClientCredentials cid _ <- registerNewOAuthClient brig newOAuthClient + (cid,) <$> generateOAuthAuthorizationCode' chal brig uid cid scope url + +generateOAuthAuthorizationCode :: (MonadIO m, MonadHttp m, MonadCatch m, HasCallStack) => Brig -> UserId -> OAuthClientId -> OAuthScopes -> RedirectUrl -> m OAuthAuthorizationCode +generateOAuthAuthorizationCode = generateOAuthAuthorizationCode' challenge + +generateOAuthAuthorizationCode' :: (MonadIO m, MonadHttp m, MonadCatch m, HasCallStack) => OAuthCodeChallenge -> Brig -> UserId -> OAuthClientId -> OAuthScopes -> RedirectUrl -> m OAuthAuthorizationCode +generateOAuthAuthorizationCode' chal brig uid cid scope url = do + state <- UUID.toText <$> liftIO nextRandom + response <- + createOAuthCode brig uid (CreateOAuthAuthorizationCodeRequest cid scope OAuthResponseTypeCode url state S256 chal) => fromByteString >=> getQueryParamValue "code" >=> fromByteString) response + +signAccessToken :: JWK -> OAuthClaimsSet -> Http SignedJWT +signAccessToken key claims = do + jwtOrError <- liftIO $ doSignClaims + either (const $ error "jwt error") pure jwtOrError + where + doSignClaims :: IO (Either JWTError SignedJWT) + doSignClaims = runJOSE $ do + algo <- bestJWSAlg key + signJWT key (newJWSHeader ((), algo)) claims + +signRefreshToken :: JWK -> ClaimsSet -> IO SignedJWT +signRefreshToken key claims = do + jwtOrError <- doSignClaims + either (const $ error "jwt error") pure jwtOrError + where + doSignClaims :: IO (Either JWTError SignedJWT) + doSignClaims = runJOSE $ do + algo <- bestJWSAlg key + signClaims key (newJWSHeader ((), algo)) claims + +badKey :: JWK +badKey = do + fromMaybe (error "invalid jwk") . A.decode $ + [r| {"kty":"OKP","crv":"Ed25519","x":"VWWycQ0yCoKAwN-DjjyQVWMRan1VOFUGlZpSTaLw1OA","d":"kH9sO4hDmRQi31IncBvwiaRB9xo9UHVaSvfV7RWiQOg"} |] + +mkUrl :: ByteString -> RedirectUrl +mkUrl = fromMaybe (error "invalid url") . fromByteString + +revokeOAuthRefreshToken :: (MonadHttp m) => Brig -> OAuthRevokeRefreshTokenRequest -> m ResponseLBS +revokeOAuthRefreshToken brig req = post (brig . paths ["oauth", "revoke"] . json req) + +instance ToHttpApiData a => ToHttpApiData (Bearer a) where + toHeader = (<>) "Bearer " . toHeader . unBearer + toUrlPiece = T.decodeUtf8 . toHeader + +getLocation :: ResponseLBS -> Maybe RedirectUrl +getLocation = getHeader "Location" >=> fromByteString + +getPath :: RedirectUrl -> ByteString +getPath (RedirectUrl uri) = uri ^. pathL + +getQueryParams :: RedirectUrl -> [(ByteString, ByteString)] +getQueryParams (RedirectUrl uri) = uri ^. (queryL . queryPairsL) + +getQueryParamValue :: ByteString -> RedirectUrl -> Maybe ByteString +getQueryParamValue key uri = snd <$> find ((== key) . fst) (getQueryParams uri) + +challenge :: OAuthCodeChallenge +challenge = either (\e -> error $ "invalid code challenge " <> show e) id $ A.eitherDecode "\"G7CWLBqYDT8doT_oEIN3un_QwZWYKHmOqG91nwNzITc\"" + +verifier :: OAuthCodeVerifier +verifier = either (\e -> error $ "invalid code verifier " <> show e) id $ A.eitherDecode "\"nE3k3zykOmYki~kriKzAmeFiGT7cWugcuToFwo1YPgrZ1cFvaQqLa.dXY9MnDj3umAmG-8lSNIYIl31Cs_.fV5r2psa4WWZcB.Nlc3A-t3p67NDZaOJjIiH~8PvUH_hR\"" diff --git a/services/brig/test/integration/API/Provider.hs b/services/brig/test/integration/API/Provider.hs index 88b33eb8f6..bf5b7db8fd 100644 --- a/services/brig/test/integration/API/Provider.hs +++ b/services/brig/test/integration/API/Provider.hs @@ -48,7 +48,7 @@ import Data.Id hiding (client) import Data.Json.Util (toBase64Text) import Data.List1 (List1) import qualified Data.List1 as List1 -import Data.Misc (PlainTextPassword (..)) +import Data.Misc import Data.PEM import Data.Qualified import Data.Range @@ -244,7 +244,7 @@ testPasswordResetProvider :: DB.ClientState -> Brig -> Http () testPasswordResetProvider db brig = do prv <- randomProvider db brig let email = providerEmail prv - let newPw = PlainTextPassword "newsupersecret" + let newPw = plainTextPassword6Unsafe "newsupersecret" initiatePasswordResetProvider brig (PasswordReset email) !!! const 201 === statusCode -- password reset with same password fails. resetPw defProviderPassword email !!! const 409 === statusCode @@ -255,7 +255,7 @@ testPasswordResetProvider db brig = do loginProvider brig email newPw !!! const 200 === statusCode where - resetPw :: PlainTextPassword -> Email -> Http ResponseLBS + resetPw :: PlainTextPassword6 -> Email -> Http ResponseLBS resetPw newPw email = do -- Get the code directly from the DB gen <- Code.mkGen (Code.ForEmail email) @@ -282,7 +282,7 @@ testPasswordResetAfterEmailUpdateProvider db brig = do CompletePasswordReset (Code.codeKey vcodePw) (Code.codeValue vcodePw) - (PlainTextPassword "doesnotmatter") + (plainTextPassword6Unsafe "doesnotmatter") -- Activate the new email genNew <- Code.mkGen (Code.ForEmail newEmail) Just vcodeEm <- lookupCode db genNew Code.IdentityVerification @@ -296,8 +296,8 @@ testPasswordResetAfterEmailUpdateProvider db brig = do loginProvider brig origEmail defProviderPassword !!! const 403 === statusCode loginProvider brig newEmail defProviderPassword !!! const 200 === statusCode -- exercise the password change endpoint - let newPass = PlainTextPassword "newpass" - let pwChangeFail = PasswordChange (PlainTextPassword "notcorrect") newPass + let newPass = plainTextPassword6Unsafe "newpass" + let pwChangeFail = PasswordChange (plainTextPassword6Unsafe "notcorrect") newPass updateProviderPassword brig pid pwChangeFail !!! const 403 === statusCode let pwChange = PasswordChange defProviderPassword newPass updateProviderPassword brig pid pwChange !!! const 200 === statusCode @@ -1069,7 +1069,7 @@ activateProvider brig key val = loginProvider :: Brig -> Email -> - PlainTextPassword -> + PlainTextPassword6 -> Http ResponseLBS loginProvider brig email pw = post $ @@ -1145,7 +1145,7 @@ completePasswordResetProvider brig e = deleteProvider :: Brig -> ProviderId -> - PlainTextPassword -> + PlainTextPassword6 -> Http ResponseLBS deleteProvider brig pid pw = delete $ @@ -1268,7 +1268,7 @@ deleteService :: Brig -> ProviderId -> ServiceId -> - PlainTextPassword -> + PlainTextPassword6 -> Http ResponseLBS deleteService brig pid sid pw = delete $ @@ -1687,8 +1687,8 @@ defProviderSummary = "A short summary of the integration test provider" defProviderDescr :: Text defProviderDescr = "A long description of an integration test provider" -defProviderPassword :: PlainTextPassword -defProviderPassword = PlainTextPassword "password" +defProviderPassword :: PlainTextPassword6 +defProviderPassword = plainTextPassword6Unsafe "password" defServiceName :: Name defServiceName = Name "Test Service" diff --git a/services/brig/test/integration/API/Swagger.hs b/services/brig/test/integration/API/Swagger.hs new file mode 100644 index 0000000000..32b91c93d8 --- /dev/null +++ b/services/brig/test/integration/API/Swagger.hs @@ -0,0 +1,54 @@ +-- 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 API.Swagger (tests) where + +import Bilge +import Brig.Options +import Control.Lens +import Data.Aeson.Lens +import Data.String.Conversions +import Imports +import Test.Tasty +import Test.Tasty.HUnit +import Util + +tests :: Manager -> Opts -> Brig -> TestTree +tests p _opts brigNoImplicitVersion = + testGroup "version" $ + [ testGroup "public" $ + [ test p "GET /api/swagger.json" $ testSwaggerJson brigNoImplicitVersion "", + test p "GET /api/swagger-ui" $ testSwaggerUI brigNoImplicitVersion "", + test p "GET /v2/api/swagger.json" $ testSwaggerJson brigNoImplicitVersion "/v2", + test p "GET /v2/api/swagger-ui" $ testSwaggerUI brigNoImplicitVersion "/v2" + ], + testGroup "internal" $ + [ test p "GET /v2/api-internal/swagger-ui/brig" $ void (get (brigNoImplicitVersion . path "/v2/api-internal/swagger-ui/brig" . expect4xx)), + test p "GET /v2/api-internal/swagger-ui/gundeck" $ void (get (brigNoImplicitVersion . path "/v2/api-internal/swagger-ui/gundeck" . expect4xx)), + test p "GET /v2/i/status" $ void (get (brigNoImplicitVersion . path "/v2/i/status" . expect4xx)) + ] + ] + +testSwaggerJson :: Brig -> ByteString -> Http () +testSwaggerJson brig version = do + r <- get (brig . path (version <> "/api/swagger.json") . expect2xx) + liftIO $ assertBool "json body" (isJust $ ((^? _Object) <=< responseBody) r) + +testSwaggerUI :: Brig -> ByteString -> Http () +testSwaggerUI brig version = do + r <- get (brig . path (version <> "/api/swagger-ui") . expect2xx) + liftIO $ assertBool "HTML body" ("

" `isInfixOf` (cs . fromJust . responseBody $ r)) diff --git a/services/brig/test/integration/API/SystemSettings.hs b/services/brig/test/integration/API/SystemSettings.hs index ea4b11a3a0..bc656fac9f 100644 --- a/services/brig/test/integration/API/SystemSettings.hs +++ b/services/brig/test/integration/API/SystemSettings.hs @@ -22,7 +22,9 @@ import Bilge.Assert import Brig.Options import Control.Lens import qualified Data.ByteString.Char8 as BS +import Data.ByteString.Conversion (toByteString') import Data.Id +import Data.String.Conversions (cs) import Imports import Network.Wai.Test as WaiTest import Test.Tasty @@ -59,11 +61,8 @@ testGetSettings opts = liftIO $ do getSystemSettings :: WaiTest.Session SystemSettingsPublic getSystemSettings = responseJsonError - =<< get (path (BS.pack ("/" ++ latestVersion ++ "/system/settings/unauthorized"))) + =<< get (path (BS.pack ("/" ++ cs latestVersion ++ "/system/settings/unauthorized"))) Http () testGetSettingsInternal opts = liftIO $ do @@ -84,4 +83,7 @@ testGetSettingsInternal opts = liftIO $ do getSystemSettings :: UserId -> WaiTest.Session SystemSettings getSystemSettings uid = - responseJsonError =<< get (paths ["system", "settings"] . zUser uid) getTeams uid galley - liftIO $ assertBool "User not part of exactly one team" (length teams == 1) - let team = fromMaybe (error "No team??") $ listToMaybe teams + let tid = fromMaybe (error "No team??") $ userTeam usr + team <- Team.tdTeam <$> getTeam galley tid mem <- getTeamMember uid (team ^. teamId) galley liftIO $ assertBool "Member not part of the team" (uid == mem ^. Member.userId) -- Verify that the user cannot send invitations before activating their account @@ -565,9 +563,8 @@ testCreateTeamPreverified brig galley userJournalWatcher = do usr <- responseJsonError =<< register' email newTeam c brig getTeams uid galley - liftIO $ assertBool "User not part of exactly one team" (length teams == 1) - let team = fromMaybe (error "No team??") $ listToMaybe teams + let tid = fromMaybe (error "No team??") $ userTeam usr + team <- Team.tdTeam <$> getTeam galley tid mem <- getTeamMember uid (team ^. teamId) galley liftIO $ assertBool "Member not part of the team" (uid == mem ^. Member.userId) team2 <- getTeam galley (team ^. teamId) diff --git a/services/brig/test/integration/API/Team/Util.hs b/services/brig/test/integration/API/Team/Util.hs index b809b89c48..ff2f9c7ab4 100644 --- a/services/brig/test/integration/API/Team/Util.hs +++ b/services/brig/test/integration/API/Team/Util.hs @@ -274,20 +274,6 @@ deleteTeam g tid u = do !!! const 202 === statusCode -getTeams :: - (MonadIO m, MonadCatch m, MonadHttp m, HasCallStack) => - UserId -> - Galley -> - m TeamList -getTeams u galley = - responseJsonError - =<< get - ( galley - . paths ["teams"] - . zAuthAccess u "conn" - . expect2xx - ) - newTeam :: BindingNewTeam newTeam = BindingNewTeam $ newNewTeam (unsafeRange "teamName") DefaultIcon @@ -414,7 +400,7 @@ unsuspendTeam brig t = . paths ["i", "teams", toByteString' t, "unsuspend"] . contentJson -getTeam :: HasCallStack => Galley -> TeamId -> Http Team.TeamData +getTeam :: (HasCallStack, MonadIO m, MonadHttp m, HasCallStack, MonadCatch m) => Galley -> TeamId -> m Team.TeamData getTeam galley t = responseJsonError =<< get (galley . paths ["i", "teams", toByteString' t]) diff --git a/services/brig/test/integration/API/User/Account.hs b/services/brig/test/integration/API/User/Account.hs index 639686ba81..664a17d964 100644 --- a/services/brig/test/integration/API/User/Account.hs +++ b/services/brig/test/integration/API/User/Account.hs @@ -49,9 +49,11 @@ import Data.Domain import Data.Handle import Data.Id hiding (client) import Data.Json.Util (fromUTCTimeMillis) +import Data.LegalHold +import qualified Data.List.NonEmpty as NonEmpty import Data.List1 (singleton) import qualified Data.List1 as List1 -import Data.Misc (PlainTextPassword (..)) +import Data.Misc (plainTextPassword6Unsafe) import Data.Proxy import Data.Qualified import Data.Range @@ -130,13 +132,15 @@ tests _ at opts p b c ch g aws userJournalWatcher = test p "head /users/:uid - 404" $ testUserDoesNotExistUnqualified b, test p "head /users/:domain/:uid - 200" $ testUserExists b, test p "head /users/:domain/:uid - 404" $ testUserDoesNotExist b, - test p "post /list-users - 200" $ testMultipleUsers b, + test p "post /list-users@v3 - 200" $ testMultipleUsersV3 b, + test p "post /list-users - 200" $ testMultipleUsers opts b, test p "put /self - 200" $ testUserUpdate b c userJournalWatcher, test p "put /access/self/email - 2xx" $ testEmailUpdate b userJournalWatcher, test p "put /self/phone - 202" $ testPhoneUpdate b, test p "put /self/phone - 403" $ testPhoneUpdateBlacklisted b, test p "put /self/phone - 409" $ testPhoneUpdateConflict b, test p "head /self/password - 200/404" $ testPasswordSet b, + test p "put /self/password - 400" $ testPasswordSetInvalidPasswordLength b, test p "put /self/password - 200" $ testPasswordChange b, test p "put /self/locale - 200" $ testUserLocaleUpdate b userJournalWatcher, test p "post /activate/send - 200" $ testSendActivationCode opts b, @@ -759,8 +763,8 @@ testMultipleUsersUnqualified brig = do field :: FromJSON a => Key -> Value -> Maybe a field f u = u ^? key f >>= maybeFromJSON -testMultipleUsers :: Brig -> Http () -testMultipleUsers brig = do +testMultipleUsersV3 :: Brig -> Http () +testMultipleUsersV3 brig = do u1 <- randomUser brig u2 <- randomUser brig u3 <- createAnonUser "a" brig @@ -773,7 +777,8 @@ testMultipleUsers brig = do (Just $ userDisplayName u3, Nothing) ] post - ( brig + ( apiVersion "v3" + . brig . zUser (userId u1) . contentJson . path "list-users" @@ -790,6 +795,73 @@ testMultipleUsers brig = do field :: FromJSON a => Key -> Value -> Maybe a field f u = u ^? key f >>= maybeFromJSON +testMultipleUsers :: Opt.Opts -> Brig -> Http () +testMultipleUsers opts brig = do + u1 <- randomUser brig + u2 <- randomUser brig + u3 <- createAnonUser "a" brig + -- A remote user that can't be listed + u4 <- Qualified <$> randomId <*> pure (Domain "far-away.example.com") + -- A remote user that can be listed + let evenFurtherAway = Domain "even-further-away.example.com" + u5 <- Qualified <$> randomId <*> pure evenFurtherAway + let u5Profile = + UserProfile + { profileQualifiedId = u5, + profileName = Name "u5", + profilePict = Pict [], + profileAssets = [], + profileAccentId = ColourId 0, + profileDeleted = False, + profileService = Nothing, + profileHandle = Nothing, + profileExpire = Nothing, + profileTeam = Nothing, + profileEmail = Nothing, + profileLegalholdStatus = UserLegalHoldDisabled + } + users = [u1, u2, u3] + q = ListUsersByIds $ u5 : u4 : map userQualifiedId users + expected = + Set.fromList + [ (Just $ userDisplayName u1, Nothing :: Maybe Email), + (Just $ userDisplayName u2, Nothing), + (Just $ userDisplayName u3, Nothing), + (Just $ profileName u5Profile, profileEmail u5Profile) + ] + expectedFailed = Set.fromList [u4] + + let fedMockResponse req = do + -- Check that our allowed remote user is being asked for + if frTargetDomain req == evenFurtherAway + then -- Return the data for u5 + pure $ encode [u5Profile] + else -- Otherwise mock an unavailable federation server + throw $ MockErrorResponse Http.status500 "Down for maintenance" + -- Galley isn't needed, but this is what mock federators are available. + galleyHandler _ = error "not mocked" + (response, _rpcCalls, _galleyCalls) <- liftIO $ + withMockedFederatorAndGalley opts (Domain "example.com") fedMockResponse galleyHandler $ do + post + ( brig + . zUser (userId u1) + . contentJson + . path "list-users" + . body (RequestBodyLBS (Aeson.encode q)) + ) + + pure response !!! do + const 200 === statusCode + const (Just expected) === result + const (pure $ pure expectedFailed) === resultFailed + where + result r = + Set.fromList + . map (\u -> (pure $ profileName u, profileEmail u)) + . listUsersByIdFound + <$> responseJsonMaybe r + resultFailed r = fmap (Set.fromList . NonEmpty.toList) . listUsersByIdFailed <$> responseJsonMaybe r + testCreateUserAnonExpiry :: Brig -> Http () testCreateUserAnonExpiry b = do u1 <- randomUser b @@ -1096,6 +1168,28 @@ testPasswordSet brig = do [ "new_password" .= ("a_very_long_password" :: Text) ] +testPasswordSetInvalidPasswordLength :: Brig -> Http () +testPasswordSetInvalidPasswordLength brig = do + p <- randomPhone + let newUser = + RequestBodyLBS . encode $ + object + [ "name" .= ("Alice" :: Text), + "phone" .= fromPhone p + ] + rs <- + post (brig . path "/i/users" . contentJson . body newUser) + responseJsonMaybe rs + put (brig . path "/self/password" . contentJson . zUser uid . body shortPassword) + !!! const 400 === statusCode + where + shortPassword = + RequestBodyLBS . encode $ + object + [ "new_password" .= ("secret" :: Text) + ] + testPasswordChange :: Brig -> Http () testPasswordChange brig = do (uid, Just email) <- (userId &&& userEmail) <$> randomUser brig @@ -1115,7 +1209,7 @@ testPasswordChange brig = do put (brig . path "/self/password" . contentJson . zUser uid2 . body pwSet) !!! (const 403 === statusCode) where - newPass = PlainTextPassword "topsecret" + newPass = plainTextPassword6Unsafe "topsecret" pwChange = RequestBodyLBS . encode $ object @@ -1554,7 +1648,7 @@ testRestrictedUserCreation opts brig = do [ "name" .= Name "Alice", "email" .= fromEmail e, "email_code" .= ("123456" :: Text), - "password" .= PlainTextPassword "123123123" + "password" .= plainTextPassword6Unsafe "123123123" ] postUserRegister' regularUser brig !!! do const 403 === statusCode @@ -1564,7 +1658,7 @@ testRestrictedUserCreation opts brig = do object [ "name" .= Name "Alice", "email" .= fromEmail e, - "password" .= PlainTextPassword "123123123" + "password" .= plainTextPassword6Unsafe "123123123" ] postUserRegister' regularUserNotPreActivated brig !!! do const 403 === statusCode @@ -1576,7 +1670,7 @@ testRestrictedUserCreation opts brig = do "email" .= fromEmail e, "email_code" .= ("123456" :: Text), "team" .= object ["name" .= ("Alice team" :: Text), "icon" .= ("default" :: Text), "binding" .= True], - "password" .= PlainTextPassword "123123123" + "password" .= plainTextPassword6Unsafe "123123123" ] postUserRegister' teamCreator brig !!! do const 403 === statusCode diff --git a/services/brig/test/integration/API/User/Auth.hs b/services/brig/test/integration/API/User/Auth.hs index 21f4189ba1..607620bc96 100644 --- a/services/brig/test/integration/API/User/Auth.hs +++ b/services/brig/test/integration/API/User/Auth.hs @@ -33,9 +33,12 @@ import Bilge.Assert hiding (assert) import qualified Brig.Code as Code import qualified Brig.Options as Opts import Brig.Types.Intra +import Brig.User.Auth.Cookie (revokeAllCookies) import Brig.ZAuth (ZAuth, runZAuth) import qualified Brig.ZAuth as ZAuth +import Cassandra hiding (Value) import qualified Cassandra as DB +import Control.Arrow ((&&&)) import Control.Lens (set, (^.)) import Control.Monad.Catch (MonadCatch) import Control.Retry @@ -45,7 +48,7 @@ import Data.ByteString.Conversion import qualified Data.ByteString.Lazy as Lazy import Data.Handle (Handle (Handle)) import Data.Id -import Data.Misc (PlainTextPassword (..)) +import Data.Misc (PlainTextPassword6, plainTextPassword6, plainTextPassword6Unsafe) import Data.Proxy import Data.Qualified import Data.Range (unsafeRange) @@ -65,6 +68,7 @@ import qualified Test.Tasty.HUnit as HUnit import UnliftIO.Async hiding (wait) import Util import Wire.API.Conversation (Conversation (..)) +import Wire.API.Password (Password, mkSafePassword) import qualified Wire.API.Team.Feature as Public import Wire.API.User import qualified Wire.API.User as Public @@ -108,6 +112,7 @@ tests conf m z db b g n = test m "failure" (testLoginFailure b), test m "throttle" (testThrottleLogins conf b), test m "limit-retry" (testLimitRetries conf b), + test m "login with 6 character password" (testLoginWith6CharPassword b db), testGroup "sso-login" [ test m "email" (testEmailSsoLogin b), @@ -183,6 +188,34 @@ tests conf m z db b g n = ] ] +testLoginWith6CharPassword :: Brig -> DB.ClientState -> Http () +testLoginWith6CharPassword brig db = do + (uid, Just email) <- (userId &&& userEmail) <$> randomUser brig + checkLogin email defPassword 200 + let pw6 = plainTextPassword6Unsafe "123456" + writeDirectlyToDB uid pw6 + checkLogin email defPassword 403 + checkLogin email pw6 200 + where + checkLogin :: Email -> PlainTextPassword6 -> Int -> Http () + checkLogin email pw expectedStatusCode = + login + brig + (PasswordLogin (PasswordLoginData (LoginByEmail email) pw Nothing Nothing)) + PersistentCookie + !!! const expectedStatusCode === statusCode + -- Since 8 char passwords are required, when setting a password via the API, + -- we need to write this directly to the db, to be able to test this + writeDirectlyToDB :: UserId -> PlainTextPassword6 -> Http () + writeDirectlyToDB uid pw = + liftIO (runClient db (updatePassword uid pw >> revokeAllCookies uid)) + updatePassword :: MonadClient m => UserId -> PlainTextPassword6 -> m () + updatePassword u t = do + p <- liftIO $ mkSafePassword t + retry x5 $ write userPasswordUpdate (params LocalQuorum (p, u)) + userPasswordUpdate :: PrepQuery W (Password, UserId) () + userPasswordUpdate = "UPDATE user SET password = ? WHERE id = ?" + -------------------------------------------------------------------------------- -- ZAuth test environment for generating arbitrary tokens. @@ -382,7 +415,7 @@ testSendLoginCode brig = do object [ "name" .= ("Alice" :: Text), "phone" .= fromPhone p, - "password" .= ("secret" :: Text) + "password" .= ("topsecretdefaultpassword" :: Text) ] post (brig . path "/i/users" . contentJson . Http.body newUser) !!! const 201 === statusCode @@ -563,7 +596,7 @@ testLoginFailure :: Brig -> Http () testLoginFailure brig = do Just email <- userEmail <$> randomUser brig -- login with wrong password - let badpw = PlainTextPassword "wrongpassword" + let badpw = plainTextPassword6Unsafe "wrongpassword" login brig (PasswordLogin (PasswordLoginData (LoginByEmail email) badpw Nothing Nothing)) @@ -724,7 +757,7 @@ testWrongPasswordLegalHoldLogin :: Brig -> Galley -> Http () testWrongPasswordLegalHoldLogin brig galley = do alice <- prepareLegalHoldUser brig galley -- attempt a legalhold login with a wrong password - legalHoldLogin brig (LegalHoldLogin alice (Just (PlainTextPassword "wrong-password")) Nothing) PersistentCookie !!! do + legalHoldLogin brig (LegalHoldLogin alice (plainTextPassword6 "wrong-password") Nothing) PersistentCookie !!! do const 403 === statusCode const (Just "invalid-credentials") === errorLabel -- attempt a legalhold login with a no password @@ -1392,7 +1425,7 @@ testReauthentication b = do -- it's ok to not give a password in the request body, but if the user has a password set, -- response will be `forbidden`. - get (b . paths ["/i/users", toByteString' u, "reauthenticate"] . contentJson . payload (Just $ PlainTextPassword "123456")) !!! do + get (b . paths ["/i/users", toByteString' u, "reauthenticate"] . contentJson . payload (plainTextPassword6 "123456")) !!! do const 403 === statusCode const (Just "invalid-credentials") === errorLabel get (b . paths ["/i/users", toByteString' u, "reauthenticate"] . contentJson . payload (Just defPassword)) !!! do @@ -1469,7 +1502,7 @@ assertSaneAccessToken now uid tk = do errorLabel :: Response (Maybe Lazy.ByteString) -> Maybe Lazy.Text errorLabel = fmap Error.label . responseJsonMaybe -remJson :: PlainTextPassword -> Maybe [CookieLabel] -> Maybe [CookieId] -> Value +remJson :: PlainTextPassword6 -> Maybe [CookieLabel] -> Maybe [CookieId] -> Value remJson p l ids = object [ "password" .= p, diff --git a/services/brig/test/integration/API/User/Client.hs b/services/brig/test/integration/API/User/Client.hs index 87297e6bd8..b7bfeac716 100644 --- a/services/brig/test/integration/API/User/Client.hs +++ b/services/brig/test/integration/API/User/Client.hs @@ -38,6 +38,7 @@ import Control.Lens (at, preview, (?~), (^.), (^?)) import Data.Aeson hiding (json) import Data.Aeson.Lens import Data.ByteString.Conversion +import Data.Coerce (coerce) import Data.Default import Data.Domain (Domain (..)) import Data.Id hiding (client) @@ -88,9 +89,12 @@ tests _cl _at opts p db b c g = test p "get /users//:uid/prekeys/:client - 200" $ testGetClientPrekeyQualified b opts, test p "post /users/prekeys" $ testMultiUserGetPrekeys b, test p "post /users/list-prekeys" $ testMultiUserGetPrekeysQualified b opts, + test p "post /users/list-prekeys@v4" $ testMultiUserGetPrekeysQualifiedV4 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 /users/list-prekeys - clients without prekeys" $ testClientsWithoutPrekeys b c db opts, + test p "post /users/list-prekeys@v4 - clients without prekeys" $ testClientsWithoutPrekeysV4 b c db opts, + test p "post /users/list-prekeys@v4 - clients without prekeys fail to list" $ testClientsWithoutPrekeysFailToListV4 b c db opts, 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 @@ -441,7 +445,8 @@ testClientsWithoutPrekeys brig cannon db opts = do const 200 === statusCode post - ( brig + ( apiVersion "v3" + . brig . paths ["users", "list-prekeys"] . contentJson . body (RequestBodyLBS $ encode userClients) @@ -471,7 +476,8 @@ testClientsWithoutPrekeys brig cannon db opts = do @?= Just (clientId c11) post - ( brig + ( apiVersion "v3" + . brig . paths ["users", "list-prekeys"] . contentJson . body (RequestBodyLBS $ encode userClients) @@ -498,6 +504,187 @@ testClientsWithoutPrekeys brig cannon db opts = do Map.singleton u $ Map.fromList xs +testClientsWithoutPrekeysV4 :: Brig -> Cannon -> DB.ClientState -> Opt.Opts -> Http () +testClientsWithoutPrekeysV4 brig cannon db opts = do + uid1 <- userId <$> randomUser brig + let (pk11, lk11) = (somePrekeys !! 0, someLastPrekeys !! 0) + c11 <- responseJsonError =<< addClient brig uid1 (defNewClient PermanentClientType [pk11] lk11) + let (pk12, lk12) = (somePrekeys !! 1, someLastPrekeys !! 1) + c12 <- responseJsonError =<< addClient brig uid1 (defNewClient PermanentClientType [pk12] lk12) + + -- Simulating loss of all prekeys from c11 (due e.g. DB problems, business + -- logic prevents this from happening) + let removeClientKeys :: DB.PrepQuery DB.W (UserId, ClientId) () + removeClientKeys = "DELETE FROM prekeys where user = ? and client = ?" + liftIO $ + DB.runClient db $ + DB.write removeClientKeys (DB.params DB.LocalQuorum (uid1, clientId c11)) + + uid2 <- userId <$> randomUser brig + + let domain = opts ^. Opt.optionSettings & Opt.setFederationDomain + + let userClients = + QualifiedUserClients $ + Map.singleton domain $ + Map.singleton uid1 $ + Set.fromList [clientId c11, clientId c12] + + WS.bracketR cannon uid1 $ \ws -> do + getClient brig uid1 (clientId c11) !!! do + const 200 === statusCode + + post + ( brig + . paths ["users", "list-prekeys"] + . contentJson + . body (RequestBodyLBS $ encode userClients) + . zUser uid2 + ) + !!! do + const 200 === statusCode + const + ( Right $ + expectedClientMapClientsWithoutPrekeys + domain + uid1 + [ (clientId c11, Nothing), + (clientId c12, Just pk12) + ] + Nothing + ) + === responseJsonEither + + getClient brig uid1 (clientId c11) !!! do + const 404 === statusCode + + liftIO . WS.assertMatch (5 # Second) ws $ \n -> do + let ob = Object $ List1.head (ntfPayload n) + ob ^? key "type" . _String + @?= Just "user.client-remove" + fmap ClientId (ob ^? key "client" . key "id" . _String) + @?= Just (clientId c11) + + post + ( brig + . paths ["users", "list-prekeys"] + . contentJson + . body (RequestBodyLBS $ encode userClients) + . zUser uid2 + ) + !!! do + const 200 === statusCode + const + ( Right $ + expectedClientMapClientsWithoutPrekeys + domain + uid1 + [ (clientId c11, Nothing), + (clientId c12, Just (unpackLastPrekey lk12)) + ] + Nothing + ) + === responseJsonEither + +expectedClientMapClientsWithoutPrekeys :: Domain -> UserId -> [(ClientId, Maybe Prekey)] -> Maybe [Qualified UserId] -> QualifiedUserClientPrekeyMapV4 +expectedClientMapClientsWithoutPrekeys domain u xs failed = + QualifiedUserClientPrekeyMapV4 + { qualifiedUserClientPrekeys = + coerce $ + mkQualifiedUserClientPrekeyMap $ + Map.singleton domain $ + mkUserClientPrekeyMap $ + Map.singleton u $ + Map.fromList xs, + failedToList = failed + } + +testClientsWithoutPrekeysFailToListV4 :: Brig -> Cannon -> DB.ClientState -> Opt.Opts -> Http () +testClientsWithoutPrekeysFailToListV4 brig cannon db opts = do + uid1 <- userId <$> randomUser brig + let (pk11, lk11) = (somePrekeys !! 0, someLastPrekeys !! 0) + c11 <- responseJsonError =<< addClient brig uid1 (defNewClient PermanentClientType [pk11] lk11) + let (pk12, lk12) = (somePrekeys !! 1, someLastPrekeys !! 1) + c12 <- responseJsonError =<< addClient brig uid1 (defNewClient PermanentClientType [pk12] lk12) + + -- Simulating loss of all prekeys from c11 (due e.g. DB problems, business + -- logic prevents this from happening) + let removeClientKeys :: DB.PrepQuery DB.W (UserId, ClientId) () + removeClientKeys = "DELETE FROM prekeys where user = ? and client = ?" + liftIO $ + DB.runClient db $ + DB.write removeClientKeys (DB.params DB.LocalQuorum (uid1, clientId c11)) + + uid2 <- fakeRemoteUser + + let domain = opts ^. Opt.optionSettings & Opt.setFederationDomain + + let userClients1 = + QualifiedUserClients $ + Map.singleton domain $ + Map.singleton uid1 $ + Set.fromList [clientId c11, clientId c12] + userClients2 = + QualifiedUserClients $ + Map.fromList + [ ( qDomain uid2, + Map.singleton (qUnqualified uid2) mempty + ) + ] + + WS.bracketR cannon uid1 $ \ws -> do + getClient brig uid1 (clientId c11) !!! do + const 200 === statusCode + + post + ( brig + . paths ["users", "list-prekeys"] + . contentJson + . body (RequestBodyLBS $ encode userClients1) + . zUser (qUnqualified uid2) + ) + !!! do + const 200 === statusCode + const + ( Right $ + expectedClientMapClientsWithoutPrekeys + domain + uid1 + [ (clientId c11, Nothing), + (clientId c12, Just pk12) + ] + Nothing + ) + === responseJsonEither + + getClient brig uid1 (clientId c11) !!! do + const 404 === statusCode + + liftIO . WS.assertMatch (5 # Second) ws $ \n -> do + let ob = Object $ List1.head (ntfPayload n) + ob ^? key "type" . _String + @?= Just "user.client-remove" + fmap ClientId (ob ^? key "client" . key "id" . _String) + @?= Just (clientId c11) + + post + ( brig + . paths ["users", "list-prekeys"] + . contentJson + . body (RequestBodyLBS $ encode userClients2) + . zUser (qUnqualified uid2) + ) + !!! do + const 200 === statusCode + const + ( Right $ + QualifiedUserClientPrekeyMapV4 + { qualifiedUserClientPrekeys = QualifiedUserClientMap Map.empty, + failedToList = pure [uid2] + } + ) + === responseJsonEither + testListClientsBulkV2 :: Opt.Opts -> Brig -> Http () testListClientsBulkV2 opts brig = do uid1 <- userId <$> randomUser brig @@ -656,6 +843,45 @@ testMultiUserGetPrekeysQualified brig opts = do xs <&> \(uid', c, _lpk, cpk) -> (uid', Map.singleton (clientId c) (Just (prekeyData cpk))) + post + ( apiVersion "v2" + . brig + . paths ["users", "list-prekeys"] + . contentJson + . body (RequestBodyLBS $ encode userClients) + . zUser uid + ) + !!! do + const 200 === statusCode + const (Right $ expectedUserClientMap) === responseJsonEither + +testMultiUserGetPrekeysQualifiedV4 :: Brig -> Opt.Opts -> Http () +testMultiUserGetPrekeysQualifiedV4 brig opts = do + let domain = opts ^. Opt.optionSettings & Opt.setFederationDomain + + xs <- generateClients 3 brig + let userClients = + QualifiedUserClients $ + Map.singleton domain $ + Map.fromList $ + xs <&> \(uid, c, _lpk, _cpk) -> + (uid, Set.fromList [clientId c]) + + uid <- userId <$> randomUser brig + + let expectedUserClientMap = + QualifiedUserClientPrekeyMapV4 + { qualifiedUserClientPrekeys = + coerce $ + mkQualifiedUserClientPrekeyMap $ + Map.singleton domain $ + mkUserClientPrekeyMap $ + Map.fromList $ + xs <&> \(uid', c, _lpk, cpk) -> + (uid', Map.singleton (clientId c) (Just (prekeyData cpk))), + failedToList = Nothing + } + post ( brig . paths ["users", "list-prekeys"] diff --git a/services/brig/test/integration/API/User/PasswordReset.hs b/services/brig/test/integration/API/User/PasswordReset.hs index 05554b4ac9..193de49bb6 100644 --- a/services/brig/test/integration/API/User/PasswordReset.hs +++ b/services/brig/test/integration/API/User/PasswordReset.hs @@ -27,7 +27,9 @@ import Bilge hiding (accept, timeout) import Bilge.Assert import qualified Brig.Options as Opt import qualified Cassandra as DB -import Data.Misc (PlainTextPassword (..)) +import Data.Aeson as A +import qualified Data.Aeson.KeyMap as KeyMap +import Data.Misc import Imports import Test.Tasty hiding (Timeout) import Util @@ -48,7 +50,8 @@ tests cs _cl _at _conf p b _c _g = testGroup "password-reset" [ test p "post /password-reset[/complete] - 201[/200]" $ testPasswordReset b cs, - test p "post /password-reset after put /access/self/email - 400" $ testPasswordResetAfterEmailUpdate b cs + test p "post /password-reset after put /access/self/email - 400" $ testPasswordResetAfterEmailUpdate b cs, + test p "post /password-reset/complete - password too short - 400" $ testPasswordResetInvalidPasswordLength b cs ] testPasswordReset :: Brig -> DB.ClientState -> Http () @@ -57,7 +60,7 @@ testPasswordReset brig cs = do let Just email = userEmail u let uid = userId u -- initiate reset - let newpw = PlainTextPassword "newsecret" + let newpw = plainTextPassword8Unsafe "newsecret" do initiatePasswordReset brig email !!! const 201 === statusCode passwordResetData <- preparePasswordReset brig cs email uid newpw @@ -67,7 +70,7 @@ testPasswordReset brig cs = do !!! const 403 === statusCode login brig - (PasswordLogin (PasswordLoginData (LoginByEmail email) newpw Nothing Nothing)) + (PasswordLogin (PasswordLoginData (LoginByEmail email) (plainTextPassword8To6 newpw) Nothing Nothing)) PersistentCookie !!! const 200 === statusCode -- reset password again to the same new password, get 400 "must be different" @@ -84,9 +87,35 @@ testPasswordResetAfterEmailUpdate brig cs = do eml <- randomEmail initiateEmailUpdateLogin brig eml (emailLogin email defPassword Nothing) uid !!! const 202 === statusCode initiatePasswordReset brig email !!! const 201 === statusCode - passwordResetData <- preparePasswordReset brig cs email uid (PlainTextPassword "newsecret") + passwordResetData <- preparePasswordReset brig cs email uid (plainTextPassword8Unsafe "newsecret") -- activate new email activateEmail brig eml checkEmail brig uid eml -- attempting to complete password reset should fail completePasswordReset brig passwordResetData !!! const 400 === statusCode + +testPasswordResetInvalidPasswordLength :: Brig -> DB.ClientState -> Http () +testPasswordResetInvalidPasswordLength brig cs = do + u <- randomUser brig + let Just email = userEmail u + let uid = userId u + -- for convenience, we create a valid password first that we replace with an invalid one in the JSON later + let newpw = plainTextPassword8Unsafe "newsecret" + initiatePasswordReset brig email !!! const 201 === statusCode + passwordResetData <- preparePasswordReset brig cs email uid newpw + let shortPassword = String "123456" + let reqBody = toJSON passwordResetData & addJsonKey "password" shortPassword + postCompletePasswordReset reqBody !!! const 400 === statusCode + where + addJsonKey :: Key -> Value -> Value -> Object + addJsonKey key val (Object xs) = KeyMap.insert key val xs + addJsonKey _ _ _ = error "invalid JSON object" + + postCompletePasswordReset :: Object -> MonadHttp m => m ResponseLBS + postCompletePasswordReset bdy = + post + ( brig + . path "/password-reset/complete" + . contentJson + . body (RequestBodyLBS (encode bdy)) + ) diff --git a/services/brig/test/integration/API/User/Util.hs b/services/brig/test/integration/API/User/Util.hs index 58fcc8786e..003bff456e 100644 --- a/services/brig/test/integration/API/User/Util.hs +++ b/services/brig/test/integration/API/User/Util.hs @@ -44,7 +44,7 @@ import Data.Handle (Handle (Handle)) import Data.Id hiding (client) import Data.Kind import qualified Data.List1 as List1 -import Data.Misc (PlainTextPassword (..)) +import Data.Misc import Data.Qualified import Data.Range (unsafeRange) import qualified Data.Text.Ascii as Ascii @@ -210,7 +210,7 @@ preparePasswordReset :: DB.ClientState -> Email -> UserId -> - PlainTextPassword -> + PlainTextPassword8 -> m CompletePasswordReset preparePasswordReset brig cs email uid newpw = do let qry = queryItem "email" (toByteString' email) diff --git a/services/brig/test/integration/API/UserPendingActivation.hs b/services/brig/test/integration/API/UserPendingActivation.hs index b4a9a82a4b..87c822f35b 100644 --- a/services/brig/test/integration/API/UserPendingActivation.hs +++ b/services/brig/test/integration/API/UserPendingActivation.hs @@ -22,7 +22,7 @@ module API.UserPendingActivation where -import API.Team.Util (getTeams) +import API.Team.Util (getTeam) import Bilge hiding (query) import Bilge.Assert (( Brig -> Galley -> m (UserId, TeamId) +createUserWithTeamDisableSSO :: (HasCallStack, MonadCatch m, MonadHttp m, MonadIO m) => Brig -> Galley -> m (UserId, TeamId) createUserWithTeamDisableSSO brg gly = do e <- randomEmail n <- UUID.toString <$> liftIO UUID.nextRandom @@ -166,7 +167,7 @@ createUserWithTeamDisableSSO brg gly = do ] bdy <- selfUser . responseJsonUnsafe <$> post (brg . path "/i/users" . contentJson . body p) let (uid, Just tid) = (userId bdy, userTeam bdy) - (team : _) <- (^. teamListTeams) <$> getTeams uid gly + team <- Team.tdTeam <$> getTeam gly tid () <- Control.Exception.assert {- "Team ID in registration and team table do not match" -} (tid == team ^. teamId) $ pure () diff --git a/services/brig/test/integration/API/Version.hs b/services/brig/test/integration/API/Version.hs index 07e52638ad..1315d356ec 100644 --- a/services/brig/test/integration/API/Version.hs +++ b/services/brig/test/integration/API/Version.hs @@ -53,7 +53,7 @@ testVersion brig = do =<< get (brig . path "/api-version") vinfoSupported vinfo) @?= supportedVersions \\ developmentVersions testVersionV1 :: Brig -> Http () testVersionV1 brig = do @@ -62,7 +62,7 @@ testVersionV1 brig = do =<< get (apiVersion "v1" . brig . path "api-version") vinfoSupported vinfo) @?= supportedVersions \\ developmentVersions testDevVersion :: Opts -> Brig -> Http () testDevVersion opts brig = withSettingsOverrides @@ -73,7 +73,7 @@ testDevVersion opts brig = withSettingsOverrides =<< get (brig . path "/api-version") vinfoSupported vinfo) @?= supportedVersions testUnsupportedVersion :: Brig -> Http () testUnsupportedVersion brig = do @@ -127,7 +127,7 @@ testDisabledVersionIsUnsupported opts brig = do testVersionDisabledSupportedVersion :: Opts -> Brig -> Http () testVersionDisabledSupportedVersion opts brig = do vinfo <- getVersionInfo brig - liftIO $ filter (== V2) (vinfoSupported vinfo) @?= [V2] + liftIO $ filter (== VersionNumber V2) (vinfoSupported vinfo) @?= [VersionNumber V2] disabledVersionIsNotAdvertised opts brig V2 testVersionDisabledDevelopmentVersion :: Opts -> Brig -> Http () @@ -135,7 +135,7 @@ testVersionDisabledDevelopmentVersion opts brig = do vinfo <- getVersionInfo brig for_ (listToMaybe (vinfoDevelopment vinfo)) $ \devVersion -> do liftIO $ filter (== devVersion) (vinfoDevelopment vinfo) @?= [devVersion] - disabledVersionIsNotAdvertised opts brig devVersion + disabledVersionIsNotAdvertised opts brig (fromVersionNumber devVersion) disabledVersionIsNotAdvertised :: Opts -> Brig -> Version -> Http () disabledVersionIsNotAdvertised opts brig version = @@ -147,8 +147,8 @@ disabledVersionIsNotAdvertised opts brig version = ) $ do vinfo <- getVersionInfo brig - liftIO $ filter (== version) (vinfoSupported vinfo) @?= [] - liftIO $ filter (== version) (vinfoDevelopment vinfo) @?= [] + liftIO $ filter (== VersionNumber version) (vinfoSupported vinfo) @?= [] + liftIO $ filter (== VersionNumber version) (vinfoDevelopment vinfo) @?= [] getVersionInfo :: (MonadIO m, MonadCatch m, MonadHttp m, HasCallStack) => diff --git a/services/brig/test/integration/Federation/End2end.hs b/services/brig/test/integration/Federation/End2end.hs index e52238aeca..4ac7f09616 100644 --- a/services/brig/test/integration/Federation/End2end.hs +++ b/services/brig/test/integration/Federation/End2end.hs @@ -170,7 +170,8 @@ testGetUsersById brig1 brig2 = do q = ListUsersByIds (map userQualifiedId users) expected = sort (map userQualifiedId users) post - ( brig1 + ( apiVersion "v3" + . brig1 . path "list-users" . zUser (userId self) . json q @@ -248,7 +249,8 @@ testClaimMultiPrekeyBundleSuccess brig1 brig2 = do mkQualifiedUserClientPrekeyMap . fmap mkUserClientPrekeyMap . qmap $ [mkClientMap <$> c1, mkClientMap <$> c2] post - ( brig1 + ( apiVersion "v3" + . brig1 . zUser (qUnqualified (fst c1)) . paths ["users", "list-prekeys"] . body (RequestBodyLBS (Aeson.encode uc)) diff --git a/services/brig/test/integration/Main.hs b/services/brig/test/integration/Main.hs index 90b64986f4..dee2c47caa 100644 --- a/services/brig/test/integration/Main.hs +++ b/services/brig/test/integration/Main.hs @@ -25,9 +25,11 @@ import qualified API.Federation import qualified API.Internal import qualified API.MLS as MLS import qualified API.Metrics as Metrics +import qualified API.OAuth import qualified API.Provider as Provider import qualified API.Search as Search import qualified API.Settings as Settings +import qualified API.Swagger import qualified API.SystemSettings as SystemSettings import qualified API.Team as Team import qualified API.TeamUserSearch as TeamUserSearch @@ -52,6 +54,7 @@ import Imports hiding (local) import qualified Index.Create import qualified Network.HTTP.Client as HTTP import Network.HTTP.Client.TLS (tlsManagerSettings) +import Network.URI (pathSegments) import Network.Wai.Utilities.Server (compile) import OpenSSL (withOpenSSL) import Options.Applicative hiding (action) @@ -112,6 +115,7 @@ instance FromJSON Config runTests :: Config -> Opts.Opts -> [String] -> IO () runTests iConf brigOpts otherArgs = do let b = mkVersionedRequest $ brig iConf + brigNoImplicitVersion = mkRequest $ brig iConf c = mkVersionedRequest $ cannon iConf gd = mkVersionedRequest $ gundeck iConf ch = mkVersionedRequest $ cargohold iConf @@ -158,7 +162,9 @@ runTests iConf brigOpts otherArgs = do let smtp = SMTP.tests mg lg versionApi = API.Version.tests mg brigOpts b + swaggerApi = API.Swagger.tests mg brigOpts brigNoImplicitVersion mlsApi = MLS.tests mg b brigOpts + oauthAPI = API.OAuth.tests mg db b n brigOpts withArgs otherArgs . defaultMain $ testGroup @@ -182,17 +188,25 @@ runTests iConf brigOpts otherArgs = do federationEndpoints, internalApi, versionApi, + swaggerApi, mlsApi, - smtp + smtp, + oauthAPI ] <> [federationEnd2End | includeFederationTests] where mkRequest (Endpoint h p) = host (encodeUtf8 h) . port p - mkVersionedRequest endpoint = addPrefix . mkRequest endpoint + mkVersionedRequest endpoint = maybeAddPrefix . mkRequest endpoint + + maybeAddPrefix :: Request -> Request + maybeAddPrefix r = case pathSegments $ getUri r of + ("i" : _) -> r + ("api-internal" : _) -> r + _ -> addPrefix r addPrefix :: Request -> Request - addPrefix r = r {HTTP.path = "v" <> toHeader latestVersion <> "/" <> removeSlash (HTTP.path r)} + addPrefix r = r {HTTP.path = toHeader latestVersion <> "/" <> removeSlash (HTTP.path r)} where removeSlash s = case B8.uncons s of Just ('/', s') -> s' diff --git a/services/brig/test/integration/Util.hs b/services/brig/test/integration/Util.hs index 07b82f458c..dd8368dba2 100644 --- a/services/brig/test/integration/Util.hs +++ b/services/brig/test/integration/Util.hs @@ -58,7 +58,7 @@ import Data.Handle (Handle (..)) import Data.Id import Data.List1 (List1) import qualified Data.List1 as List1 -import Data.Misc (PlainTextPassword (..)) +import Data.Misc import Data.Proxy import Data.Qualified hiding (isLocal) import Data.Range @@ -442,7 +442,7 @@ postUserRegister' :: MonadHttp m => Object -> Brig -> m ResponseLBS postUserRegister' payload brig = do post (brig . path "/register" . contentJson . body (RequestBodyLBS $ encode payload)) -deleteUser :: (MonadHttp m, HasCallStack) => UserId -> Maybe PlainTextPassword -> Brig -> m ResponseLBS +deleteUser :: (MonadHttp m, HasCallStack) => UserId -> Maybe PlainTextPassword6 -> Brig -> m ResponseLBS deleteUser u p brig = delete $ brig @@ -906,7 +906,7 @@ updatePhone brig uid phn = do defEmailLogin :: Email -> Login defEmailLogin e = emailLogin e defPassword (Just defCookieLabel) -emailLogin :: Email -> PlainTextPassword -> Maybe CookieLabel -> Login +emailLogin :: Email -> PlainTextPassword6 -> Maybe CookieLabel -> Login emailLogin e pw cl = PasswordLogin (PasswordLoginData (LoginByEmail e) pw cl Nothing) somePrekeys :: [Prekey] @@ -973,14 +973,14 @@ someLastPrekeys = lastPrekey "pQABARn//wKhAFggQeUPM119c+6zRsEupA8zshTfrZiLpXx1Ji0UMMumq9IDoQChAFgglacihnqg/YQJHkuHNFU7QD6Pb3KN4FnubaCF2EVOgRkE9g==" ] -defPassword :: PlainTextPassword -defPassword = PlainTextPassword defPasswordText +defPassword :: PlainTextPassword6 +defPassword = plainTextPassword6Unsafe defPasswordText defPasswordText :: Text -defPasswordText = "secret" +defPasswordText = "topsecretdefaultpassword" -defWrongPassword :: PlainTextPassword -defWrongPassword = PlainTextPassword "not secret" +defWrongPassword :: PlainTextPassword6 +defWrongPassword = plainTextPassword6Unsafe "not secret" defCookieLabel :: CookieLabel defCookieLabel = CookieLabel "auth" diff --git a/services/brig/test/resources/oauth/ed25519.jwk b/services/brig/test/resources/oauth/ed25519.jwk new file mode 100644 index 0000000000..c00a8270aa --- /dev/null +++ b/services/brig/test/resources/oauth/ed25519.jwk @@ -0,0 +1 @@ +{"kty":"OKP","crv":"Ed25519","x":"mhP-NgFw3ifIXGZqJVB0kemt9L3BtD5P8q4Gah4Iklc","d":"R8-pV2-sPN7dykV8HFJ73S64F3kMHTNnJiSN8UdWk_o"} diff --git a/services/cannon/cannon.cabal b/services/cannon/cannon.cabal index 2c76e00e2c..06aebd4d49 100644 --- a/services/cannon/cannon.cabal +++ b/services/cannon/cannon.cabal @@ -42,6 +42,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -53,6 +54,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -136,6 +138,7 @@ executable cannon DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -147,6 +150,7 @@ executable cannon MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -202,6 +206,7 @@ test-suite cannon-tests DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -213,6 +218,7 @@ test-suite cannon-tests MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -276,6 +282,7 @@ benchmark cannon-bench DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -287,6 +294,7 @@ benchmark cannon-bench MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/services/cargohold/cargohold.cabal b/services/cargohold/cargohold.cabal index c5fa83a620..11d23f486b 100644 --- a/services/cargohold/cargohold.cabal +++ b/services/cargohold/cargohold.cabal @@ -49,6 +49,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -60,6 +61,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -155,6 +157,7 @@ executable cargohold DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -166,6 +169,7 @@ executable cargohold MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -241,6 +245,7 @@ executable cargohold-integration DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -252,6 +257,7 @@ executable cargohold-integration MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/services/cargohold/src/CargoHold/Options.hs b/services/cargohold/src/CargoHold/Options.hs index c6c7076e99..e4bc760c15 100644 --- a/services/cargohold/src/CargoHold/Options.hs +++ b/services/cargohold/src/CargoHold/Options.hs @@ -130,7 +130,7 @@ data Settings = Settings -- It should also match the SRV DNS records under which other wire-server installations can find this backend: -- _wire-server-federator._tcp. -- Once set, DO NOT change it: if you do, existing users may have a broken experience and/or stop working - -- Remember to keep it the same in Galley and in Brig. + -- Remember to keep it the same in all services. -- This is referred to as the 'backend domain' in the public documentation; See -- https://docs.wire.com/how-to/install/configure-federation.html#choose-a-backend-domain-name _setFederationDomain :: !Domain, diff --git a/services/cargohold/test/integration/Metrics.hs b/services/cargohold/test/integration/Metrics.hs index 33ae1e92bd..0ffbeeab63 100644 --- a/services/cargohold/test/integration/Metrics.hs +++ b/services/cargohold/test/integration/Metrics.hs @@ -31,7 +31,7 @@ tests s = testGroup "Metrics" [test s "prometheus" testPrometheusMetrics] testPrometheusMetrics :: TestM () testPrometheusMetrics = do - cargohold <- viewCargohold + cargohold <- viewUnversionedCargohold get (cargohold . path "/i/metrics") !!! do const 200 === statusCode -- Should contain the request duration metric in its output diff --git a/services/cargohold/test/integration/TestSetup.hs b/services/cargohold/test/integration/TestSetup.hs index 7b494caa43..759c760a69 100644 --- a/services/cargohold/test/integration/TestSetup.hs +++ b/services/cargohold/test/integration/TestSetup.hs @@ -115,7 +115,7 @@ unversioned r = viewCargohold :: TestM Cargohold viewCargohold = fmap - (apiVersion ("v" <> toHeader latestVersion) .) + (apiVersion (toHeader latestVersion) .) viewUnversionedCargohold where latestVersion :: Version diff --git a/services/federator/federator.cabal b/services/federator/federator.cabal index c72fbcb7a9..f0c792768c 100644 --- a/services/federator/federator.cabal +++ b/services/federator/federator.cabal @@ -66,6 +66,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -77,6 +78,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -177,6 +179,7 @@ executable federator DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -188,6 +191,7 @@ executable federator MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -242,6 +246,7 @@ executable federator-integration DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -253,6 +258,7 @@ executable federator-integration MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -376,6 +382,7 @@ test-suite federator-tests DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -387,6 +394,7 @@ test-suite federator-tests MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/services/federator/test/integration/Test/Federator/Util.hs b/services/federator/test/integration/Test/Federator/Util.hs index e520abadaa..b7c8fea9cb 100644 --- a/services/federator/test/integration/Test/Federator/Util.hs +++ b/services/federator/test/integration/Test/Federator/Util.hs @@ -311,8 +311,8 @@ randomPhone = liftIO $ do let phone = parsePhone . Text.pack $ "+0" ++ concat nrs pure $ fromMaybe (error "Invalid random phone#") phone -defPassword :: PlainTextPassword -defPassword = PlainTextPassword "secret" +defPassword :: PlainTextPassword6 +defPassword = plainTextPassword6Unsafe "topsecretdefaultpassword" defCookieLabel :: CookieLabel defCookieLabel = CookieLabel "auth" diff --git a/services/federator/test/unit/Test/Federator/Remote.hs b/services/federator/test/unit/Test/Federator/Remote.hs index 6222516069..54dd584d25 100644 --- a/services/federator/test/unit/Test/Federator/Remote.hs +++ b/services/federator/test/unit/Test/Federator/Remote.hs @@ -39,6 +39,7 @@ import Test.Federator.Options (defRunSettings) import Test.Federator.Util import Test.Tasty import Test.Tasty.HUnit +import Test.Tasty.Pending (flakyTestCase) import Wire.API.Federation.Component import Wire.API.Federation.Error import Wire.Network.DNS.SRV (SrvTarget (SrvTarget)) @@ -101,11 +102,11 @@ testValidatesCertificateSuccess :: TestTree testValidatesCertificateSuccess = testGroup "can get response with valid certificate" - [ testCase "when hostname=localhost and certificate-for=localhost" $ + [ flakyTestCase "when hostname=localhost and certificate-for=localhost" $ withMockServer certForLocalhost $ \port -> do tlsSettings <- mkTLSSettingsOrThrow settings runCodensity (mkTestCall tlsSettings "localhost" port) assertNoRemoteError, - testCase "when hostname=localhost. and certificate-for=localhost" $ + flakyTestCase "when hostname=localhost. and certificate-for=localhost" $ withMockServer certForLocalhost $ \port -> do tlsSettings <- mkTLSSettingsOrThrow settings runCodensity (mkTestCall tlsSettings "localhost." port) assertNoRemoteError, diff --git a/services/galley/default.nix b/services/galley/default.nix index 5f5ac943ce..898d305101 100644 --- a/services/galley/default.nix +++ b/services/galley/default.nix @@ -63,6 +63,7 @@ , metrics-wai , mtl , network +, network-uri , optparse-applicative , pem , polysemy @@ -117,6 +118,7 @@ , unordered-containers , uri-bytestring , uuid +, uuid-types , vector , wai , wai-extra @@ -295,6 +297,7 @@ mkDerivation { metrics-wai mtl network + network-uri optparse-applicative pem process @@ -337,6 +340,7 @@ mkDerivation { unordered-containers uri-bytestring uuid + uuid-types vector wai wai-extra @@ -374,6 +378,7 @@ mkDerivation { tasty-quickcheck transformers types-common + uuid-types wai wai-predicates wire-api diff --git a/services/galley/galley.cabal b/services/galley/galley.cabal index ac1d204da6..1a5fa0a2e2 100644 --- a/services/galley/galley.cabal +++ b/services/galley/galley.cabal @@ -154,6 +154,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -165,6 +166,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -309,6 +311,7 @@ executable galley DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -320,6 +323,7 @@ executable galley MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -406,6 +410,7 @@ executable galley-integration DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -417,6 +422,7 @@ executable galley-integration MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -490,6 +496,7 @@ executable galley-integration , metrics-wai , mtl , network + , network-uri , optparse-applicative , pem , process @@ -532,6 +539,7 @@ executable galley-integration , unordered-containers , uri-bytestring , uuid + , uuid-types , vector , wai , wai-extra @@ -571,6 +579,7 @@ executable galley-migrate-data DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -582,6 +591,7 @@ executable galley-migrate-data MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -703,6 +713,7 @@ executable galley-schema V77_MLSGroupMemberClient V78_TeamFeatureOutlookCalIntegration V79_TeamFeatureMlsE2EId + V80_AddConversationCodePassword hs-source-dirs: schema/src default-extensions: @@ -718,6 +729,7 @@ executable galley-schema DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -729,6 +741,7 @@ executable galley-schema MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -800,6 +813,7 @@ test-suite galley-tests DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -811,6 +825,7 @@ test-suite galley-tests MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -859,6 +874,7 @@ test-suite galley-tests , tasty-quickcheck , transformers , types-common + , uuid-types , wai , wai-predicates , wire-api diff --git a/services/galley/schema/src/Main.hs b/services/galley/schema/src/Main.hs index 5f493498f8..0b4aae92aa 100644 --- a/services/galley/schema/src/Main.hs +++ b/services/galley/schema/src/Main.hs @@ -82,6 +82,7 @@ import qualified V76_ProposalOrigin import qualified V77_MLSGroupMemberClient import qualified V78_TeamFeatureOutlookCalIntegration import qualified V79_TeamFeatureMlsE2EId +import qualified V80_AddConversationCodePassword main :: IO () main = do @@ -149,7 +150,8 @@ main = do V76_ProposalOrigin.migration, V77_MLSGroupMemberClient.migration, V78_TeamFeatureOutlookCalIntegration.migration, - V79_TeamFeatureMlsE2EId.migration + V79_TeamFeatureMlsE2EId.migration, + V80_AddConversationCodePassword.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/V80_AddConversationCodePassword.hs b/services/galley/schema/src/V80_AddConversationCodePassword.hs new file mode 100644 index 0000000000..34c67a4253 --- /dev/null +++ b/services/galley/schema/src/V80_AddConversationCodePassword.hs @@ -0,0 +1,35 @@ +-- 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 V80_AddConversationCodePassword + ( migration, + ) +where + +import Cassandra.Schema +import Imports +import Text.RawString.QQ + +migration :: Migration +migration = + Migration 80 "Add optional password to conversation_codes table" $ + schema' + [r| + ALTER TABLE conversation_codes ADD ( + password blob + ) + |] diff --git a/services/galley/src/Galley/API/Action.hs b/services/galley/src/Galley/API/Action.hs index f81190f798..dfe49a0595 100644 --- a/services/galley/src/Galley/API/Action.hs +++ b/services/galley/src/Galley/API/Action.hs @@ -558,6 +558,7 @@ data LocalConversationUpdate = LocalConversationUpdate { lcuEvent :: Event, lcuUpdate :: ConversationUpdate } + deriving (Show) updateLocalConversation :: forall tag r. @@ -569,6 +570,7 @@ updateLocalConversation :: Member FederatorAccess r, Member GundeckAccess r, Member (Input UTCTime) r, + Member (Logger (Log.Msg -> Log.Msg)) r, HasConversationActionEffects tag r, SingI tag ) => @@ -607,6 +609,7 @@ updateLocalConversationUnchecked :: Member FederatorAccess r, Member GundeckAccess r, Member (Input UTCTime) r, + Member (Logger (Log.Msg -> Log.Msg)) r, HasConversationActionEffects tag r ) => Local Conversation -> @@ -629,6 +632,11 @@ updateLocalConversationUnchecked lconv qusr con action = do (extraTargets, action') <- performAction tag qusr lconv action notifyConversationAction + -- Removing members should be fault tolerant. + ( case tag of + SConversationRemoveMembersTag -> False + _ -> True + ) (sing @tag) qusr False @@ -686,8 +694,10 @@ notifyConversationAction :: ( Member FederatorAccess r, Member ExternalAccess r, Member GundeckAccess r, - Member (Input UTCTime) r + Member (Input UTCTime) r, + Member (Logger (Log.Msg -> Log.Msg)) r ) => + Bool -> Sing tag -> Qualified UserId -> Bool -> @@ -696,7 +706,7 @@ notifyConversationAction :: BotsAndMembers -> ConversationAction (tag :: ConversationActionTag) -> Sem r LocalConversationUpdate -notifyConversationAction tag quid notifyOrigDomain con lconv targets action = do +notifyConversationAction failEarly tag quid notifyOrigDomain con lconv targets action = do now <- input let lcnv = fmap convId lconv conv = tUnqualified lconv @@ -721,19 +731,45 @@ notifyConversationAction tag quid notifyOrigDomain con lconv targets action = do { nrcConvId = convId conv, nrcProtocol = convProtocol conv } - E.runFederatedConcurrently_ (toList newDomains) $ \_ -> do - void $ fedClient @'Galley @"on-new-remote-conversation" nrc - - update <- fmap (fromMaybe (mkUpdate []) . asum . map tUnqualified) - . E.runFederatedConcurrently (toList (bmRemotes targets)) - $ \ruids -> do - let update = mkUpdate (tUnqualified ruids) - -- if notifyOrigDomain is false, filter out user from quid's domain, - -- because quid's backend will update local state and notify its users - -- itself using the ConversationUpdate returned by this function - if notifyOrigDomain || tDomain ruids /= qDomain quid - then fedClient @'Galley @"on-conversation-updated" update $> Nothing - else pure (Just update) + let errorIntolerant = do + E.runFederatedConcurrently_ (toList newDomains) $ \_ -> do + void $ fedClient @'Galley @"on-new-remote-conversation" nrc + fmap (fromMaybe (mkUpdate []) . asum . map tUnqualified) + . E.runFederatedConcurrently (toList (bmRemotes targets)) + $ \ruids -> do + let update = mkUpdate (tUnqualified ruids) + -- if notifyOrigDomain is false, filter out user from quid's domain, + -- because quid's backend will update local state and notify its users + -- itself using the ConversationUpdate returned by this function + if notifyOrigDomain || tDomain ruids /= qDomain quid + then fedClient @'Galley @"on-conversation-updated" update $> Nothing + else pure (Just update) + errorTolerant = do + fedEithers <- E.runFederatedConcurrentlyEither (toList newDomains) $ \_ -> do + void $ fedClient @'Galley @"on-new-remote-conversation" nrc + for_ fedEithers $ + either + (logError "on-new-remote-conversation" "An error occurred while communicating with federated server: ") + (pure . tUnqualified) + updates <- + E.runFederatedConcurrentlyEither (toList (bmRemotes targets)) $ + \ruids -> do + let update = mkUpdate (tUnqualified ruids) + -- if notifyOrigDomain is false, filter out user from quid's domain, + -- because quid's backend will update local state and notify its users + -- itself using the ConversationUpdate returned by this function + if notifyOrigDomain || tDomain ruids /= qDomain quid + then fedClient @'Galley @"on-conversation-updated" update $> Nothing + else pure (Just update) + let f = fromMaybe (mkUpdate []) . asum . map tUnqualified . rights + update = f updates + for_ (lefts updates) $ + logError + "on-conversation-update" + "An error occurred while communicating with federated server: " + pure update + + update <- if failEarly then errorIntolerant else errorTolerant -- notify local participants and bots pushConversationEvent con e (qualifyAs lcnv (bmLocals targets)) (bmBots targets) @@ -741,6 +777,11 @@ notifyConversationAction tag quid notifyOrigDomain con lconv targets action = do -- return both the event and the 'ConversationUpdate' structure corresponding -- to the originating domain (if it is remote) pure $ LocalConversationUpdate e update + where + logError :: Show a => String -> String -> (a, FederationError) -> Sem r () + logError field msg e = + P.warn $ + Log.field "federation call" field . Log.msg (msg <> show e) -- | Notify all local members about a remote conversation update that originated -- from a local user @@ -815,6 +856,7 @@ kickMember qusr lconv targets victim = void . runError @NoChanges $ do lconv () notifyConversationAction + False (sing @'ConversationRemoveMembersTag) qusr True diff --git a/services/galley/src/Galley/API/Create.hs b/services/galley/src/Galley/API/Create.hs index 3c97d90909..7ddb0b3fbc 100644 --- a/services/galley/src/Galley/API/Create.hs +++ b/services/galley/src/Galley/API/Create.hs @@ -23,7 +23,8 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . module Galley.API.Create - ( createGroupConversation, + ( createGroupConversationUpToV3, + createGroupConversation, createProteusSelfConversation, createOne2OneConversation, createConnectConversation, @@ -31,6 +32,7 @@ module Galley.API.Create where import Control.Lens hiding ((??)) +import Data.Domain import Data.Id import Data.List1 (list1) import Data.Misc (FutureWork (FutureWork)) @@ -51,6 +53,7 @@ import qualified Galley.Data.Conversation as Data import Galley.Data.Conversation.Types import Galley.Effects import qualified Galley.Effects.ConversationStore as E +import qualified Galley.Effects.FederatorAccess as E import qualified Galley.Effects.GundeckAccess as E import qualified Galley.Effects.MemberStore as E import qualified Galley.Effects.TeamStore as E @@ -82,12 +85,14 @@ import Wire.API.Team.Permission hiding (self) ---------------------------------------------------------------------------- -- Group conversations --- | The public-facing endpoint for creating group conversations. -createGroupConversation :: +-- | The public-facing endpoint for creating group conversations in the client +-- API up to and including version 3. +createGroupConversationUpToV3 :: ( Member BrigAccess r, Member ConversationStore r, Member MemberStore r, Member (ErrorS 'ConvAccessDenied) r, + Member (Error FederationError) r, Member (Error InternalError) r, Member (Error InvalidInput) r, Member (ErrorS 'NotATeamMember) r, @@ -108,10 +113,90 @@ createGroupConversation :: ) => Local UserId -> Maybe ClientId -> - ConnId -> + Maybe ConnId -> NewConv -> Sem r ConversationResponse -createGroupConversation lusr mCreatorClient conn newConv = do +createGroupConversationUpToV3 lusr mCreatorClient conn newConv = + createGroupConversationGeneric + lusr + mCreatorClient + conn + newConv + (const conversationCreated) + +-- | The public-facing endpoint for creating group conversations in the client +-- API in version 4 and above. +createGroupConversation :: + ( Member BrigAccess r, + Member ConversationStore r, + Member MemberStore r, + Member (ErrorS 'ConvAccessDenied) r, + Member (Error FederationError) r, + Member (Error InternalError) r, + Member (Error InvalidInput) r, + Member (ErrorS 'NotATeamMember) r, + Member (ErrorS OperationDenied) r, + Member (ErrorS 'NotConnected) r, + Member (ErrorS 'MLSNotEnabled) r, + Member (ErrorS 'MLSNonEmptyMemberList) r, + Member (ErrorS 'MLSMissingSenderClient) r, + Member (ErrorS 'MissingLegalholdConsent) r, + Member FederatorAccess r, + Member GundeckAccess r, + Member (Input Env) r, + Member (Input Opts) r, + Member (Input UTCTime) r, + Member LegalHoldStore r, + Member TeamStore r, + Member P.TinyLog r + ) => + Local UserId -> + Maybe ClientId -> + Maybe ConnId -> + NewConv -> + Sem r CreateGroupConversationResponse +createGroupConversation lusr mCreatorClient conn newConv = + createGroupConversationGeneric + lusr + mCreatorClient + conn + newConv + groupConversationCreated + +createGroupConversationGeneric :: + ( Member BrigAccess r, + Member ConversationStore r, + Member MemberStore r, + Member (ErrorS 'ConvAccessDenied) r, + Member (Error FederationError) r, + Member (Error InternalError) r, + Member (Error InvalidInput) r, + Member (ErrorS 'NotATeamMember) r, + Member (ErrorS OperationDenied) r, + Member (ErrorS 'NotConnected) r, + Member (ErrorS 'MLSNotEnabled) r, + Member (ErrorS 'MLSNonEmptyMemberList) r, + Member (ErrorS 'MLSMissingSenderClient) r, + Member (ErrorS 'MissingLegalholdConsent) r, + Member FederatorAccess r, + Member GundeckAccess r, + Member (Input Env) r, + Member (Input Opts) r, + Member (Input UTCTime) r, + Member LegalHoldStore r, + Member TeamStore r, + Member P.TinyLog r + ) => + Local UserId -> + Maybe ClientId -> + Maybe ConnId -> + NewConv -> + -- | The function that incorporates the failed to add remote users in the + -- response. In the client API up to and including V3 this function simply + -- ignores the first argument. + (Set (Remote UserId) -> Local UserId -> Conversation -> Sem r resp) -> + Sem r resp +createGroupConversationGeneric lusr mCreatorClient conn newConv convCreated = do (nc, fromConvSize -> allUsers) <- newRegularConversation lusr newConv let tinfo = newConvTeam newConv checkCreateConvPermissions lusr newConv tinfo allUsers @@ -130,19 +215,27 @@ createGroupConversation lusr mCreatorClient conn newConv = do -- protocol-specific validation is successful. Otherwise we might write the -- conversation to the database, and throw a validation error when the -- conversation is already in the database. - conv <- E.createConversation lcnv nc + failedToNotify <- do + conv <- E.createConversation lcnv nc - -- set creator client for MLS conversations - case (convProtocol conv, mCreatorClient) of - (ProtocolProteus, _) -> pure () - (ProtocolMLS mlsMeta, Just c) -> - E.addMLSClients (cnvmlsGroupId mlsMeta) (tUntagged lusr) (Set.singleton (c, nullKeyPackageRef)) - (ProtocolMLS _mlsMeta, Nothing) -> throwS @'MLSMissingSenderClient + -- set creator client for MLS conversations + case (convProtocol conv, mCreatorClient) of + (ProtocolProteus, _) -> pure () + (ProtocolMLS mlsMeta, Just c) -> + E.addMLSClients (cnvmlsGroupId mlsMeta) (tUntagged lusr) (Set.singleton (c, nullKeyPackageRef)) + (ProtocolMLS _mlsMeta, Nothing) -> throwS @'MLSMissingSenderClient - now <- input - -- NOTE: We only send (conversation) events to members of the conversation - notifyCreatedConversation (Just now) lusr (Just conn) conv - conversationCreated lusr conv + -- NOTE: We only send (conversation) events to members of the conversation + failedToNotify <- notifyCreatedConversation lusr conn conv + -- We already added all the invitees, but now remove from the conversation + -- those that could not be notified + E.deleteMembers (convId conv) . ulFromRemotes . Set.toList $ failedToNotify + pure failedToNotify + conv <- + E.getConversation (tUnqualified lcnv) + >>= note (BadConvState (tUnqualified lcnv)) + + convCreated failedToNotify lusr conv ensureNoLegalholdConflicts :: ( Member (ErrorS 'MissingLegalholdConsent) r, @@ -289,6 +382,7 @@ createOne2OneConversation lusr zcon j = do createLegacyOne2OneConversationUnchecked :: ( Member ConversationStore r, + Member (Error FederationError) r, Member (Error InternalError) r, Member (Error InvalidInput) r, Member FederatorAccess r, @@ -321,7 +415,7 @@ createLegacyOne2OneConversationUnchecked self zcon name mtid other = do Just c -> conversationExisted self c Nothing -> do c <- E.createConversation lcnv nc - notifyCreatedConversation Nothing self (Just zcon) c + void $ notifyCreatedConversation self (Just zcon) c conversationCreated self c createOne2OneConversationUnchecked :: @@ -349,6 +443,7 @@ createOne2OneConversationUnchecked self zcon name mtid other = do createOne2OneConversationLocally :: ( Member ConversationStore r, + Member (Error FederationError) r, Member (Error InternalError) r, Member FederatorAccess r, Member GundeckAccess r, @@ -380,7 +475,7 @@ createOne2OneConversationLocally lcnv self zcon name mtid other = do ncProtocol = ProtocolProteusTag } c <- E.createConversation lcnv nc - notifyCreatedConversation Nothing self (Just zcon) c + void $ notifyCreatedConversation self (Just zcon) c conversationCreated self c createOne2OneConversationRemotely :: @@ -436,7 +531,7 @@ createConnectConversation lusr conn j = do c <- E.createConversation lcnv nc now <- input let e = Event (tUntagged lcnv) Nothing (tUntagged lusr) now (EdConnect j) - notifyCreatedConversation Nothing lusr conn c + void $ notifyCreatedConversation lusr conn c for_ (newPushLocal ListComplete (tUnqualified lusr) (ConvEvent e) (recipient <$> Data.convLocalMembers c)) $ \p -> E.push1 $ p @@ -534,30 +629,63 @@ conversationCreated :: Sem r ConversationResponse conversationCreated lusr cnv = Created <$> conversationView lusr cnv -notifyCreatedConversation :: +groupConversationCreated :: ( Member (Error InternalError) r, + Member P.TinyLog r + ) => + Set (Remote UserId) -> + Local UserId -> + Data.Conversation -> + Sem r CreateGroupConversationResponse +groupConversationCreated failedToAdd lusr cnv = do + conv <- conversationView lusr cnv + pure . GroupConversationCreated $ + CreateGroupConversation conv (toMap failedToAdd) + where + toMap :: Ord a => Set (Remote a) -> Map Domain (Set a) + toMap = fmap Set.fromList . indexQualified @[] . foldMap (pure . tUntagged) + +-- | The return set contains all the remote users that could not be contacted. +-- Consequently, they are not added to the member list. This behavior might be +-- changed later on when a message/event queue per remote backend is +-- implemented. +notifyCreatedConversation :: + ( Member (Error FederationError) r, + Member (Error InternalError) r, Member FederatorAccess r, Member GundeckAccess r, Member (Input UTCTime) r, Member P.TinyLog r ) => - Maybe UTCTime -> Local UserId -> Maybe ConnId -> Data.Conversation -> - Sem r () -notifyCreatedConversation dtime lusr conn c = do - now <- maybe input pure dtime - -- FUTUREWORK: Handle failures in notifying so it does not abort half way - -- through (either when notifying remotes or locals) - -- + Sem r (Set (Remote UserId)) +notifyCreatedConversation lusr conn c = do + now <- input -- Ask remote server to store conversation membership and notify remote users -- of being added to a conversation - registerRemoteConversationMemberships now (tDomain lusr) c - -- Notify local users - let remoteOthers = map remoteMemberToOther $ Data.convRemoteMembers c + failedToNotify <- registerRemoteConversationMemberships now (tDomain lusr) c + let allRemotes = Data.convRemoteMembers c + notifiedRemotes = + filter + (\rm -> Set.notMember (rmId rm) failedToNotify) + allRemotes localOthers = map (localMemberToOther (tDomain lusr)) $ Data.convLocalMembers c - E.push =<< mapM (toPush now remoteOthers localOthers) (Data.convLocalMembers c) + unless (null allRemotes) $ + unlessM E.isFederationConfigured $ + throw FederationNotConfigured + + -- Notify local users + E.push + =<< mapM + ( toPush + now + (remoteMemberToOther <$> notifiedRemotes) + localOthers + ) + (Data.convLocalMembers c) + pure failedToNotify where route | Data.convType c == RegularConv = RouteAny diff --git a/services/galley/src/Galley/API/Federation.hs b/services/galley/src/Galley/API/Federation.hs index 7850852ab2..11f24556ef 100644 --- a/services/galley/src/Galley/API/Federation.hs +++ b/services/galley/src/Galley/API/Federation.hs @@ -372,6 +372,7 @@ leaveConversation requestingDomain lc = do let botsAndMembers = BotsAndMembers mempty (Set.fromList remotes) mempty _ <- notifyConversationAction + False SConversationLeaveTag (tUntagged leaver) False @@ -499,6 +500,7 @@ onUserDeleted origDomain udcn = do removeUser (qualifyAs lc conv) (tUntagged deletedUser) void $ notifyConversationAction + False (sing @'ConversationLeaveTag) untaggedDeletedUser False @@ -629,7 +631,8 @@ sendMLSCommitBundle remoteDomain msr = let msg = rmValue (cbCommitMsg bundle) qcnv <- E.getConversationIdByGroupId (msgGroupId msg) >>= noteS @'ConvNotFound when (Conv (qUnqualified qcnv) /= F.mmsrConvOrSubId msr) $ throwS @'MLSGroupConversationMismatch - F.MLSMessageResponseUpdates . map lcuUpdate + uncurry F.MLSMessageResponseUpdates + . first (map lcuUpdate) <$> postMLSCommitBundle loc (tUntagged sender) Nothing qcnv Nothing bundle sendMLSMessage :: @@ -671,7 +674,8 @@ sendMLSMessage remoteDomain msr = SomeMessage _ msg -> do qcnv <- E.getConversationIdByGroupId (msgGroupId msg) >>= noteS @'ConvNotFound when (Conv (qUnqualified qcnv) /= F.mmsrConvOrSubId msr) $ throwS @'MLSGroupConversationMismatch - F.MLSMessageResponseUpdates . map lcuUpdate + uncurry F.MLSMessageResponseUpdates + . first (map lcuUpdate) <$> postMLSMessage loc (tUntagged sender) Nothing qcnv Nothing raw class ToGalleyRuntimeError (effs :: EffectRow) r where diff --git a/services/galley/src/Galley/API/MLS/Message.hs b/services/galley/src/Galley/API/MLS/Message.hs index 891d6350a1..2b92bdb0d2 100644 --- a/services/galley/src/Galley/API/MLS/Message.hs +++ b/services/galley/src/Galley/API/MLS/Message.hs @@ -37,6 +37,7 @@ import Data.Qualified import qualified Data.Set as Set import qualified Data.Text as T import Data.Time +import Data.Tuple.Extra import Galley.API.Action import Galley.API.Error import Galley.API.MLS.Enabled @@ -150,7 +151,7 @@ postMLSMessageFromLocalUserV1 lusr mc conn smsg = do case rmValue smsg of SomeMessage _ msg -> do qcnv <- getConversationIdByGroupId (msgGroupId msg) >>= noteS @'ConvNotFound - map lcuEvent + fst . first (map lcuEvent) <$> postMLSMessage lusr (tUntagged lusr) mc qcnv (Just conn) smsg postMLSMessageFromLocalUser :: @@ -181,13 +182,19 @@ postMLSMessageFromLocalUser :: ConnId -> RawMLS SomeMessage -> Sem r MLSMessageSendingStatus -postMLSMessageFromLocalUser lusr mc conn msg = do +postMLSMessageFromLocalUser lusr mc conn smsg = do -- FUTUREWORK: Inline the body of 'postMLSMessageFromLocalUserV1' once version -- V1 is dropped assertMLSEnabled - events <- postMLSMessageFromLocalUserV1 lusr mc conn msg + -- (events, unreachables) <- postMLSMessageFromLocalUserV1 lusr mc conn msg + assertMLSEnabled + (events, unreachables) <- case rmValue smsg of + SomeMessage _ msg -> do + qcnv <- getConversationIdByGroupId (msgGroupId msg) >>= noteS @'ConvNotFound + first (map lcuEvent) + <$> postMLSMessage lusr (tUntagged lusr) mc qcnv (Just conn) smsg t <- toUTCTimeMillis <$> input - pure $ MLSMessageSendingStatus events t + pure $ MLSMessageSendingStatus events t unreachables postMLSCommitBundle :: ( HasProposalEffects r, @@ -211,7 +218,7 @@ postMLSCommitBundle :: Qualified ConvId -> Maybe ConnId -> CommitBundle -> - Sem r [LocalConversationUpdate] + Sem r ([LocalConversationUpdate], UnreachableUsers) postMLSCommitBundle loc qusr mc qcnv conn rawBundle = foldQualified loc @@ -244,11 +251,11 @@ postMLSCommitBundleFromLocalUser lusr mc conn bundle = do assertMLSEnabled let msg = rmValue (cbCommitMsg bundle) qcnv <- getConversationIdByGroupId (msgGroupId msg) >>= noteS @'ConvNotFound - events <- - map lcuEvent + (events, unreachables) <- + first (map lcuEvent) <$> postMLSCommitBundle lusr (tUntagged lusr) mc qcnv (Just conn) bundle t <- toUTCTimeMillis <$> input - pure $ MLSMessageSendingStatus events t + pure $ MLSMessageSendingStatus events t unreachables postMLSCommitBundleToLocalConv :: ( HasProposalEffects r, @@ -269,7 +276,7 @@ postMLSCommitBundleToLocalConv :: Maybe ConnId -> CommitBundle -> Local ConvId -> - Sem r [LocalConversationUpdate] + Sem r ([LocalConversationUpdate], UnreachableUsers) postMLSCommitBundleToLocalConv qusr mc conn bundle lcnv = do let msg = rmValue (cbCommitMsg bundle) conv <- getLocalConvForUser qusr lcnv @@ -308,12 +315,12 @@ postMLSCommitBundleToLocalConv qusr mc conn bundle lcnv = do ApplicationMessage _ -> throwS @'MLSUnsupportedMessage ProposalMessage _ -> throwS @'MLSUnsupportedMessage - propagateMessage qusr (qualifyAs lcnv conv) cm conn (rmRaw (cbCommitMsg bundle)) + unreachables <- propagateMessage qusr (qualifyAs lcnv conv) cm conn (rmRaw (cbCommitMsg bundle)) for_ (cbWelcome bundle) $ postMLSWelcome lcnv conn - pure events + pure (events, unreachables) postMLSCommitBundleToRemoteConv :: ( Members MLSBundleStaticErrors r, @@ -332,7 +339,7 @@ postMLSCommitBundleToRemoteConv :: Maybe ConnId -> CommitBundle -> Remote ConvId -> - Sem r [LocalConversationUpdate] + Sem r ([LocalConversationUpdate], UnreachableUsers) postMLSCommitBundleToRemoteConv loc qusr con bundle rcnv = do -- only local users can send messages to remote conversations lusr <- foldQualified loc pure (\_ -> throwS @'ConvAccessDenied) qusr @@ -347,15 +354,15 @@ postMLSCommitBundleToRemoteConv loc qusr con bundle rcnv = do mmsrSender = tUnqualified lusr, mmsrRawMessage = Base64ByteString (serializeCommitBundle bundle) } - updates <- case resp of + case resp of MLSMessageResponseError e -> rethrowErrors @MLSBundleStaticErrors e MLSMessageResponseProtocolError e -> throw (mlsProtocolError e) MLSMessageResponseProposalFailure e -> throw (MLSProposalFailure e) - MLSMessageResponseUpdates updates -> pure updates - - for updates $ \update -> do - e <- notifyRemoteConversationAction loc (qualifyAs rcnv update) con - pure (LocalConversationUpdate e update) + MLSMessageResponseUpdates updates unreachables -> do + ups <- for updates $ \update -> do + e <- notifyRemoteConversationAction loc (qualifyAs rcnv update) con + pure (LocalConversationUpdate e update) + pure (ups, unreachables) postMLSMessage :: ( HasProposalEffects r, @@ -386,15 +393,16 @@ postMLSMessage :: Qualified ConvId -> Maybe ConnId -> RawMLS SomeMessage -> - Sem r [LocalConversationUpdate] -postMLSMessage loc qusr mc qcnv con smsg = case rmValue smsg of - SomeMessage tag msg -> do - mSender <- fmap ciClient <$> getSenderIdentity qusr mc tag msg - foldQualified - loc - (postMLSMessageToLocalConv qusr mSender con smsg) - (postMLSMessageToRemoteConv loc qusr mSender con smsg) - qcnv + Sem r ([LocalConversationUpdate], UnreachableUsers) +postMLSMessage loc qusr mc qcnv con smsg = + case rmValue smsg of + SomeMessage tag msg -> do + mSender <- fmap ciClient <$> getSenderIdentity qusr mc tag msg + foldQualified + loc + (postMLSMessageToLocalConv qusr mSender con smsg) + (postMLSMessageToRemoteConv loc qusr mSender con smsg) + qcnv -- Check that the MLS client who created the message belongs to the user who -- is the sender of the REST request, identified by HTTP header. @@ -463,34 +471,34 @@ postMLSMessageToLocalConv :: Maybe ConnId -> RawMLS SomeMessage -> Local ConvId -> - Sem r [LocalConversationUpdate] -postMLSMessageToLocalConv qusr senderClient con smsg lcnv = case rmValue smsg of - SomeMessage tag msg -> do - conv <- getLocalConvForUser qusr lcnv - mlsMeta <- Data.mlsMetadata conv & noteS @'ConvNotFound - - -- construct client map - cm <- lookupMLSClients (cnvmlsGroupId mlsMeta) - let lconv = qualifyAs lcnv conv - - -- validate message - events <- case tag of - SMLSPlainText -> case msgPayload msg of - CommitMessage c -> - processCommit qusr senderClient con lconv mlsMeta cm (msgEpoch msg) (msgSender msg) c - ApplicationMessage _ -> throwS @'MLSUnsupportedMessage - ProposalMessage prop -> - processProposal qusr conv mlsMeta msg prop $> mempty - SMLSCipherText -> case toMLSEnum' (msgContentType (msgPayload msg)) of - Right CommitMessageTag -> throwS @'MLSUnsupportedMessage - Right ProposalMessageTag -> throwS @'MLSUnsupportedMessage - Right ApplicationMessageTag -> pure mempty - Left _ -> throwS @'MLSUnsupportedMessage - - -- forward message - propagateMessage qusr lconv cm con (rmRaw smsg) - - pure events + Sem r ([LocalConversationUpdate], UnreachableUsers) +postMLSMessageToLocalConv qusr senderClient con smsg lcnv = + case rmValue smsg of + SomeMessage tag msg -> do + conv <- getLocalConvForUser qusr lcnv + mlsMeta <- Data.mlsMetadata conv & noteS @'ConvNotFound + + -- construct client map + cm <- lookupMLSClients (cnvmlsGroupId mlsMeta) + let lconv = qualifyAs lcnv conv + + -- validate message + events <- case tag of + SMLSPlainText -> case msgPayload msg of + CommitMessage c -> + processCommit qusr senderClient con lconv mlsMeta cm (msgEpoch msg) (msgSender msg) c + ApplicationMessage _ -> throwS @'MLSUnsupportedMessage + ProposalMessage prop -> + processProposal qusr conv mlsMeta msg prop $> mempty + SMLSCipherText -> case toMLSEnum' (msgContentType (msgPayload msg)) of + Right CommitMessageTag -> throwS @'MLSUnsupportedMessage + Right ProposalMessageTag -> throwS @'MLSUnsupportedMessage + Right ApplicationMessageTag -> pure mempty + Left _ -> throwS @'MLSUnsupportedMessage + + -- forward message + unreachables <- propagateMessage qusr lconv cm con (rmRaw smsg) + pure (events, unreachables) postMLSMessageToRemoteConv :: ( Members MLSMessageStaticErrors r, @@ -505,13 +513,13 @@ postMLSMessageToRemoteConv :: Maybe ConnId -> RawMLS SomeMessage -> Remote ConvId -> - Sem r [LocalConversationUpdate] + Sem r ([LocalConversationUpdate], UnreachableUsers) postMLSMessageToRemoteConv loc qusr _senderClient con smsg rcnv = do -- only local users can send messages to remote conversations lusr <- foldQualified loc pure (\_ -> throwS @'ConvAccessDenied) qusr -- only members may send messages to the remote conversation - flip unless (throwS @'ConvMemberNotFound) =<< checkLocalMemberRemoteConv (tUnqualified lusr) rcnv + flip unless (throwS @'ConvMemberNotFound) =<< checkLocalMemberRemoteConv (tUnqualified lusr) rcnv resp <- runFederated rcnv $ fedClient @'Galley @"send-mls-message" $ @@ -520,15 +528,17 @@ postMLSMessageToRemoteConv loc qusr _senderClient con smsg rcnv = do mmsrSender = tUnqualified lusr, mmsrRawMessage = Base64ByteString (rmRaw smsg) } - updates <- case resp of + case resp of MLSMessageResponseError e -> rethrowErrors @MLSMessageStaticErrors e - MLSMessageResponseProtocolError e -> throw (mlsProtocolError e) + MLSMessageResponseProtocolError e -> + throw (mlsProtocolError e) MLSMessageResponseProposalFailure e -> throw (MLSProposalFailure e) - MLSMessageResponseUpdates updates -> pure updates + MLSMessageResponseUpdates updates unreachables -> do + lcus <- for updates $ \update -> do + e <- notifyRemoteConversationAction loc (qualifyAs rcnv update) con + pure (LocalConversationUpdate e update) - for updates $ \update -> do - e <- notifyRemoteConversationAction loc (qualifyAs rcnv update) con - pure (LocalConversationUpdate e update) + pure (lcus, unreachables) type HasProposalEffects r = ( Member BrigAccess r, diff --git a/services/galley/src/Galley/API/MLS/Propagate.hs b/services/galley/src/Galley/API/MLS/Propagate.hs index 74fbf8f608..e3b5918bb4 100644 --- a/services/galley/src/Galley/API/MLS/Propagate.hs +++ b/services/galley/src/Galley/API/MLS/Propagate.hs @@ -36,7 +36,7 @@ import qualified Network.Wai.Utilities.Error as Wai import Network.Wai.Utilities.Server import Polysemy import Polysemy.Input -import Polysemy.TinyLog +import Polysemy.TinyLog hiding (trace) import qualified System.Logger.Class as Logger import Wire.API.Error import Wire.API.Error.Galley @@ -44,6 +44,7 @@ import Wire.API.Event.Conversation import Wire.API.Federation.API import Wire.API.Federation.API.Galley import Wire.API.Federation.Error +import Wire.API.MLS.Message import Wire.API.Message -- | Propagate a message. @@ -59,10 +60,11 @@ propagateMessage :: ClientMap -> Maybe ConnId -> ByteString -> - Sem r () + Sem r UnreachableUsers propagateMessage qusr lconv cm con raw = do -- FUTUREWORK: check the epoch let lmems = Data.convLocalMembers . tUnqualified $ lconv + rmems = Data.convRemoteMembers . tUnqualified $ lconv botMap = Map.fromList $ do m <- lmems b <- maybeToList $ newBotMember m @@ -78,8 +80,9 @@ propagateMessage qusr lconv cm con raw = do foldMap (uncurry mkPush) (lmems >>= localMemberMLSClients lcnv) -- send to remotes - traverse_ handleError - <=< runFederatedConcurrentlyEither (map remoteMemberQualify (Data.convRemoteMembers . tUnqualified $ lconv)) + UnreachableUsers . concat + <$$> traverse handleError + <=< runFederatedConcurrentlyEither (map remoteMemberQualify rmems) $ \(tUnqualified -> rs) -> fedClient @'Galley @"on-mls-message-sent" $ RemoteMLSMessage @@ -107,15 +110,20 @@ propagateMessage qusr lconv cm con raw = do (\(c, _) -> (remoteUserId, c)) (toList (Map.findWithDefault mempty remoteUserQId cm)) + remotesToQIds = fmap (tUntagged . rmId) + handleError :: Member TinyLog r => - Either (Remote [a], FederationError) (Remote RemoteMLSMessageResponse) -> - Sem r () + Either (Remote [RemoteMember], FederationError) (Remote RemoteMLSMessageResponse) -> + Sem r [Qualified UserId] handleError (Right x) = case tUnqualified x of - RemoteMLSMessageOk -> pure () - RemoteMLSMessageMLSNotEnabled -> logFedError x (errorToWai @'MLSNotEnabled) - handleError (Left (r, e)) = logFedError r (toWai e) - + RemoteMLSMessageOk -> pure [] + RemoteMLSMessageMLSNotEnabled -> do + logFedError x (errorToWai @'MLSNotEnabled) + pure [] + handleError (Left (r, e)) = do + logFedError r (toWai e) + pure $ remotesToQIds (tUnqualified r) logFedError :: Member TinyLog r => Remote x -> Wai.Error -> Sem r () logFedError r e = warn $ diff --git a/services/galley/src/Galley/API/MLS/Util.hs b/services/galley/src/Galley/API/MLS/Util.hs index a678de4440..eeffc97b41 100644 --- a/services/galley/src/Galley/API/MLS/Util.hs +++ b/services/galley/src/Galley/API/MLS/Util.hs @@ -51,7 +51,15 @@ getLocalConvForUser qusr lcnv = do conv <- getConversation (tUnqualified lcnv) >>= noteS @'ConvNotFound -- check that sender is part of conversation - isMember' <- foldQualified lcnv (fmap isJust . getLocalMember (convId conv) . tUnqualified) (fmap isJust . getRemoteMember (convId conv)) qusr + isMember' <- + foldQualified + lcnv + ( fmap isJust + . getLocalMember (convId conv) + . tUnqualified + ) + (fmap isJust . getRemoteMember (convId conv)) + qusr unless isMember' $ throwS @'ConvNotFound pure conv diff --git a/services/galley/src/Galley/API/Message.hs b/services/galley/src/Galley/API/Message.hs index 5ed9bdbde3..585c721cb9 100644 --- a/services/galley/src/Galley/API/Message.hs +++ b/services/galley/src/Galley/API/Message.hs @@ -31,6 +31,7 @@ module Galley.API.Message QualifiedMismatch (..), mkQualifiedUserClients, clientMismatchStrategyApply, + collectFailedToSend, ) where @@ -217,11 +218,12 @@ checkMessageClients sender participantMap recipientMap mismatchStrat = getRemoteClients :: (Member FederatorAccess r) => [RemoteMember] -> - Sem r (Map (Domain, UserId) (Set ClientId)) + Sem r [Either (Remote [UserId], FederationError) (Map (Domain, UserId) (Set ClientId))] getRemoteClients remoteMembers = -- concatenating maps is correct here, because their sets of keys are disjoint - mconcat . map tUnqualified - <$> runFederatedConcurrently (map rmId remoteMembers) getRemoteClientsFromDomain + -- Use runFederatedConcurrentlyEither so we can catch federation errors and report to clients + -- which domains and users aren't contactable at the moment. + tUnqualified <$$$> runFederatedConcurrentlyEither (map rmId remoteMembers) getRemoteClientsFromDomain where getRemoteClientsFromDomain :: Remote [UserId] -> FederatorClient 'Brig (Map (Domain, UserId) (Set ClientId)) getRemoteClientsFromDomain (tUntagged -> Qualified uids domain) = @@ -424,8 +426,19 @@ postQualifiedOtrMessage senderType sender mconn lcnv msg = -- get remote clients qualifiedRemoteClients <- getRemoteClients (convRemoteMembers conv) - let qualifiedClients = qualifiedLocalClients <> qualifiedRemoteClients - + let qualifiedClients = qualifiedLocalClients <> qualifiedRemoteClients' + -- concatenating maps is correct here, because their sets of keys are disjoint + qualifiedRemoteClients' = mconcat $ rights qualifiedRemoteClients + -- Collect the list of users and their domains that we weren't able to fetch clients for. + failedToSendFetchingClients :: QualifiedUserClients + failedToSendFetchingClients = + QualifiedUserClients . mconcat $ extractUserMap . fst <$> lefts qualifiedRemoteClients + where + extractUserMap :: Remote [UserId] -> Map Domain (Map UserId (Set ClientId)) + extractUserMap l = Map.singleton domain $ Map.fromList $ (,mempty) <$> users + where + domain = qDomain $ tUntagged l + users = tUnqualified l -- check if the sender client exists (as one of the clients in the conversation) unless ( Set.member @@ -452,7 +465,6 @@ postQualifiedOtrMessage senderType sender mconn lcnv msg = . runError @LegalholdConflicts $ guardQualifiedLegalholdPolicyConflicts lhProtectee missingClients throw e - failedToSend <- sendMessages @'NormalMessage now @@ -463,7 +475,49 @@ postQualifiedOtrMessage senderType sender mconn lcnv msg = botMap (qualifiedNewOtrMetadata msg) validMessages - pure otrResult {mssFailedToSend = failedToSend} + + let -- List of the clients that are initially flagged as redundant. + redundant' = toDomUserClient $ qualifiedUserClients $ mssRedundantClients otrResult + -- List of users that we couldn't fetch clients for. Used to get their "redundant" + -- clients for reporting as failedToSend. + failed' = toDomUserClient $ qualifiedUserClients failedToSendFetchingClients + -- failedToSendFetchingClients doesn't contain client IDs, so those need to be excluded + -- from the filter search. We have to focus on only the domain and user. These clients + -- should be listed in the failedToSend field however, as tracking these clients is an + -- important part of the proteus protocol. + predicate (d, (u, _)) = any (\(d', (u', _)) -> d == d' && u == u') failed' + -- Failed users/clients aren't redundant + (failed, redundant) = partition predicate redundant' + pure + otrResult + { mssFailedToSend = + QualifiedUserClients $ + collectFailedToSend + [ qualifiedUserClients failedToSend, + qualifiedUserClients failedToSendFetchingClients, + fromDomUserClient failed + ], + mssRedundantClients = QualifiedUserClients $ fromDomUserClient redundant + } + where + -- Get the triples for domains, users, and clients so we can easily filter + -- out the values from redundant clients that should be in failed to send. + toDomUserClient :: Map Domain (Map UserId (Set ClientId)) -> [(Domain, (UserId, Set ClientId))] + toDomUserClient m = do + (d, m') <- Map.assocs m + (d,) <$> Map.assocs m' + -- Rebuild the map, concatenating results along the way. + fromDomUserClient :: [(Domain, (UserId, Set ClientId))] -> Map Domain (Map UserId (Set ClientId)) + fromDomUserClient = foldr buildUserClientMap mempty + where + buildUserClientMap :: (Domain, (UserId, Set ClientId)) -> Map Domain (Map UserId (Set ClientId)) -> Map Domain (Map UserId (Set ClientId)) + buildUserClientMap (d, (u, c)) m = Map.alter (pure . Map.alter (pure . Set.union c . fromMaybe mempty) u . fromMaybe mempty) d m + +collectFailedToSend :: + Foldable f => + f (Map Domain (Map UserId (Set ClientId))) -> + Map Domain (Map UserId (Set ClientId)) +collectFailedToSend = foldr (Map.unionWith (Map.unionWith Set.union)) mempty makeUserMap :: Set UserId -> Map UserId (Set ClientId) -> Map UserId (Set ClientId) makeUserMap keys = (<> Map.fromSet (const mempty) keys) diff --git a/services/galley/src/Galley/API/Public/Conversation.hs b/services/galley/src/Galley/API/Public/Conversation.hs index 73bb3cb575..73de6113d9 100644 --- a/services/galley/src/Galley/API/Public/Conversation.hs +++ b/services/galley/src/Galley/API/Public/Conversation.hs @@ -24,6 +24,7 @@ import Galley.API.Query import Galley.API.Update import Galley.App import Galley.Cassandra.TeamFeatures +import Imports import Wire.API.Federation.API import Wire.API.Routes.API import Wire.API.Routes.Public.Galley.Conversation @@ -44,7 +45,8 @@ conversationAPI = <@> mkNamedAPI @"list-conversations@v2" (callsFed (exposeAnnotations listConversations)) <@> mkNamedAPI @"list-conversations" (callsFed (exposeAnnotations listConversations)) <@> mkNamedAPI @"get-conversation-by-reusable-code" (getConversationByReusableCode @Cassandra) - <@> mkNamedAPI @"create-group-conversation@v2" (callsFed (exposeAnnotations createGroupConversation)) + <@> mkNamedAPI @"create-group-conversation@v2" (callsFed (exposeAnnotations createGroupConversationUpToV3)) + <@> mkNamedAPI @"create-group-conversation@v3" (callsFed (exposeAnnotations createGroupConversationUpToV3)) <@> mkNamedAPI @"create-group-conversation" (callsFed (exposeAnnotations createGroupConversation)) <@> mkNamedAPI @"create-self-conversation@v2" createProteusSelfConversation <@> mkNamedAPI @"create-self-conversation" createProteusSelfConversation @@ -57,7 +59,8 @@ conversationAPI = <@> mkNamedAPI @"join-conversation-by-id-unqualified" (callsFed joinConversationById) <@> mkNamedAPI @"join-conversation-by-code-unqualified" (callsFed (joinConversationByReusableCode @Cassandra)) <@> mkNamedAPI @"code-check" (checkReusableCode @Cassandra) - <@> mkNamedAPI @"create-conversation-code-unqualified" (addCodeUnqualified @Cassandra) + <@> mkNamedAPI @"create-conversation-code-unqualified@v3" (addCodeUnqualified @Cassandra Nothing) + <@> mkNamedAPI @"create-conversation-code-unqualified" (addCodeUnqualifiedWithReqBody @Cassandra) <@> mkNamedAPI @"get-conversation-guest-links-status" (getConversationGuestLinksStatus @Cassandra) <@> mkNamedAPI @"remove-code-unqualified" rmCodeUnqualified <@> mkNamedAPI @"get-code" (getCode @Cassandra) diff --git a/services/galley/src/Galley/API/Query.hs b/services/galley/src/Galley/API/Query.hs index 26290f4c1e..5aa2733ec0 100644 --- a/services/galley/src/Galley/API/Query.hs +++ b/services/galley/src/Galley/API/Query.hs @@ -61,6 +61,7 @@ import qualified Galley.API.Mapping as Mapping import Galley.API.Util import qualified Galley.Data.Conversation as Data import Galley.Data.Types (Code (codeConversation)) +import qualified Galley.Data.Types as Data import Galley.Effects import qualified Galley.Effects.ConversationStore as E import qualified Galley.Effects.FederatorAccess as E @@ -624,6 +625,7 @@ getConversationByReusableCode :: Member CodeStore r, Member ConversationStore r, Member (ErrorS 'CodeNotFound) r, + Member (ErrorS 'InvalidConversationPassword) r, Member (ErrorS 'ConvNotFound) r, Member (ErrorS 'ConvAccessDenied) r, Member (ErrorS 'GuestLinksDisabled) r, @@ -638,17 +640,18 @@ getConversationByReusableCode :: Value -> Sem r ConversationCoverView getConversationByReusableCode lusr key value = do - c <- verifyReusableCode (ConversationCode key value Nothing) + c <- verifyReusableCode False Nothing (ConversationCode key value Nothing) conv <- E.getConversation (codeConversation c) >>= noteS @'ConvNotFound ensureConversationAccess (tUnqualified lusr) conv CodeAccess ensureGuestLinksEnabled @db (Data.convTeam conv) - pure $ coverView conv + pure $ coverView c conv where - coverView :: Data.Conversation -> ConversationCoverView - coverView conv = + coverView :: Data.Code -> Data.Conversation -> ConversationCoverView + coverView c conv = ConversationCoverView { cnvCoverConvId = Data.convId conv, - cnvCoverName = Data.convName conv + cnvCoverName = Data.convName conv, + cnvCoverHasPassword = Data.codeHasPassword c } ensureGuestLinksEnabled :: diff --git a/services/galley/src/Galley/API/Teams.hs b/services/galley/src/Galley/API/Teams.hs index 393a49f45b..1c8a93461c 100644 --- a/services/galley/src/Galley/API/Teams.hs +++ b/services/galley/src/Galley/API/Teams.hs @@ -120,6 +120,7 @@ import Polysemy.Input import Polysemy.Output import qualified Polysemy.TinyLog as P import qualified SAML2.WebSSO as SAML +import System.Logger (Msg) import qualified System.Logger.Class as Log import Wire.API.Conversation.Role (Action (DeleteConversation), wireConvRoles) import qualified Wire.API.Conversation.Role as Public @@ -1098,7 +1099,8 @@ deleteTeamConversation :: Member FederatorAccess r, Member GundeckAccess r, Member (Input UTCTime) r, - Member TeamStore r + Member TeamStore r, + Member (P.Logger (Msg -> Msg)) r ) => Local UserId -> ConnId -> diff --git a/services/galley/src/Galley/API/Update.hs b/services/galley/src/Galley/API/Update.hs index b431ed747e..2460533ccb 100644 --- a/services/galley/src/Galley/API/Update.hs +++ b/services/galley/src/Galley/API/Update.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE OverloadedRecordDot #-} -- This file is part of the Wire Server implementation. -- -- Copyright (C) 2022 Wire Swiss GmbH @@ -25,6 +26,7 @@ module Galley.API.Update joinConversationByReusableCode, joinConversationById, addCodeUnqualified, + addCodeUnqualifiedWithReqBody, rmCodeUnqualified, getCode, updateUnqualifiedConversationName, @@ -118,6 +120,7 @@ import Polysemy import Polysemy.Error import Polysemy.Input import Polysemy.TinyLog +import System.Logger (Msg) import Wire.API.Conversation hiding (Member) import Wire.API.Conversation.Action import Wire.API.Conversation.Code @@ -130,6 +133,7 @@ import Wire.API.Federation.API import Wire.API.Federation.API.Galley import Wire.API.Federation.Error import Wire.API.Message +import Wire.API.Password (mkSafePassword) import Wire.API.Provider.Service (ServiceRef) import Wire.API.Routes.Public.Galley.Messaging import Wire.API.Routes.Public.Util (UpdateResult (..)) @@ -402,7 +406,8 @@ updateConversationMessageTimer :: Member ExternalAccess r, Member FederatorAccess r, Member GundeckAccess r, - Member (Input UTCTime) r + Member (Input UTCTime) r, + Member (Logger (Msg -> Msg)) r ) => Local UserId -> ConnId -> @@ -434,7 +439,8 @@ updateConversationMessageTimerUnqualified :: Member ExternalAccess r, Member FederatorAccess r, Member GundeckAccess r, - Member (Input UTCTime) r + Member (Input UTCTime) r, + Member (Logger (Msg -> Msg)) r ) => Local UserId -> ConnId -> @@ -455,7 +461,8 @@ deleteLocalConversation :: Member FederatorAccess r, Member GundeckAccess r, Member (Input UTCTime) r, - Member TeamStore r + Member TeamStore r, + Member (Logger (Msg -> Msg)) r ) => Local UserId -> ConnId -> @@ -468,6 +475,30 @@ deleteLocalConversation lusr con lcnv = getUpdateResult :: Sem (Error NoChanges ': r) a -> Sem r (UpdateResult a) getUpdateResult = fmap (either (const Unchanged) Updated) . runError +addCodeUnqualifiedWithReqBody :: + forall db r. + ( Member CodeStore r, + Member ConversationStore r, + Member (ErrorS 'ConvAccessDenied) r, + Member (ErrorS 'ConvNotFound) r, + Member (ErrorS 'GuestLinksDisabled) r, + Member (ErrorS 'CreateConversationCodeConflict) r, + Member ExternalAccess r, + Member GundeckAccess r, + Member (Input (Local ())) r, + Member (Input UTCTime) r, + Member (Embed IO) r, + Member (Input Opts) r, + Member (TeamFeatureStore db) r, + FeaturePersistentConstraint db GuestLinksConfig + ) => + UserId -> + Maybe ConnId -> + ConvId -> + CreateConversationCodeRequest -> + Sem r AddCodeResult +addCodeUnqualifiedWithReqBody usr mZcon cnv req = addCodeUnqualified @db (Just req) usr mZcon cnv + addCodeUnqualified :: forall db r. ( Member CodeStore r, @@ -475,22 +506,25 @@ addCodeUnqualified :: Member (ErrorS 'ConvAccessDenied) r, Member (ErrorS 'ConvNotFound) r, Member (ErrorS 'GuestLinksDisabled) r, + Member (ErrorS 'CreateConversationCodeConflict) r, Member ExternalAccess r, Member GundeckAccess r, Member (Input (Local ())) r, Member (Input UTCTime) r, Member (Input Opts) r, + Member (Embed IO) r, Member (TeamFeatureStore db) r, FeaturePersistentConstraint db GuestLinksConfig ) => + Maybe CreateConversationCodeRequest -> UserId -> - ConnId -> + Maybe ConnId -> ConvId -> Sem r AddCodeResult -addCodeUnqualified usr zcon cnv = do +addCodeUnqualified mReq usr mZcon cnv = do lusr <- qualifyLocal usr lcnv <- qualifyLocal cnv - addCode @db lusr zcon lcnv + addCode @db lusr mZcon lcnv mReq addCode :: forall db r. @@ -499,18 +533,21 @@ addCode :: Member (ErrorS 'ConvNotFound) r, Member (ErrorS 'ConvAccessDenied) r, Member (ErrorS 'GuestLinksDisabled) r, + Member (ErrorS 'CreateConversationCodeConflict) r, Member ExternalAccess r, Member GundeckAccess r, Member (Input UTCTime) r, Member (Input Opts) r, Member (TeamFeatureStore db) r, + Member (Embed IO) r, FeaturePersistentConstraint db GuestLinksConfig ) => Local UserId -> - ConnId -> + Maybe ConnId -> Local ConvId -> + Maybe CreateConversationCodeRequest -> Sem r AddCodeResult -addCode lusr zcon lcnv = do +addCode lusr mZcon lcnv mReq = do conv <- E.getConversation (tUnqualified lcnv) >>= noteS @'ConvNotFound Query.ensureGuestLinksEnabled @db (Data.convTeam conv) Query.ensureConvAdmin (Data.convLocalMembers conv) (tUnqualified lusr) @@ -522,19 +559,21 @@ addCode lusr zcon lcnv = do case mCode of Nothing -> do code <- E.generateCode (tUnqualified lcnv) ReusableCode (Timeout 3600 * 24 * 365) -- one year FUTUREWORK: configurable - E.createCode code + mPw <- forM ((.password) =<< mReq) mkSafePassword + E.createCode code mPw now <- input - conversationCode <- createCode code + conversationCode <- createCode (isJust mPw) code let event = Event (tUntagged lcnv) Nothing (tUntagged lusr) now (EdConvCodeUpdate conversationCode) - pushConversationEvent (Just zcon) event (qualifyAs lusr (map lmId users)) bots + pushConversationEvent mZcon event (qualifyAs lusr (map lmId users)) bots pure $ CodeAdded event - Just code -> do - conversationCode <- createCode code + Just (code, mPw) -> do + when (isJust mPw || isJust (mReq >>= (.password))) $ throwS @'CreateConversationCodeConflict + conversationCode <- createCode (isJust mPw) code pure $ CodeAlreadyExisted conversationCode where - createCode :: Code -> Sem r ConversationCode - createCode code = do - mkConversationCode (codeKey code) (codeValue code) <$> E.getConversationCodeURI + createCode :: Bool -> Code -> Sem r ConversationCodeInfo + createCode hasPw code = do + mkConversationCodeInfo hasPw (codeKey code) (codeValue code) <$> E.getConversationCodeURI ensureGuestsOrNonTeamMembersAllowed :: Data.Conversation -> Sem r () ensureGuestsOrNonTeamMembersAllowed conv = unless @@ -601,7 +640,7 @@ getCode :: ) => Local UserId -> ConvId -> - Sem r ConversationCode + Sem r ConversationCodeInfo getCode lusr cnv = do conv <- E.getConversation cnv >>= noteS @'ConvNotFound @@ -609,12 +648,8 @@ getCode lusr cnv = do ensureAccess conv CodeAccess ensureConvMember (Data.convLocalMembers conv) (tUnqualified lusr) key <- E.makeKey cnv - c <- E.getCode key ReusableCode >>= noteS @'CodeNotFound - returnCode c - -returnCode :: Member CodeStore r => Code -> Sem r ConversationCode -returnCode c = do - mkConversationCode (codeKey c) (codeValue c) <$> E.getConversationCodeURI + (c, mPw) <- E.getCode key ReusableCode >>= noteS @'CodeNotFound + mkConversationCodeInfo (isJust mPw) (codeKey c) (codeValue c) <$> E.getConversationCodeURI checkReusableCode :: forall db r. @@ -623,13 +658,14 @@ checkReusableCode :: Member (TeamFeatureStore db) r, Member (ErrorS 'CodeNotFound) r, Member (ErrorS 'ConvNotFound) r, + Member (ErrorS 'InvalidConversationPassword) r, Member (Input Opts) r, FeaturePersistentConstraint db GuestLinksConfig ) => ConversationCode -> Sem r () checkReusableCode convCode = do - code <- verifyReusableCode convCode + code <- verifyReusableCode False Nothing convCode conv <- E.getConversation (codeConversation code) >>= noteS @'ConvNotFound mapErrorS @'GuestLinksDisabled @'CodeNotFound $ Query.ensureGuestLinksEnabled @db (Data.convTeam conv) @@ -640,6 +676,7 @@ joinConversationByReusableCode :: Member CodeStore r, Member ConversationStore r, Member (ErrorS 'CodeNotFound) r, + Member (ErrorS 'InvalidConversationPassword) r, Member (ErrorS 'ConvAccessDenied) r, Member (ErrorS 'ConvNotFound) r, Member (ErrorS 'GuestLinksDisabled) r, @@ -654,14 +691,15 @@ joinConversationByReusableCode :: Member MemberStore r, Member TeamStore r, Member (TeamFeatureStore db) r, + Member (Logger (Msg -> Msg)) r, FeaturePersistentConstraint db GuestLinksConfig ) => Local UserId -> ConnId -> - ConversationCode -> + JoinConversationByCode -> Sem r (UpdateResult Event) -joinConversationByReusableCode lusr zcon convCode = do - c <- verifyReusableCode convCode +joinConversationByReusableCode lusr zcon req = do + c <- verifyReusableCode True req.password req.code conv <- E.getConversation (codeConversation c) >>= noteS @'ConvNotFound Query.ensureGuestLinksEnabled @db (Data.convTeam conv) joinConversation lusr zcon conv CodeAccess @@ -681,7 +719,8 @@ joinConversationById :: Member (Input Opts) r, Member (Input UTCTime) r, Member MemberStore r, - Member TeamStore r + Member TeamStore r, + Member (Logger (Msg -> Msg)) r ) => Local UserId -> ConnId -> @@ -704,7 +743,8 @@ joinConversation :: Member (Input Opts) r, Member (Input UTCTime) r, Member MemberStore r, - Member TeamStore r + Member TeamStore r, + Member (Logger (Msg -> Msg)) r ) => Local UserId -> ConnId -> @@ -726,6 +766,7 @@ joinConversation lusr zcon conv access = do addMembersToLocalConversation lcnv (UserList users []) roleNameWireMember lcuEvent <$> notifyConversationAction + False (sing @'ConversationJoinTag) (tUntagged lusr) False @@ -918,7 +959,8 @@ updateOtherMemberLocalConv :: Member FederatorAccess r, Member GundeckAccess r, Member (Input UTCTime) r, - Member MemberStore r + Member MemberStore r, + Member (Logger (Msg -> Msg)) r ) => Local ConvId -> Local UserId -> @@ -943,7 +985,8 @@ updateOtherMemberUnqualified :: Member FederatorAccess r, Member GundeckAccess r, Member (Input UTCTime) r, - Member MemberStore r + Member MemberStore r, + Member (Logger (Msg -> Msg)) r ) => Local UserId -> ConnId -> @@ -968,7 +1011,8 @@ updateOtherMember :: Member FederatorAccess r, Member GundeckAccess r, Member (Input UTCTime) r, - Member MemberStore r + Member MemberStore r, + Member (Logger (Msg -> Msg)) r ) => Local UserId -> ConnId -> @@ -1277,7 +1321,8 @@ updateConversationName :: Member ExternalAccess r, Member FederatorAccess r, Member GundeckAccess r, - Member (Input UTCTime) r + Member (Input UTCTime) r, + Member (Logger (Msg -> Msg)) r ) => Local UserId -> ConnId -> @@ -1301,7 +1346,8 @@ updateUnqualifiedConversationName :: Member ExternalAccess r, Member FederatorAccess r, Member GundeckAccess r, - Member (Input UTCTime) r + Member (Input UTCTime) r, + Member (Logger (Msg -> Msg)) r ) => Local UserId -> ConnId -> @@ -1321,7 +1367,8 @@ updateLocalConversationName :: Member ExternalAccess r, Member FederatorAccess r, Member GundeckAccess r, - Member (Input UTCTime) r + Member (Input UTCTime) r, + Member (Logger (Msg -> Msg)) r ) => Local UserId -> ConnId -> diff --git a/services/galley/src/Galley/API/Util.hs b/services/galley/src/Galley/API/Util.hs index 33ce13bbeb..040b327a17 100644 --- a/services/galley/src/Galley/API/Util.hs +++ b/services/galley/src/Galley/API/Util.hs @@ -29,7 +29,7 @@ import Data.Id as Id import Data.LegalHold (UserLegalHoldStatus (..), defUserLegalHoldStatus) import Data.List.Extra (chunksOf, nubOrd) import qualified Data.Map as Map -import Data.Misc (PlainTextPassword (..)) +import Data.Misc (PlainTextPassword6, PlainTextPassword8) import Data.Qualified import qualified Data.Set as Set import Data.Singletons @@ -76,6 +76,7 @@ import Wire.API.Event.Conversation import Wire.API.Federation.API import Wire.API.Federation.API.Galley import Wire.API.Federation.Error +import Wire.API.Password (verifyPassword) import Wire.API.Routes.Public.Galley.Conversation import Wire.API.Routes.Public.Util import Wire.API.Team.Member @@ -176,7 +177,7 @@ ensureReAuthorised :: Member (Error AuthenticationError) r ) => UserId -> - Maybe PlainTextPassword -> + Maybe PlainTextPassword6 -> Maybe Code.Value -> Maybe VerificationAction -> Sem r () @@ -594,16 +595,25 @@ pushConversationEvent conn e lusers bots = do verifyReusableCode :: ( Member CodeStore r, - Member (ErrorS 'CodeNotFound) r + Member (ErrorS 'CodeNotFound) r, + Member (ErrorS 'InvalidConversationPassword) r ) => + Bool -> + Maybe PlainTextPassword8 -> ConversationCode -> Sem r DataTypes.Code -verifyReusableCode convCode = do - c <- +verifyReusableCode checkPw mPtpw convCode = do + (c, mPw) <- getCode (conversationKey convCode) DataTypes.ReusableCode >>= noteS @'CodeNotFound unless (DataTypes.codeValue c == conversationCode convCode) $ throwS @'CodeNotFound + case (checkPw, mPtpw, mPw) of + (True, Just ptpw, Just pw) -> + unless (verifyPassword ptpw pw) $ throwS @'InvalidConversationPassword + (True, Nothing, Just _) -> + throwS @'InvalidConversationPassword + (_, _, _) -> pure () pure c ensureConversationAccess :: @@ -747,7 +757,9 @@ fromConversationCreated loc rc@ConversationCreated {..} = (ConvMembers this others) ProtocolProteus --- | Notify remote users of being added to a new conversation +-- | Notify remote users of being added to a new conversation. The return value +-- consists of users that could not be notified; they will not be considered to +-- be conversation members. registerRemoteConversationMemberships :: (Member FederatorAccess r) => -- | The time stamp when the conversation was created @@ -755,12 +767,16 @@ registerRemoteConversationMemberships :: -- | The domain of the user that created the conversation Domain -> Data.Conversation -> - Sem r () + Sem r (Set (Remote UserId)) registerRemoteConversationMemberships now localDomain c = do let allRemoteMembers = nubOrd (map rmId (Data.convRemoteMembers c)) rc = toConversationCreated now localDomain c - runFederatedConcurrently_ allRemoteMembers $ \_ -> + fmap toSet $ runFederatedConcurrentlyEither allRemoteMembers $ \_ -> fedClient @'Galley @"on-conversation-created" rc + where + toSet :: forall a x e. Ord x => [Either (Remote [x], e) a] -> Set (Remote x) + toSet = + Set.fromList . foldMap (either (sequenceA . fst) mempty) -------------------------------------------------------------------------------- -- Legalhold diff --git a/services/galley/src/Galley/Cassandra.hs b/services/galley/src/Galley/Cassandra.hs index 8d75052d2d..093120d6a1 100644 --- a/services/galley/src/Galley/Cassandra.hs +++ b/services/galley/src/Galley/Cassandra.hs @@ -20,4 +20,4 @@ module Galley.Cassandra (schemaVersion) where import Imports schemaVersion :: Int32 -schemaVersion = 79 +schemaVersion = 80 diff --git a/services/galley/src/Galley/Cassandra/Code.hs b/services/galley/src/Galley/Cassandra/Code.hs index 754df8e747..c205733718 100644 --- a/services/galley/src/Galley/Cassandra/Code.hs +++ b/services/galley/src/Galley/Cassandra/Code.hs @@ -33,6 +33,7 @@ import Galley.Options import Imports import Polysemy import Polysemy.Input +import Wire.API.Password interpretCodeStoreToCassandra :: ( Member (Embed IO) r, @@ -43,7 +44,7 @@ interpretCodeStoreToCassandra :: Sem r a interpretCodeStoreToCassandra = interpret $ \case GetCode k s -> embedClient $ lookupCode k s - CreateCode code -> embedClient $ insertCode code + CreateCode code mPw -> embedClient $ insertCode code mPw DeleteCode k s -> embedClient $ deleteCode k s MakeKey cid -> Code.mkKey cid GenerateCode cid s t -> Code.generate cid s t @@ -51,18 +52,19 @@ interpretCodeStoreToCassandra = interpret $ \case view (options . optSettings . setConversationCodeURI) <$> input -- | Insert a conversation code -insertCode :: Code -> Client () -insertCode c = do +insertCode :: Code -> Maybe Password -> Client () +insertCode c mPw = do let k = codeKey c let v = codeValue c let cnv = codeConversation c let t = round (codeTTL c) let s = codeScope c - retry x5 (write Cql.insertCode (params LocalQuorum (k, v, cnv, s, t))) + retry x5 (write Cql.insertCode (params LocalQuorum (k, v, cnv, s, mPw, t))) -- | Lookup a conversation by code. -lookupCode :: Key -> Scope -> Client (Maybe Code) -lookupCode k s = fmap (toCode k s) <$> retry x1 (query1 Cql.lookupCode (params LocalQuorum (k, s))) +lookupCode :: Key -> Scope -> Client (Maybe (Code, Maybe Password)) +lookupCode k s = + fmap (toCode k s) <$> retry x1 (query1 Cql.lookupCode (params LocalQuorum (k, s))) -- | Delete a code associated with the given conversation key deleteCode :: Key -> Scope -> Client () diff --git a/services/galley/src/Galley/Cassandra/Queries.hs b/services/galley/src/Galley/Cassandra/Queries.hs index 6796456070..db3b5b91a8 100644 --- a/services/galley/src/Galley/Cassandra/Queries.hs +++ b/services/galley/src/Galley/Cassandra/Queries.hs @@ -36,6 +36,7 @@ import Wire.API.Conversation.Role import Wire.API.MLS.CipherSuite import Wire.API.MLS.KeyPackage import Wire.API.MLS.PublicGroupState +import Wire.API.Password (Password) import Wire.API.Provider import Wire.API.Provider.Service import Wire.API.Routes.Internal.Galley.TeamsIntra @@ -285,11 +286,11 @@ updatePublicGroupState = "update conversation set public_group_state = ? where c -- Conversations accessible by code ----------------------------------------- -insertCode :: PrepQuery W (Key, Value, ConvId, Scope, Int32) () -insertCode = "INSERT INTO conversation_codes (key, value, conversation, scope) VALUES (?, ?, ?, ?) USING TTL ?" +insertCode :: PrepQuery W (Key, Value, ConvId, Scope, Maybe Password, Int32) () +insertCode = "INSERT INTO conversation_codes (key, value, conversation, scope, password) VALUES (?, ?, ?, ?, ?) USING TTL ?" -lookupCode :: PrepQuery R (Key, Scope) (Value, Int32, ConvId) -lookupCode = "SELECT value, ttl(value), conversation FROM conversation_codes WHERE key = ? AND scope = ?" +lookupCode :: PrepQuery R (Key, Scope) (Value, Int32, ConvId, Maybe Password) +lookupCode = "SELECT value, ttl(value), conversation, password FROM conversation_codes WHERE key = ? AND scope = ?" deleteCode :: PrepQuery W (Key, Scope) () deleteCode = "DELETE FROM conversation_codes WHERE key = ? AND scope = ?" diff --git a/services/galley/src/Galley/Data/Types.hs b/services/galley/src/Galley/Data/Types.hs index a314af11db..53fe356379 100644 --- a/services/galley/src/Galley/Data/Types.hs +++ b/services/galley/src/Galley/Data/Types.hs @@ -41,6 +41,7 @@ import Galley.Data.Scope import Imports import OpenSSL.EVP.Digest (digestBS, getDigestByName) import OpenSSL.Random (randBytes) +import Wire.API.Password (Password) -------------------------------------------------------------------------------- -- Code @@ -50,19 +51,23 @@ data Code = Code codeValue :: !Value, codeTTL :: !Timeout, codeConversation :: !ConvId, - codeScope :: !Scope + codeScope :: !Scope, + codeHasPassword :: !Bool } deriving (Eq, Show, Generic) -toCode :: Key -> Scope -> (Value, Int32, ConvId) -> Code -toCode k s (val, ttl, cnv) = - Code - { codeKey = k, - codeValue = val, - codeTTL = Timeout (fromIntegral ttl), - codeConversation = cnv, - codeScope = s - } +toCode :: Key -> Scope -> (Value, Int32, ConvId, Maybe Password) -> (Code, Maybe Password) +toCode k s (val, ttl, cnv, mPw) = + ( Code + { codeKey = k, + codeValue = val, + codeTTL = Timeout (fromIntegral ttl), + codeConversation = cnv, + codeScope = s, + codeHasPassword = isJust mPw + }, + mPw + ) -- Note on key/value used for a conversation Code -- @@ -81,7 +86,8 @@ generate cnv s t = do codeValue = val, codeConversation = cnv, codeTTL = t, - codeScope = s + codeScope = s, + codeHasPassword = False } mkKey :: MonadIO m => ConvId -> m Key diff --git a/services/galley/src/Galley/Effects/BrigAccess.hs b/services/galley/src/Galley/Effects/BrigAccess.hs index 570672a0b9..c4cd5d8d9d 100644 --- a/services/galley/src/Galley/Effects/BrigAccess.hs +++ b/services/galley/src/Galley/Effects/BrigAccess.hs @@ -119,7 +119,7 @@ data BrigAccess m a where BrigAccess m () GetLegalHoldAuthToken :: UserId -> - Maybe PlainTextPassword -> + Maybe PlainTextPassword6 -> BrigAccess m OpaqueAuthToken AddLegalHoldClientToUserEither :: UserId -> diff --git a/services/galley/src/Galley/Effects/CodeStore.hs b/services/galley/src/Galley/Effects/CodeStore.hs index c4662be23e..88b31b0dfc 100644 --- a/services/galley/src/Galley/Effects/CodeStore.hs +++ b/services/galley/src/Galley/Effects/CodeStore.hs @@ -45,10 +45,11 @@ import Data.Misc import Galley.Data.Types import Imports import Polysemy +import Wire.API.Password data CodeStore m a where - CreateCode :: Code -> CodeStore m () - GetCode :: Key -> Scope -> CodeStore m (Maybe Code) + CreateCode :: Code -> Maybe Password -> CodeStore m () + GetCode :: Key -> Scope -> CodeStore m (Maybe (Code, Maybe Password)) DeleteCode :: Key -> Scope -> CodeStore m () MakeKey :: ConvId -> CodeStore m Key GenerateCode :: ConvId -> Scope -> Timeout -> CodeStore m Code diff --git a/services/galley/src/Galley/Intra/Client.hs b/services/galley/src/Galley/Intra/Client.hs index 7a6f0558bf..ccded78334 100644 --- a/services/galley/src/Galley/Intra/Client.hs +++ b/services/galley/src/Galley/Intra/Client.hs @@ -116,7 +116,7 @@ getLegalHoldAuthToken :: Member (Input Env) r ) => UserId -> - Maybe PlainTextPassword -> + Maybe PlainTextPassword6 -> Sem r OpaqueAuthToken getLegalHoldAuthToken uid pw = do r <- diff --git a/services/galley/src/Galley/Options.hs b/services/galley/src/Galley/Options.hs index 844ca39064..d9776451eb 100644 --- a/services/galley/src/Galley/Options.hs +++ b/services/galley/src/Galley/Options.hs @@ -101,7 +101,7 @@ data Settings = Settings -- It should also match the SRV DNS records under which other wire-server installations can find this backend: -- _wire-server-federator._tcp. -- Once set, DO NOT change it: if you do, existing users may have a broken experience and/or stop working - -- Remember to keep it the same in Galley. + -- Remember to keep it the same in all services. -- Example: -- allowedDomains: -- - wire.com diff --git a/services/galley/test/integration/API.hs b/services/galley/test/integration/API.hs index 9f28461430..5689797051 100644 --- a/services/galley/test/integration/API.hs +++ b/services/galley/test/integration/API.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE OverloadedRecordDot #-} {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} -- This file is part of the Wire Server implementation. @@ -24,6 +25,7 @@ where import qualified API.CustomBackend as CustomBackend import qualified API.Federation as Federation +import API.Federation.Util import qualified API.MLS import qualified API.MessageTimer as MessageTimer import qualified API.Roles as Roles @@ -39,6 +41,7 @@ import qualified API.Util.TeamFeature as Util import Bilge hiding (head, timeout) import qualified Bilge import Bilge.Assert +import Control.Arrow import qualified Control.Concurrent.Async as Async import Control.Exception (throw) import Control.Lens (at, ix, preview, view, (.~), (?~)) @@ -55,6 +58,7 @@ import Data.List.NonEmpty (NonEmpty (..)) import Data.List1 hiding (head) import qualified Data.List1 as List1 import qualified Data.Map.Strict as Map +import Data.Misc import Data.Qualified import Data.Range import qualified Data.Set as Set @@ -81,6 +85,7 @@ import Util.Options (Endpoint (Endpoint)) import Wire.API.Connection import Wire.API.Conversation import Wire.API.Conversation.Action +import Wire.API.Conversation.Code hiding (Value) import Wire.API.Conversation.Protocol import Wire.API.Conversation.Role import Wire.API.Conversation.Typing @@ -126,7 +131,8 @@ tests s = test s "metrics" metrics, test s "fetch conversation by qualified ID (v2)" testGetConvQualifiedV2, test s "create Proteus conversation" postProteusConvOk, - test s "create conversation with remote users" postConvWithRemoteUsersOk, + test s "create conversation with remote users" (postConvWithRemoteUsersOk $ Set.fromList [rb1, rb2]), + test s "create conversation with remote users (some unreachable)" (postConvWithRemoteUsersOk $ Set.fromList [rb1, rb2, rb3]), test s "get empty conversations" getConvsOk, test s "get conversations by ids" getConvsOk2, test s "fail to get >500 conversations with v2 API" getConvsFailMaxSizeV2, @@ -147,7 +153,7 @@ tests s = test s "fail to create conversation when blocked by qualified member" postConvQualifiedFailBlocked, test s "fail to create conversation with remote users when remote user is not connected" postConvQualifiedNoConnection, test s "fail to create team conversation with remote users when remote user is not connected" postTeamConvQualifiedNoConnection, - test s "fail to create conversation with remote users when remote user's domain doesn't exist" postConvQualifiedNonExistentDomain, + test s "create conversation with optional remote users when remote user's domain doesn't exist" postConvQualifiedNonExistentDomain, test s "fail to create conversation with remote users when federation not configured" postConvQualifiedFederationNotEnabled, test s "create self conversation" postSelfConvOk, test s "create 1:1 conversation" postO2OConvOk, @@ -171,6 +177,7 @@ tests s = test s "fail to add too many members" postTooManyMembersFail, test s "add remote members" testAddRemoteMember, test s "delete conversation with remote members" testDeleteTeamConversationWithRemoteMembers, + test s "delete conversation with unavailable remote members" testDeleteTeamConversationWithUnavailableRemoteMembers, test s "get conversations/:domain/:cnv - local" testGetQualifiedLocalConv, test s "get conversations/:domain/:cnv - local, not found" testGetQualifiedLocalConvNotFound, test s "get conversations/:domain/:cnv - local, not participating" testGetQualifiedLocalConvNotParticipating, @@ -186,6 +193,7 @@ tests s = test s "delete conversations/:domain/:cnv/members/:domain/:usr - local conv with all locals" deleteMembersConvLocalQualifiedOk, test s "delete conversations/:domain/:cnv/members/:domain/:usr - local conv with locals and remote, delete local" deleteLocalMemberConvLocalQualifiedOk, test s "delete conversations/:domain/:cnv/members/:domain/:usr - local conv with locals and remote, delete remote" deleteRemoteMemberConvLocalQualifiedOk, + test s "delete conversations/:domain/:cnv/members/:domain/:usr - local conv with locals and remote, delete unavailable remote" deleteUnavailableRemoteMemberConvLocalQualifiedOk, test s "delete conversations/:domain/:cnv/members/:domain/:usr - remote conv, leave conv" leaveRemoteConvQualifiedOk, test s "delete conversations/:domain/:cnv/members/:domain/:usr - remote conv, leave conv, non-existent" leaveNonExistentRemoteConv, test s "delete conversations/:domain/:cnv/members/:domain/:usr - remote conv, leave conv, denied" leaveRemoteConvDenied, @@ -220,11 +228,13 @@ tests s = test s "post message qualified - local owning backend - redundant and deleted clients" postMessageQualifiedLocalOwningBackendRedundantAndDeletedClients, test s "post message qualified - local owning backend - ignore missing" postMessageQualifiedLocalOwningBackendIgnoreMissingClients, test s "post message qualified - local owning backend - failed to send clients" postMessageQualifiedLocalOwningBackendFailedToSendClients, + test s "post message qualified - local owning backend - failed to get clients and failed to send clients" postMessageQualifiedLocalOwningBackendFailedToSendClientsFailingGetUserClients, test s "post message qualified - remote owning backend - federation failure" postMessageQualifiedRemoteOwningBackendFailure, test s "post message qualified - remote owning backend - success" postMessageQualifiedRemoteOwningBackendSuccess, test s "join conversation" postJoinConvOk, test s "get code-access conversation information" testJoinCodeConv, - test s "join code-access conversation" postJoinCodeConvOk, + test s "join code-access conversation - no password" postJoinCodeConvOk, + test s "join code-access conversation - password" postJoinCodeConvWithPassword, test s "convert invite to code-access conversation" postConvertCodeConv, test s "convert code to team-access conversation" postConvertTeamConv, test s "local and remote guests are removed when access changes" testAccessUpdateGuestRemoved, @@ -234,6 +244,13 @@ tests s = test s "revoke guest links for non-team conversation" testJoinNonTeamConvGuestLinksDisabled, test s "get code rejected if guest links disabled" testGetCodeRejectedIfGuestLinksDisabled, test s "post code rejected if guest links disabled" testPostCodeRejectedIfGuestLinksDisabled, + testGroup + "conversation code already exists" + [ test s "existing has no password, requested has no password - 201" postCodeWithoutPasswordExistsWithoutPasswordRequested, + test s "existing has no password, requested has password - 409" postCodeWithoutPasswordExistsWithPasswordRequested, + test s "existing has password, requested has no password - 409" postCodeWithPasswordExistsWithoutPasswordRequested, + test s "existing has password, requested has password - 409" postCodeWithPasswordExistsWithPasswordRequested + ], test s "remove user with only local convs" removeUserNoFederation, test s "remove user with local and remote convs" removeUser, test s "iUpsertOne2OneConversation" testAllOne2OneConversationRequests, @@ -247,6 +264,31 @@ tests s = test s "send typing indicators with invalid pyaload" postTypingIndicatorsHandlesNonsense ] ] + rb1, rb2, rb3 :: Remote Backend + rb1 = + toRemoteUnsafe + (Domain "c.example.com") + ( Backend + { bReachable = BackendReachable, + bUsers = 2 + } + ) + rb2 = + toRemoteUnsafe + (Domain "d.example.com") + ( Backend + { bReachable = BackendReachable, + bUsers = 1 + } + ) + rb3 = + toRemoteUnsafe + (Domain "e.example.com") + ( Backend + { bReachable = BackendUnreachable, + bUsers = 2 + } + ) ------------------------------------------------------------------------------- -- API Tests @@ -318,56 +360,101 @@ postProteusConvOk = do EdConversation c' -> assertConvEquals cnv c' _ -> assertFailure "Unexpected event data" -postConvWithRemoteUsersOk :: TestM () -postConvWithRemoteUsersOk = do +postConvWithRemoteUsersOk :: Set (Remote Backend) -> TestM () +postConvWithRemoteUsersOk rbs = do c <- view tsCannon (alice, qAlice) <- randomUserTuple (alex, qAlex) <- randomUserTuple (amy, qAmy) <- randomUserTuple connectUsers alice (list1 alex [amy]) - let cDomain = Domain "c.example.com" - dDomain = Domain "d.example.com" - qChad <- randomQualifiedId cDomain - qCharlie <- randomQualifiedId cDomain - qDee <- randomQualifiedId dDomain - mapM_ (connectWithRemoteUser alice) [qChad, qCharlie, qDee] + (allRemotes, participatingRemotes) <- do + v <- forM (toList rbs) $ \rb -> do + users <- connectBackend alice rb + pure (users, participating rb users) + pure $ foldr (\(a, p) acc -> bimap ((<>) a) ((<>) p) acc) ([], []) v let nameMaxSize = T.replicate 256 "a" + otherLocals = [qAlex, qAmy] WS.bracketR3 c alice alex amy $ \(wsAlice, wsAlex, wsAmy) -> do + let joiners = allRemotes <> otherLocals + unreachableBackends = + Set.fromList $ + foldMap + ( \rb -> + guard (rbReachable rb == BackendUnreachable) + $> tDomain rb + ) + rbs (rsp, federatedRequests) <- - withTempMockFederator' (mockReply ()) $ - postConvQualified alice Nothing defNewProteusConv {newConvName = checked nameMaxSize, newConvQualifiedUsers = [qAlex, qAmy, qChad, qCharlie, qDee]} + withTempMockFederator' (mockUnreachable unreachableBackends) $ + postConvQualified + alice + Nothing + defNewProteusConv + { newConvName = checked nameMaxSize, + newConvQualifiedUsers = joiners + } participatingRemotes + shouldBePresentSet = Set.fromList (toOtherMember <$> shouldBePresent) + qcid <- + assertConv + rsp + RegularConv + alice + qAlice + shouldBePresent + (Just nameMaxSize) + Nothing let cid = qUnqualified qcid - cvs <- mapM (convView cid) [alice, alex, amy] - liftIO $ mapM_ WS.assertSuccess =<< Async.mapConcurrently (checkWs qAlice) (zip cvs [wsAlice, wsAlex, wsAmy]) - - cFedReq <- assertOne $ filter (\r -> frTargetDomain r == cDomain) federatedRequests - cFedReqBody <- assertRight $ parseFedRequest cFedReq + cvs <- mapM (convView qcid) [alice, alex, amy] + liftIO $ + mapM_ WS.assertSuccess + =<< Async.mapConcurrently (checkWs qAlice) (zip cvs [wsAlice, wsAlex, wsAmy]) - dFedReq <- assertOne $ filter (\r -> frTargetDomain r == dDomain) federatedRequests - dFedReqBody <- assertRight $ parseFedRequest dFedReq + liftIO $ + -- as many federated requests as remote backends, i.e., one per remote backend + Set.size (Set.fromList $ frTargetDomain <$> federatedRequests) @?= Set.size rbs + let fedReq = head federatedRequests + fedReqBody <- assertRight $ parseFedRequest fedReq liftIO $ do - length federatedRequests @?= 2 - - F.ccOrigUserId cFedReqBody @?= alice - F.ccCnvId cFedReqBody @?= cid - F.ccCnvType cFedReqBody @?= RegularConv - F.ccCnvAccess cFedReqBody @?= [InviteAccess] - F.ccCnvAccessRoles cFedReqBody @?= Set.fromList [TeamMemberAccessRole, NonTeamMemberAccessRole, ServiceAccessRole] - F.ccCnvName cFedReqBody @?= Just nameMaxSize - F.ccNonCreatorMembers cFedReqBody @?= Set.fromList (toOtherMember <$> [qAlex, qAmy, qChad, qCharlie, qDee]) - F.ccMessageTimer cFedReqBody @?= Nothing - F.ccReceiptMode cFedReqBody @?= Nothing - - dFedReqBody @?= cFedReqBody + length federatedRequests @?= Set.size rbs + + F.ccOrigUserId fedReqBody @?= alice + F.ccCnvId fedReqBody @?= cid + F.ccCnvType fedReqBody @?= RegularConv + F.ccCnvAccess fedReqBody @?= [InviteAccess] + F.ccCnvAccessRoles fedReqBody + @?= Set.fromList [TeamMemberAccessRole, NonTeamMemberAccessRole, ServiceAccessRole] + F.ccCnvName fedReqBody @?= Just nameMaxSize + assertBool "Notifying an incorrect set of conversation members" $ + shouldBePresentSet `Set.isSubsetOf` F.ccNonCreatorMembers fedReqBody + F.ccMessageTimer fedReqBody @?= Nothing + F.ccReceiptMode fedReqBody @?= Nothing + + fedBodies <- forM federatedRequests $ assertRight . parseFedRequest + assertBool "Not all request bodies are equal" (all (fedReqBody ==) fedBodies) where + mockUnreachable :: Set Domain -> Mock LByteString + mockUnreachable unreachable = do + r <- getRequest + if Set.member (frTargetDomain r) unreachable + then throw (MockErrorResponse HTTP.status503 "Down for maintenance.") + else mockReply () + connectBackend :: UserId -> Remote Backend -> TestM [Qualified UserId] + connectBackend usr (tDomain &&& bUsers . tUnqualified -> (d, c)) = do + users <- replicateM (fromIntegral c) (randomQualifiedId d) + mapM_ (connectWithRemoteUser usr) users + pure users + participating :: Remote Backend -> [a] -> [a] + participating rb users = + if rbReachable rb == BackendReachable + then users + else [] toOtherMember qid = OtherMember qid Nothing roleNameWireAdmin - convView cnv usr = do - r <- getConv usr cnv responseJsonError r + convView cnv usr = + responseJsonError =<< getConvQualified usr cnv do ntfTransient n @?= False let e = List1.head (WS.unpackPayload n) @@ -1151,6 +1238,103 @@ postMessageQualifiedLocalOwningBackendFailedToSendClients = do WS.assertMatch_ t wsBob (wsAssertOtr' encodedData convId aliceOwningDomain aliceClient bobClient encodedTextForBob) WS.assertMatch_ t wsChad (wsAssertOtr' encodedData convId aliceOwningDomain aliceClient chadClient encodedTextForChad) +-- This test is similar to postMessageQualifiedLocalOwningBackendFailedToSendClients +-- except that both of the calls to the federated server are failing. +postMessageQualifiedLocalOwningBackendFailedToSendClientsFailingGetUserClients :: TestM () +postMessageQualifiedLocalOwningBackendFailedToSendClientsFailingGetUserClients = do + -- WS receive timeout + let t = 5 # Second + -- Cannon for local users + cannon <- view tsCannon + -- Domain which owns the converstaion + owningDomain <- viewFederationDomain + + (aliceOwningDomain, aliceClient) <- randomUserWithClientQualified (head someLastPrekeys) + (bobOwningDomain, bobClient) <- randomUserWithClientQualified (someLastPrekeys !! 1) + bobClient2 <- randomClient (qUnqualified bobOwningDomain) (someLastPrekeys !! 2) + (chadOwningDomain, chadClient) <- randomUserWithClientQualified (someLastPrekeys !! 3) + deeId <- randomId + deeClient <- liftIO $ generate arbitrary + emilyId <- randomId + emilyClient <- liftIO $ generate arbitrary + let remoteDomain = Domain "far-away.example.com" + deeRemote = Qualified deeId remoteDomain + remoteDomain2 = Domain "far-away2.example.com" + emilyRemote = Qualified emilyId remoteDomain2 + + let aliceUnqualified = qUnqualified aliceOwningDomain + bobUnqualified = qUnqualified bobOwningDomain + chadUnqualified = qUnqualified chadOwningDomain + + connectLocalQualifiedUsers aliceUnqualified (list1 bobOwningDomain [chadOwningDomain]) + connectWithRemoteUser aliceUnqualified deeRemote + connectWithRemoteUser aliceUnqualified emilyRemote + + -- FUTUREWORK: Do this test with more than one remote domains + resp <- + postConvWithRemoteUsers + aliceUnqualified + Nothing + defNewProteusConv {newConvQualifiedUsers = [bobOwningDomain, chadOwningDomain, deeRemote, emilyRemote]} + let convId = (`Qualified` owningDomain) . decodeConvId $ resp + + WS.bracketR2 cannon bobUnqualified chadUnqualified $ \(wsBob, wsChad) -> do + let message = + [ (bobOwningDomain, bobClient, "text-for-bob"), + (bobOwningDomain, bobClient2, "text-for-bob2"), + (chadOwningDomain, chadClient, "text-for-chad"), + (deeRemote, deeClient, "text-for-dee"), + (emilyRemote, emilyClient, "text-for-emily") + ] + + let mock = + ( do + -- Dee is always unavailable, + -- Emily is paritally available + guardRPC "get-user-clients" + d <- frTargetDomain <$> getRequest + if d == remoteDomain + then throw (MockErrorResponse HTTP.status503 "Down for maintenance.") + else mockReply $ UserMap (Map.singleton (qUnqualified emilyRemote) (Set.singleton (PubClient emilyClient Nothing))) + ) + <|> ( guardRPC "on-message-sent" + *> throw (MockErrorResponse HTTP.status503 "Down for maintenance.") + ) + + (resp2, _requests) <- + withTempMockFederator' mock $ + postProteusMessageQualified + aliceUnqualified + aliceClient + convId + message + "data" + Message.MismatchReportAll + + let expectedFailedToSend = + QualifiedUserClients . Map.fromList $ + [ ( remoteDomain, + Map.fromList + [ (deeId, Set.singleton deeClient) + ] + ), + ( remoteDomain2, + Map.fromList + [ (emilyId, Set.singleton emilyClient) + ] + ) + ] + pure resp2 !!! do + const 201 === statusCode + assertMismatchQualified expectedFailedToSend mempty mempty mempty + + liftIO $ do + let encodedTextForBob = toBase64Text "text-for-bob" + encodedTextForChad = toBase64Text "text-for-chad" + encodedData = toBase64Text "data" + WS.assertMatch_ t wsBob (wsAssertOtr' encodedData convId aliceOwningDomain aliceClient bobClient encodedTextForBob) + WS.assertMatch_ t wsChad (wsAssertOtr' encodedData convId aliceOwningDomain aliceClient chadClient encodedTextForChad) + postMessageQualifiedRemoteOwningBackendFailure :: TestM () postMessageQualifiedRemoteOwningBackendFailure = do (aliceLocal, aliceClient) <- randomUserWithClientQualified (head someLastPrekeys) @@ -1227,12 +1411,12 @@ testJoinCodeConv = do Right noGuestsAccess <- liftIO $ genAccessRolesV2 [NonTeamMemberAccessRole] [GuestAccessRole] alice <- randomUser convId <- decodeConvId <$> postConv alice [] (Just convName) [CodeAccess] (Just noGuestsAccess) Nothing - cCode <- decodeConvCodeEvent <$> postConvCode alice convId + cCode <- (.code) . decodeConvCodeEvent <$> postConvCode alice convId qbob <- randomQualifiedUser let bob = qUnqualified qbob getJoinCodeConv bob (conversationKey cCode) (conversationCode cCode) !!! do - const (Right (ConversationCoverView convId (Just convName))) === responseJsonEither + const (Right (ConversationCoverView convId (Just convName) False)) === responseJsonEither -- A user that would not be able to join conversation cannot view it either. eve <- ephemeralUser @@ -1288,7 +1472,7 @@ testJoinTeamConvGuestLinksDisabled = do bob <- randomUser Right accessRoles <- liftIO $ genAccessRolesV2 [TeamMemberAccessRole, NonTeamMemberAccessRole, GuestAccessRole] [] convId <- decodeConvId <$> postTeamConv teamId owner [] (Just convName) [CodeAccess, LinkAccess] (Just accessRoles) Nothing - cCode <- decodeConvCodeEvent <$> postConvCode owner convId + cCode <- (.code) . decodeConvCodeEvent <$> postConvCode owner convId let checkFeatureStatus fstatus = Util.getTeamFeatureFlagWithGalley @Public.GuestLinksConfig galley owner teamId !!! do @@ -1298,7 +1482,7 @@ testJoinTeamConvGuestLinksDisabled = do -- guest can join if guest link feature is enabled checkFeatureStatus Public.FeatureStatusEnabled getJoinCodeConv eve (conversationKey cCode) (conversationCode cCode) !!! do - const (Right (ConversationCoverView convId (Just convName))) === responseJsonEither + const (Right (ConversationCoverView convId (Just convName) False)) === responseJsonEither const 200 === statusCode postConvCodeCheck cCode !!! const 200 === statusCode postJoinCodeConv eve cCode !!! const 200 === statusCode @@ -1329,7 +1513,7 @@ testJoinTeamConvGuestLinksDisabled = do TeamFeatures.putTeamFeatureFlagWithGalley @Public.GuestLinksConfig galley owner teamId enabled !!! do const 200 === statusCode getJoinCodeConv eve' (conversationKey cCode) (conversationCode cCode) !!! do - const (Right (ConversationCoverView convId (Just convName))) === responseJsonEither + const (Right (ConversationCoverView convId (Just convName) False)) === responseJsonEither const 200 === statusCode postConvCodeCheck cCode !!! const 200 === statusCode postJoinCodeConv eve' cCode !!! const 200 === statusCode @@ -1346,11 +1530,11 @@ testJoinNonTeamConvGuestLinksDisabled = do userNotInTeam <- randomUser Right accessRoles <- liftIO $ genAccessRolesV2 [NonTeamMemberAccessRole] [GuestAccessRole] convId <- decodeConvId <$> postConv owner [] (Just convName) [CodeAccess] (Just accessRoles) Nothing - cCode <- decodeConvCodeEvent <$> postConvCode owner convId + cCode <- (.code) . decodeConvCodeEvent <$> postConvCode owner convId -- works by default getJoinCodeConv userNotInTeam (conversationKey cCode) (conversationCode cCode) !!! do - const (Right (ConversationCoverView convId (Just convName))) === responseJsonEither + const (Right (ConversationCoverView convId (Just convName) False)) === responseJsonEither const 200 === statusCode -- for non-team conversations it still works if status is disabled for the team but not server wide @@ -1359,7 +1543,7 @@ testJoinNonTeamConvGuestLinksDisabled = do const 200 === statusCode getJoinCodeConv userNotInTeam (conversationKey cCode) (conversationCode cCode) !!! do - const (Right (ConversationCoverView convId (Just convName))) === responseJsonEither + const (Right (ConversationCoverView convId (Just convName) False)) === responseJsonEither const 200 === statusCode -- @SF.Separation @TSFI.RESTfulAPI @S2 @@ -1381,7 +1565,9 @@ postJoinCodeConvOk = do Right accessRoles <- liftIO $ genAccessRolesV2 [TeamMemberAccessRole, NonTeamMemberAccessRole] [GuestAccessRole] conv <- decodeConvId <$> postConv alice [] (Just "gossip") [CodeAccess] (Just accessRoles) Nothing let qconv = Qualified conv (qDomain qbob) - cCode <- decodeConvCodeEvent <$> postConvCode alice conv + info <- decodeConvCodeEvent <$> postConvCode alice conv + let cCode = info.code + liftIO $ info.hasPassword @?= False -- currently ConversationCode is used both as return type for POST ../code and as body for ../join -- POST /code gives code,key,uri -- POST /join expects code,key @@ -1417,6 +1603,57 @@ postJoinCodeConvOk = do -- @END +postJoinCodeConvWithPassword :: TestM () +postJoinCodeConvWithPassword = do + alice <- randomUser + qbob <- randomQualifiedUser + let bob = qUnqualified qbob + Right accessRoles <- liftIO $ genAccessRolesV2 [TeamMemberAccessRole, NonTeamMemberAccessRole] [GuestAccessRole] + conv <- decodeConvId <$> postConv alice [] (Just "gossip") [CodeAccess] (Just accessRoles) Nothing + let pw = plainTextPassword8Unsafe "password" + info <- decodeConvCodeEvent <$> postConvCode' (Just pw) alice conv + liftIO $ info.hasPassword @?= True + let cCode = info.code + getJoinCodeConv bob (conversationKey cCode) (conversationCode cCode) !!! do + const (Right (ConversationCoverView conv (Just "gossip") True)) === responseJsonEither + const 200 === statusCode + -- join without password should fail + postJoinCodeConv' Nothing bob cCode !!! const 403 === statusCode + -- join with wrong password should fail + postJoinCodeConv' (Just (plainTextPassword8Unsafe "wrong-password")) bob cCode !!! const 403 === statusCode + -- join with correct password should succeed + postJoinCodeConv' (Just pw) bob cCode !!! const 200 === statusCode + +postCodeWithoutPasswordExistsWithoutPasswordRequested :: TestM () +postCodeWithoutPasswordExistsWithoutPasswordRequested = do + alice <- randomUser + conv <- decodeConvId <$> postConv alice [] (Just "gossip") [CodeAccess] (Just (Set.fromList [TeamMemberAccessRole, NonTeamMemberAccessRole, GuestAccessRole])) Nothing + info1 <- decodeConvCodeEvent <$> (postConvCode alice conv (postConvCode alice conv postConv alice [] (Just "gossip") [CodeAccess] (Just (Set.fromList [TeamMemberAccessRole, NonTeamMemberAccessRole, GuestAccessRole])) Nothing + postConvCode' (Just (plainTextPassword8Unsafe "password")) alice conv !!! const 201 === statusCode + postConvCode alice conv !!! const 409 === statusCode + +postCodeWithoutPasswordExistsWithPasswordRequested :: TestM () +postCodeWithoutPasswordExistsWithPasswordRequested = do + alice <- randomUser + conv <- decodeConvId <$> postConv alice [] (Just "gossip") [CodeAccess] (Just (Set.fromList [TeamMemberAccessRole, NonTeamMemberAccessRole, GuestAccessRole])) Nothing + postConvCode alice conv !!! const 201 === statusCode + postConvCode' (Just (plainTextPassword8Unsafe "password")) alice conv !!! const 409 === statusCode + +postCodeWithPasswordExistsWithPasswordRequested :: TestM () +postCodeWithPasswordExistsWithPasswordRequested = do + alice <- randomUser + conv <- decodeConvId <$> postConv alice [] (Just "gossip") [CodeAccess] (Just (Set.fromList [TeamMemberAccessRole, NonTeamMemberAccessRole, GuestAccessRole])) Nothing + postConvCode' (Just (plainTextPassword8Unsafe "password")) alice conv !!! const 201 === statusCode + postConvCode' (Just (plainTextPassword8Unsafe "some-other-password")) alice conv !!! const 409 === statusCode + postConvCode' (Just (plainTextPassword8Unsafe "password")) alice conv !!! const 409 === statusCode + postConvertCodeConv :: TestM () postConvertCodeConv = do c <- view tsCannon @@ -1445,12 +1682,12 @@ postConvertCodeConv = do wsAssertConvAccessUpdate qconv qalice nonActivatedAccess -- Create/get/update/delete codes getConvCode alice conv !!! const 404 === statusCode - c1 <- decodeConvCodeEvent <$> (postConvCode alice conv (postConvCode alice conv (getConvCode alice conv (getConvCode alice conv (postConvCode alice conv (postConvCode alice conv postConvCode alice conv + j <- (.code) . decodeConvCodeEvent <$> postConvCode alice conv WS.bracketR3 c alice bob eve $ \(wsA, wsB, wsE) -> do postJoinCodeConv mallory j !!! const 200 === statusCode void . liftIO $ @@ -1617,7 +1854,7 @@ testTeamMemberCantJoinViaGuestLinkIfAccessRoleRemoved = do -- and given alice and bob are in a team conversation and alice created a guest link let accessRoles = Set.fromList [TeamMemberAccessRole, GuestAccessRole, ServiceAccessRole] qconvId <- decodeQualifiedConvId <$> postTeamConv tid alice [bob] (Just "chit chat") [CodeAccess] (Just accessRoles) Nothing - cCode <- decodeConvCodeEvent <$> postConvCode alice (qUnqualified qconvId) + cCode <- (.code) . decodeConvCodeEvent <$> postConvCode alice (qUnqualified qconvId) -- then charlie can join via the guest link postJoinCodeConv charlie cCode !!! const 200 === statusCode @@ -2115,15 +2352,33 @@ postTeamConvQualifiedNoConnection = do postConvQualifiedNonExistentDomain :: TestM () postConvQualifiedNonExistentDomain = do - alice <- randomUser - bob <- flip Qualified (Domain "non-existent.example.com") <$> randomId - connectWithRemoteUser alice bob - postConvQualified - alice - Nothing - defNewProteusConv {newConvQualifiedUsers = [bob]} - !!! do - const 422 === statusCode + let remoteDomain = Domain "non-existent.example.com" + (uAlice, alice) <- randomUserTuple + uBob <- randomId + let bob = Qualified uBob remoteDomain + connectWithRemoteUser uAlice bob + createdConv <- + responseJsonError + =<< postConvQualified + uAlice + Nothing + defNewProteusConv {newConvQualifiedUsers = [bob]} + postTeamConv tid alice [] (Just "remote gossip") [] Nothing Nothing + + connectWithRemoteUser alice remoteBob + + let mock = + ("on-new-remote-conversation" ~> EmptyResponse) + -- Mock an unavailable federation server for the deletion call + <|> (guardRPC "on-conversation-updated" *> throw (MockErrorResponse HTTP.status503 "Down for maintenance.")) + <|> (guardRPC "delete-team-conversation" *> throw (MockErrorResponse HTTP.status503 "Down for maintenance.")) + (_, received) <- withTempMockFederator' mock $ do + postQualifiedMembers alice (remoteBob :| []) convId + !!! const 503 === statusCode + + deleteTeamConv tid convId alice + !!! const 503 === statusCode + liftIO $ do + let convUpdates = mapMaybe (eitherToMaybe . parseFedRequest) received + convUpdate <- case filter ((== SomeConversationAction (sing @'ConversationDeleteTag) ()) . cuAction) convUpdates of + [] -> assertFailure "No ConversationUpdate requests received" + [convDelete] -> pure convDelete + _ -> assertFailure "Multiple ConversationUpdate requests received" + cuAlreadyPresentUsers convUpdate @?= [bobId] + cuOrigUserId convUpdate @?= qalice + testGetQualifiedLocalConv :: TestM () testGetQualifiedLocalConv = do alice <- randomUser @@ -2975,6 +3264,74 @@ deleteRemoteMemberConvLocalQualifiedOk = do const 204 === statusCode const Nothing === responseBody +-- Creates a conversation with five users. Alice and Bob are on the local +-- domain. Chad and Dee are on far-away-1.example.com. Eve is on +-- far-away-2.example.com. It uses a qualified endpoint to remove Chad from the +-- conversation. The federator for far-away-2.example.com isn't availabe: +-- +-- DELETE /conversations/:domain/:cnv/members/:domain/:usr +deleteUnavailableRemoteMemberConvLocalQualifiedOk :: TestM () +deleteUnavailableRemoteMemberConvLocalQualifiedOk = do + localDomain <- viewFederationDomain + [alice, bob] <- randomUsers 2 + let [qAlice, qBob] = (`Qualified` localDomain) <$> [alice, bob] + remoteDomain1 = Domain "far-away-1.example.com" + remoteDomain2 = Domain "far-away-2.example.com" + qChad <- (`Qualified` remoteDomain1) <$> randomId + qDee <- (`Qualified` remoteDomain1) <$> randomId + qEve <- (`Qualified` remoteDomain2) <$> randomId + connectUsers alice (singleton bob) + mapM_ (connectWithRemoteUser alice) [qChad, qDee, qEve] + + let mockedGetUsers = do + guardRPC "get-users-by-ids" + d <- frTargetDomain <$> getRequest + asum + [ guard (d == remoteDomain1) + *> mockReply [mkProfile qChad (Name "Chad"), mkProfile qDee (Name "Dee")], + guard (d == remoteDomain2) + *> throw (MockErrorResponse HTTP.status503 "Down for maintenance.") + ] + mockedOther = do + d <- frTargetDomain <$> getRequest + asum + [ guard (d == remoteDomain1) + *> mockReply (), + guard (d == remoteDomain2) + *> asum + [ guardRPC "on-conversation-created" *> mockReply (), + throw $ MockErrorResponse HTTP.status503 "Down for maintenance." + ] + ] + (convId, _) <- + withTempMockFederator' (mockedGetUsers <|> mockedOther) $ + fmap decodeConvId $ + postConvQualified + alice + Nothing + defNewProteusConv {newConvQualifiedUsers = [qBob, qChad, qDee, qEve]} + mockedOther) $ + deleteMemberQualified alice qChad qconvId + liftIO $ do + statusCode respDel @?= 200 + case responseJsonEither respDel of + Left err -> assertFailure err + Right e -> assertLeaveEvent qconvId qAlice [qChad] e + + let [remote1GalleyFederatedRequest] = fedRequestsForDomain remoteDomain1 Galley federatedRequests + [remote2GalleyFederatedRequest] = fedRequestsForDomain remoteDomain2 Galley federatedRequests + assertRemoveUpdate remote1GalleyFederatedRequest qconvId qAlice [qUnqualified qChad, qUnqualified qDee] qChad + assertRemoveUpdate remote2GalleyFederatedRequest qconvId qAlice [qUnqualified qEve] qChad + + -- Now that Chad is gone, try removing him once again + deleteMemberQualified alice qChad qconvId !!! do + const 204 === statusCode + const Nothing === responseBody + -- Alice, a local user, leaves a remote conversation. Bob's domain is the same -- as that of the conversation. The test uses the following endpoint: -- diff --git a/services/galley/test/integration/API/Federation.hs b/services/galley/test/integration/API/Federation.hs index 4b2d42233f..f05cfe537e 100644 --- a/services/galley/test/integration/API/Federation.hs +++ b/services/galley/test/integration/API/Federation.hs @@ -958,26 +958,35 @@ onUserDeleted = do createOne2OneConvWithRemote alice bob -- create group conversation with everybody - groupConvId <- - decodeQualifiedConvId - <$> ( postConvWithRemoteUsers - (tUnqualified alice) - Nothing - defNewProteusConv {newConvQualifiedUsers = [tUntagged bob, alex, bart, carl]} - do + convId <- + decodeQualifiedConvId + <$> ( postConvWithRemoteUsers + (tUnqualified alice) + Nothing + defNewProteusConv {newConvQualifiedUsers = [tUntagged bob, alex, bart, carl]} + do + convId <- + fmap decodeQualifiedConvId $ + postConvQualified + (tUnqualified alice) + Nothing + defNewProteusConv {newConvQualifiedUsers = [alex]} + do (resp, rpcCalls) <- withTempMockFederator' (mockReply ()) $ do diff --git a/services/galley/test/integration/API/Federation/Util.hs b/services/galley/test/integration/API/Federation/Util.hs index f29d031927..494194d461 100644 --- a/services/galley/test/integration/API/Federation/Util.hs +++ b/services/galley/test/integration/API/Federation/Util.hs @@ -15,9 +15,18 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module API.Federation.Util (mkHandler) where +module API.Federation.Util + ( mkHandler, + + -- * the remote backend type + BackendReachability (..), + Backend (..), + rbReachable, + ) +where import Data.Kind +import Data.Qualified import Data.SOP import Data.String.Conversions (cs) import GHC.TypeLits @@ -101,3 +110,18 @@ instance PartialAPI (Named (name :: Symbol) endpoint :<|> api) (Named name h) where mkHandler h = h :<|> mkHandler @api EmptyAPI + +-------------------------------------------------------------------------------- +-- The remote backend type + +data BackendReachability = BackendReachable | BackendUnreachable + deriving (Eq, Ord) + +data Backend = Backend + { bReachable :: BackendReachability, + bUsers :: Nat + } + deriving (Eq, Ord) + +rbReachable :: Remote Backend -> BackendReachability +rbReachable = bReachable . tUnqualified diff --git a/services/galley/test/integration/API/MLS.hs b/services/galley/test/integration/API/MLS.hs index 5666531bb5..c6c1c2d3db 100644 --- a/services/galley/test/integration/API/MLS.hs +++ b/services/galley/test/integration/API/MLS.hs @@ -24,8 +24,10 @@ import API.MLS.Util import API.Util import Bilge hiding (head) import Bilge.Assert -import Cassandra +import Cassandra hiding (Set) +import Control.Exception (throw) import Control.Lens (view) +import Control.Lens.Extras import qualified Control.Monad.State as State import Crypto.Error import qualified Crypto.PubKey.Ed25519 as Ed25519 @@ -47,6 +49,7 @@ import qualified Data.Text as T import Data.Time import Federator.MockServer hiding (withTempMockFederator) import Imports +import qualified Network.HTTP.Types.Status as HTTP import qualified Network.Wai.Utilities.Error as Wai import Test.QuickCheck (Arbitrary (arbitrary), generate) import Test.Tasty @@ -60,9 +63,11 @@ import Wire.API.Conversation.Action import Wire.API.Conversation.Protocol import Wire.API.Conversation.Role import Wire.API.Error.Galley +import Wire.API.Event.Conversation import Wire.API.Federation.API.Galley import Wire.API.MLS.Credential import Wire.API.MLS.Keys +import Wire.API.MLS.Message import Wire.API.MLS.Serialisation import Wire.API.MLS.SubConversation import Wire.API.MLS.Welcome @@ -126,7 +131,9 @@ tests s = "Local Sender/Local Conversation" [ test s "send application message" testAppMessage, test s "send remote application message" testRemoteAppMessage, - test s "another participant sends an application message" testAppMessage2 + test s "another participant sends an application message" testAppMessage2, + test s "send message, some remotes are reachable" testAppMessageSomeReachable, + test s "send message, remote users are unreachable" testAppMessageUnreachable ], testGroup "Local Sender/Remote Conversation" @@ -812,7 +819,7 @@ testRemoteAppMessage = do ((message, events), reqs) <- withTempMockFederator' mock $ do void $ createAddCommit alice1 [bob] >>= sendAndConsumeCommit message <- createApplicationMessage alice1 "hello" - events <- sendAndConsumeMessage message + (events, _) <- sendAndConsumeMessage message pure (message, events) liftIO $ do @@ -1065,7 +1072,7 @@ testAppMessage = do message <- createApplicationMessage alice1 "some text" mlsBracket clients $ \wss -> do - events <- sendAndConsumeMessage message + (events, _) <- sendAndConsumeMessage message liftIO $ events @?= [] liftIO $ WS.assertMatchN_ (5 # WS.Second) wss $ @@ -1093,7 +1100,7 @@ testAppMessage2 = do message <- createApplicationMessage bob1 "some text" mlsBracket (alice1 : clients) $ \wss -> do - events <- sendAndConsumeMessage message + (events, _) <- sendAndConsumeMessage message liftIO $ events @?= [] -- check that the corresponding event is received @@ -1102,6 +1109,66 @@ testAppMessage2 = do WS.assertMatchN_ (5 # WS.Second) wss $ wsAssertMLSMessage conversation bob (mpMessage message) +testAppMessageSomeReachable :: TestM () +testAppMessageSomeReachable = do + users@[_alice, bob, charlie] <- + createAndConnectUsers + [ Nothing, + Just "bob.example.com", + Just "charlie.example.com" + ] + + void $ runMLSTest $ do + [alice1, bob1, charlie1] <- + traverse createMLSClient users + + void $ setupMLSGroup alice1 + commit <- createAddCommit alice1 [bob, charlie] + + let mocks = + receiveCommitMockByDomain [bob1, charlie1] + <|> welcomeMock + ([event], _) <- + withTempMockFederator' mocks $ do + sendAndConsumeCommit commit + + let unreachables = Set.singleton (Domain "charlie.example.com") + withTempMockFederator' (mockUnreachableFor unreachables) $ do + message <- createApplicationMessage alice1 "hi, bob!" + (_, us) <- sendAndConsumeMessage message + liftIO $ do + assertBool "Event should be member join" $ is _EdMembersJoin (evtData event) + us @?= UnreachableUsers [charlie] + where + mockUnreachableFor :: Set Domain -> Mock LByteString + mockUnreachableFor backends = do + r <- getRequest + if Set.member (frTargetDomain r) backends + then throw (MockErrorResponse HTTP.status503 "Down for maintenance.") + else mockReply ("RemoteMLSMessageOk" :: String) + +testAppMessageUnreachable :: TestM () +testAppMessageUnreachable = do + -- alice is local, bob is remote + -- alice creates a local conversation and invites bob + -- alice then sends a message to the conversation, but bob is not reachable anymore + -- since we did not properly setup federation, we can't reach the remote server with bob's msg + users@[_alice, bob] <- createAndConnectUsers [Nothing, Just "bob.example.com"] + runMLSTest $ do + [alice1, bob1] <- traverse createMLSClient users + void $ setupMLSGroup alice1 + + commit <- createAddCommit alice1 [bob] + ([event], _) <- + withTempMockFederator' (receiveCommitMock [bob1] <|> welcomeMock) $ + sendAndConsumeCommit commit + + message <- createApplicationMessage alice1 "hi, bob!" + (_, us) <- sendAndConsumeMessage message + liftIO $ do + assertBool "Event should be member join" $ is _EdMembersJoin (evtData event) + us @?= UnreachableUsers [bob] + testRemoteToRemote :: TestM () testRemoteToRemote = do localDomain <- viewFederationDomain @@ -1164,12 +1231,15 @@ testRemoteToLocal = do let bobDomain = Domain "faraway.example.com" -- create users - [alice, bob] <- createAndConnectUsers [Nothing, Just (domainText bobDomain)] + [alice, bob] <- + createAndConnectUsers + [ Nothing, + Just (domainText bobDomain) + ] -- Simulate the whole MLS setup for both clients first. In reality, -- backend calls would need to happen in order for bob to get ahold of a -- welcome message, but that should not affect the correctness of the test. - runMLSTest $ do [alice1, bob1] <- traverse createMLSClient [alice, bob] @@ -1188,7 +1258,6 @@ testRemoteToLocal = do cannon <- view tsCannon -- actual test - let msr = MLSMessageSendRequest { mmsrConvOrSubId = Conv (qUnqualified qcnv), @@ -1197,9 +1266,9 @@ testRemoteToLocal = do } WS.bracketR cannon (qUnqualified alice) $ \ws -> do - resp <- runFedClient @"send-mls-message" fedGalleyClient bobDomain msr + MLSMessageResponseUpdates updates _ <- runFedClient @"send-mls-message" fedGalleyClient bobDomain msr liftIO $ do - resp @?= MLSMessageResponseUpdates [] + updates @?= [] WS.assertMatch_ (5 # Second) ws $ wsAssertMLSMessage qcnv bob (mpMessage message) @@ -1298,9 +1367,9 @@ propExistingConv = do [alice1, bob1] <- traverse createMLSClient [alice, bob] void $ uploadNewKeyPackage bob1 void $ setupMLSGroup alice1 - events <- createAddProposals alice1 [bob] >>= traverse sendAndConsumeMessage + res <- traverse sendAndConsumeMessage =<< createAddProposals alice1 [bob] - liftIO $ events @?= [[]] + liftIO $ (fst <$> res) @?= [[]] propInvalidEpoch :: TestM () propInvalidEpoch = do @@ -1969,7 +2038,7 @@ testAddUserToRemoteConvWithBundle = do commit <- createAddCommit bob1 [charlie] commitBundle <- createBundle commit - let mock = "send-mls-commit-bundle" ~> MLSMessageResponseUpdates [] + let mock = "send-mls-commit-bundle" ~> MLSMessageResponseUpdates [] (UnreachableUsers []) (_, reqs) <- withTempMockFederator' mock $ do void $ sendAndConsumeCommitBundle commit diff --git a/services/galley/test/integration/API/MLS/Mocks.hs b/services/galley/test/integration/API/MLS/Mocks.hs index 71d1dd6a7e..911b338053 100644 --- a/services/galley/test/integration/API/MLS/Mocks.hs +++ b/services/galley/test/integration/API/MLS/Mocks.hs @@ -17,6 +17,7 @@ module API.MLS.Mocks ( receiveCommitMock, + receiveCommitMockByDomain, messageSentMock, welcomeMock, sendMessageMock, @@ -36,6 +37,7 @@ import Wire.API.Federation.API.Common import Wire.API.Federation.API.Galley import Wire.API.MLS.Credential import Wire.API.MLS.KeyPackage +import Wire.API.MLS.Message import Wire.API.User.Client receiveCommitMock :: [ClientIdentity] -> Mock LByteString @@ -49,6 +51,19 @@ receiveCommitMock clients = ) ] +receiveCommitMockByDomain :: [ClientIdentity] -> Mock LByteString +receiveCommitMockByDomain clients = do + r <- getRequest + let fClients = filter (\c -> frTargetDomain r == ciDomain c) clients + asum + [ "on-conversation-updated" ~> (), + "on-new-remote-conversation" ~> EmptyResponse, + "get-mls-clients" ~> + Set.fromList + ( map (flip ClientInfo True . ciClient) fClients + ) + ] + messageSentMock :: Mock LByteString messageSentMock = "on-mls-message-sent" ~> RemoteMLSMessageOk @@ -56,7 +71,11 @@ welcomeMock :: Mock LByteString welcomeMock = "mls-welcome" ~> MLSWelcomeSent sendMessageMock :: Mock LByteString -sendMessageMock = "send-mls-message" ~> MLSMessageResponseUpdates [] +sendMessageMock = + "send-mls-message" ~> + MLSMessageResponseUpdates + [] + (UnreachableUsers []) claimKeyPackagesMock :: KeyPackageBundle -> Mock LByteString claimKeyPackagesMock kpb = "claim-key-packages" ~> kpb diff --git a/services/galley/test/integration/API/MLS/Util.hs b/services/galley/test/integration/API/MLS/Util.hs index 313b0cbc61..28f1a47d00 100644 --- a/services/galley/test/integration/API/MLS/Util.hs +++ b/services/galley/test/integration/API/MLS/Util.hs @@ -48,6 +48,7 @@ import qualified Data.Set as Set import qualified Data.Text as T import qualified Data.Text.Encoding as T import Data.Time.Clock (getCurrentTime) +import qualified Data.Tuple.Extra as Tuple import Galley.Keys import Galley.Options import qualified Galley.Options as Opts @@ -838,12 +839,13 @@ consumeMessage1 cid msg = do -- | Send an MLS message and simulate clients receiving it. If the message is a -- commit, the 'sendAndConsumeCommit' function should be used instead. -sendAndConsumeMessage :: HasCallStack => MessagePackage -> MLSTest [Event] +sendAndConsumeMessage :: HasCallStack => MessagePackage -> MLSTest ([Event], UnreachableUsers) sendAndConsumeMessage mp = do - events <- - fmap mmssEvents . responseJsonError - =<< postMessage (mpSender mp) (mpMessage mp) - do @@ -851,7 +853,7 @@ sendAndConsumeMessage mp = do !!! const 201 === statusCode consumeWelcome welcome - pure events + pure res -- | Send an MLS commit message, simulate clients receiving it, and update the -- test state accordingly. @@ -860,7 +862,7 @@ sendAndConsumeCommit :: MessagePackage -> MLSTest [Event] sendAndConsumeCommit mp = do - events <- sendAndConsumeMessage mp + (events, _) <- sendAndConsumeMessage mp -- increment epoch and add new clients State.modify $ \mls -> diff --git a/services/galley/test/integration/API/Teams.hs b/services/galley/test/integration/API/Teams.hs index 097538d205..3e03c34ca0 100644 --- a/services/galley/test/integration/API/Teams.hs +++ b/services/galley/test/integration/API/Teams.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE OverloadedRecordDot #-} {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} -- Disabling to stop warnings on HasCallStack {-# OPTIONS_GHC -Wno-redundant-constraints #-} @@ -49,7 +50,7 @@ import Data.List.NonEmpty (NonEmpty ((:|))) import Data.List1 hiding (head) import qualified Data.List1 as List1 import qualified Data.Map as Map -import Data.Misc (HttpsUrl, PlainTextPassword (..), mkHttpsUrl) +import Data.Misc (HttpsUrl, PlainTextPassword6, mkHttpsUrl, plainTextPassword6) import Data.Qualified import Data.Range import qualified Data.Set as Set @@ -79,11 +80,13 @@ import TestHelpers import TestSetup import UnliftIO (mapConcurrently) import Wire.API.Conversation +import Wire.API.Conversation.Code import Wire.API.Conversation.Protocol import Wire.API.Conversation.Role import Wire.API.Event.Team import Wire.API.Internal.Notification hiding (target) import Wire.API.Routes.Internal.Galley.TeamsIntra as TeamsIntra +import Wire.API.Routes.Version import Wire.API.Team import Wire.API.Team.Export (TeamExportUser (..)) import qualified Wire.API.Team.Feature as Public @@ -612,9 +615,12 @@ testRemoveBindingTeamMember ownerHasPassword = do Util.connectUsers owner (List1.singleton mext) cid1 <- Util.createTeamConv owner tid [mem1 ^. userId, mext] (Just "blaa") Nothing Nothing when ownerHasPassword $ do + -- request to remove a team member is handled by the by the endpoint do remove non-binding team member + -- which is not supported from V4 onwards, therefore we need to use API version V3 + gv3 <- fmap (addPrefixAtVersion V3 .) (view tsUnversionedGalley) -- Deleting from a binding team with empty body is invalid delete - ( g + ( gv3 . paths ["teams", toByteString' tid, "members", toByteString' (mem1 ^. userId)] . zUser owner . zConn "conn" @@ -638,7 +644,7 @@ testRemoveBindingTeamMember ownerHasPassword = do . paths ["teams", toByteString' tid, "members", toByteString' (mem1 ^. userId)] . zUser owner . zConn "conn" - . json (newTeamMemberDeleteData (Just $ PlainTextPassword "wrong passwd")) + . json (newTeamMemberDeleteData (plainTextPassword6 "wrong passwd")) ) !!! do const 403 === statusCode @@ -711,7 +717,7 @@ testRemoveBindingTeamOwner = do Util.waitForMemberDeletion ownerB tid ownerWithoutEmail assertTeamUpdate "Remove ownerWithoutEmail" tid 2 [ownerB] where - check :: HasCallStack => TeamId -> UserId -> UserId -> Maybe PlainTextPassword -> Maybe LText -> TestM () + check :: HasCallStack => TeamId -> UserId -> UserId -> Maybe PlainTextPassword6 -> Maybe LText -> TestM () check tid deleter deletee pass maybeError = do g <- viewGalley delete @@ -980,7 +986,7 @@ testDeleteBindingTeamSingleMember = do ) !!! const 202 === statusCode - checkUserDeleteEvent owner wsOwner + checkUserDeleteEvent owner checkTimeout wsOwner WS.assertNoEvent (1 # Second) [wsExtern] -- Note that given the async nature of team deletion, we may @@ -1015,8 +1021,8 @@ testDeleteBindingTeamMoreThanOneMember = do -- now try again with the 'force' query flag, which should work delete (g . paths ["/i/teams", toByteString' tid] . queryItem "force" "true") !!! do const 202 === statusCode - checkUserDeleteEvent alice wsAlice - zipWithM_ checkUserDeleteEvent members wsMembers + checkUserDeleteEvent alice checkTimeout wsAlice + zipWithM_ (flip checkUserDeleteEvent checkTimeout) members wsMembers assertTeamDelete 10 "team delete, should be there" tid let ensureDeleted :: UserId -> TestM () @@ -1180,7 +1186,7 @@ testDeleteBindingTeam ownerHasPassword = do . paths ["teams", toByteString' tid] . zUser owner . zConn "conn" - . json (newTeamDeleteData (Just $ PlainTextPassword "wrong passwd")) + . json (newTeamDeleteData (plainTextPassword6 "wrong passwd")) ) !!! do const 403 === statusCode @@ -1217,9 +1223,9 @@ testDeleteBindingTeam ownerHasPassword = do ) !!! const 202 === statusCode - checkUserDeleteEvent owner wsOwner - checkUserDeleteEvent (mem1 ^. userId) wsMember1 - checkUserDeleteEvent (mem2 ^. userId) wsMember2 + checkUserDeleteEvent owner checkTimeout wsOwner + checkUserDeleteEvent (mem1 ^. userId) checkTimeout wsMember1 + checkUserDeleteEvent (mem2 ^. userId) checkTimeout wsMember2 checkTeamDeleteEvent tid wsOwner checkTeamDeleteEvent tid wsMember1 checkTeamDeleteEvent tid wsMember2 @@ -1246,12 +1252,21 @@ testDeleteTeamConv = do extern <- Util.randomUser qExtern <- Qualified extern <$> viewFederationDomain for_ members $ \m -> Util.connectUsers (m & qUnqualified) (list1 extern []) - cid1 <- Util.createTeamConv owner tid [] (Just "blaa") Nothing Nothing - qcid1 <- Qualified cid1 <$> viewFederationDomain + (cid1, qcid1) <- WS.bracketR c owner $ \wsOwner -> do + cid1 <- Util.createTeamConv owner tid [] (Just "blaa") Nothing Nothing + qcid1 <- Qualified cid1 <$> viewFederationDomain + WS.assertMatch_ (5 # Second) wsOwner $ + wsAssertConvCreate qcid1 qOwner + pure (cid1, qcid1) let access = ConversationAccessData (Set.fromList [InviteAccess, CodeAccess]) (Set.fromList [TeamMemberAccessRole, NonTeamMemberAccessRole]) putQualifiedAccessUpdate owner qcid1 access !!! const 200 === statusCode - code <- decodeConvCodeEvent <$> (postConvCode owner cid1 members) (Just "blup") Nothing Nothing + codeInfo <- decodeConvCodeEvent <$> (postConvCode owner cid1 do + cid2 <- Util.createTeamConv owner tid (qUnqualified <$> members) (Just "blup") Nothing Nothing + qcid2 <- Qualified cid2 <$> viewFederationDomain + WS.assertMatch_ (5 # Second) wsOwner $ + wsAssertConvCreate qcid2 qOwner + pure cid2 Util.postMembers owner (qExtern :| [qMember]) qcid1 !!! const 200 === statusCode for_ (qExtern : members) $ \u -> Util.assertConvMember u cid1 for_ members $ flip Util.assertConvMember cid2 @@ -1284,7 +1299,7 @@ testDeleteTeamConv = do for_ [owner, member ^. userId, extern] $ \u -> do Util.getConv u x !!! const 404 === statusCode Util.assertNotConvMember u x - postConvCodeCheck code !!! const 404 === statusCode + postConvCodeCheck codeInfo.code !!! const 404 === statusCode testUpdateTeamIconValidation :: TestM () testUpdateTeamIconValidation = do @@ -1480,7 +1495,7 @@ testTeamAddRemoveMemberAboveThresholdNoEvents = do then checkTeamMemberLeave tid victim wsOwner else WS.assertNoEvent (1 # Second) [wsOwner] -- User deletion events - mapM_ (checkUserDeleteEvent victim) wsOthers + mapM_ (checkUserDeleteEvent victim checkTimeout) wsOthers Util.ensureDeletedState True owner victim deleteTeam :: HasCallStack => TeamId -> UserId -> [UserId] -> [Qualified ConvId] -> UserId -> TestM () deleteTeam tid owner otherRealUsersInTeam teamCidsThatExternBelongsTo extern = do @@ -1496,7 +1511,7 @@ testTeamAddRemoveMemberAboveThresholdNoEvents = do ) !!! const 202 === statusCode - for_ (owner : otherRealUsersInTeam) $ \u -> checkUserDeleteEvent u wsExtern + for_ (owner : otherRealUsersInTeam) $ \u -> checkUserDeleteEvent u (7 # Second) wsExtern -- Ensure users are marked as deleted; since we already -- received the event, should _really_ be deleted for_ (owner : otherRealUsersInTeam) $ Util.ensureDeletedState True extern diff --git a/services/galley/test/integration/API/Teams/LegalHold/Util.hs b/services/galley/test/integration/API/Teams/LegalHold/Util.hs index 62fc82bb4a..847e477bfb 100644 --- a/services/galley/test/integration/API/Teams/LegalHold/Util.hs +++ b/services/galley/test/integration/API/Teams/LegalHold/Util.hs @@ -29,7 +29,7 @@ import Data.Id import Data.LegalHold import qualified Data.List.NonEmpty as NonEmpty import qualified Data.List1 as List1 -import Data.Misc (PlainTextPassword) +import Data.Misc (PlainTextPassword6) import Data.PEM import Data.Streaming.Network (bindRandomPortTCP) import Data.String.Conversions (LBS, cs) @@ -171,7 +171,7 @@ withTeam action = where waitForDeleteEvent :: TeamId -> TestM () waitForDeleteEvent tid = - assertTeamDelete 10 "waitForDeleteEvent" tid + assertTeamDelete 15 "waitForDeleteEvent" tid withFreePortAnyAddr :: (MonadMask m, MonadIO m) => ((Warp.Port, Socket) -> m a) -> m a withFreePortAnyAddr = bracket openFreePortAnyAddr (liftIO . Socket.close . snd) @@ -346,7 +346,7 @@ getSettings uid tid = do . zConn "conn" . zType "access" -deleteSettings :: HasCallStack => Maybe PlainTextPassword -> UserId -> TeamId -> TestM ResponseLBS +deleteSettings :: HasCallStack => Maybe PlainTextPassword6 -> UserId -> TeamId -> TestM ResponseLBS deleteSettings mPassword uid tid = do g <- viewGalley delete $ @@ -376,7 +376,7 @@ getUserStatus' g uid tid = do . zConn "conn" . zType "access" -approveLegalHoldDevice :: HasCallStack => Maybe PlainTextPassword -> UserId -> UserId -> TeamId -> TestM ResponseLBS +approveLegalHoldDevice :: HasCallStack => Maybe PlainTextPassword6 -> UserId -> UserId -> TeamId -> TestM ResponseLBS approveLegalHoldDevice mPassword zusr uid tid = do g <- viewGalley approveLegalHoldDevice' g mPassword zusr uid tid @@ -384,7 +384,7 @@ approveLegalHoldDevice mPassword zusr uid tid = do approveLegalHoldDevice' :: (HasCallStack, MonadHttp m) => GalleyR -> - Maybe PlainTextPassword -> + Maybe PlainTextPassword6 -> UserId -> UserId -> TeamId -> @@ -400,7 +400,7 @@ approveLegalHoldDevice' g mPassword zusr uid tid = do disableLegalHoldForUser :: HasCallStack => - Maybe PlainTextPassword -> + Maybe PlainTextPassword6 -> TeamId -> UserId -> UserId -> @@ -412,7 +412,7 @@ disableLegalHoldForUser mPassword tid zusr uid = do disableLegalHoldForUser' :: (HasCallStack, MonadHttp m) => GalleyR -> - Maybe PlainTextPassword -> + Maybe PlainTextPassword6 -> TeamId -> UserId -> UserId -> diff --git a/services/galley/test/integration/API/Util.hs b/services/galley/test/integration/API/Util.hs index ade69830f1..1a0fcebce7 100644 --- a/services/galley/test/integration/API/Util.hs +++ b/services/galley/test/integration/API/Util.hs @@ -87,6 +87,7 @@ import Imports import qualified Network.HTTP.Client as HTTP import Network.HTTP.Media.MediaType import qualified Network.HTTP.Types as HTTP +import Network.URI (pathSegments) import Network.Wai (defaultRequest) import qualified Network.Wai as Wai import qualified Network.Wai.Test as Wai @@ -107,6 +108,7 @@ import Web.Cookie import Wire.API.Connection import Wire.API.Conversation import Wire.API.Conversation.Action +import Wire.API.Conversation.Code hiding (Value) import Wire.API.Conversation.Protocol import Wire.API.Conversation.Role import Wire.API.Conversation.Typing @@ -147,10 +149,16 @@ import Wire.API.User.Client.Prekey -- API Operations addPrefix :: Request -> Request -addPrefix = addPrefixAtVersion maxBound +addPrefix = maybeAddPrefixAtVersion maxBound + +maybeAddPrefixAtVersion :: Version -> Request -> Request +maybeAddPrefixAtVersion vers r = case pathSegments $ getUri r of + ("i" : _) -> r + ("api-internal" : _) -> r + _ -> addPrefixAtVersion vers r addPrefixAtVersion :: Version -> Request -> Request -addPrefixAtVersion v r = r {HTTP.path = "v" <> toHeader v <> "/" <> removeSlash (HTTP.path r)} +addPrefixAtVersion v r = r {HTTP.path = toHeader v <> "/" <> removeSlash (HTTP.path r)} where removeSlash s = case B8.uncons s of Just ('/', s') -> s' @@ -214,7 +222,8 @@ createBindingTeamWithQualifiedMembers num = do getTeams :: UserId -> [(ByteString, Maybe ByteString)] -> TestM TeamList getTeams u queryItems = do - g <- viewGalley + -- This endpoint is removed from version v4 onwards + g <- fmap (addPrefixAtVersion V3 .) (view tsUnversionedGalley) r <- get ( g @@ -1346,15 +1355,19 @@ postJoinConv u c = do . zType "access" postJoinCodeConv :: UserId -> ConversationCode -> TestM ResponseLBS -postJoinCodeConv u j = do +postJoinCodeConv = postJoinCodeConv' Nothing + +postJoinCodeConv' :: Maybe PlainTextPassword8 -> UserId -> ConversationCode -> TestM ResponseLBS +postJoinCodeConv' mPw u j = do g <- viewGalley post $ g - . paths ["/conversations", "join"] + . paths ["conversations", "join"] . zUser u . zConn "conn" . zType "access" - . json j + -- `json (JoinConversationByCode j Nothing)` and `json j` are equivalent, using the latter to test backwards compatibility + . (if isJust mPw then json (JoinConversationByCode j mPw) else json j) putQualifiedAccessUpdate :: (MonadHttp m, HasGalley m, MonadIO m) => @@ -1406,7 +1419,10 @@ putMessageTimerUpdate u c acc = do . json acc postConvCode :: UserId -> ConvId -> TestM ResponseLBS -postConvCode u c = do +postConvCode = postConvCode' Nothing + +postConvCode' :: Maybe PlainTextPassword8 -> UserId -> ConvId -> TestM ResponseLBS +postConvCode' mPw u c = do g <- viewGalley post $ g @@ -1414,6 +1430,7 @@ postConvCode u c = do . zUser u . zConn "conn" . zType "access" + . json (CreateConversationCodeRequest mPw) postConvCodeCheck :: ConversationCode -> TestM ResponseLBS postConvCodeCheck code = do @@ -1843,10 +1860,10 @@ instance IsString TestErrorLabel where instance FromJSON TestErrorLabel where parseJSON = fmap TestErrorLabel . withObject "TestErrorLabel" (.: "label") -decodeConvCode :: Response (Maybe Lazy.ByteString) -> ConversationCode +decodeConvCode :: Response (Maybe Lazy.ByteString) -> ConversationCodeInfo decodeConvCode = responseJsonUnsafe -decodeConvCodeEvent :: Response (Maybe Lazy.ByteString) -> ConversationCode +decodeConvCodeEvent :: Response (Maybe Lazy.ByteString) -> ConversationCodeInfo decodeConvCodeEvent r = case responseJsonUnsafe r of (Event _ _ _ _ (EdConvCodeUpdate c)) -> c _ -> error "Failed to parse ConversationCode from Event" @@ -2154,7 +2171,7 @@ ensureClientCaps uid cid caps = do liftIO $ assertEqual ("ensureClientCaps: " <> show (uid, cid, caps)) (clientCapabilities clnt) caps -- TODO: Refactor, as used also in brig -deleteClient :: UserId -> ClientId -> Maybe PlainTextPassword -> TestM ResponseLBS +deleteClient :: UserId -> ClientId -> Maybe PlainTextPassword6 -> TestM ResponseLBS deleteClient u c pw = do b <- viewBrig delete $ @@ -2308,8 +2325,8 @@ otrRecipients = genRandom :: (Q.Arbitrary a, MonadIO m) => m a genRandom = liftIO . Q.generate $ Q.arbitrary -defPassword :: PlainTextPassword -defPassword = PlainTextPassword "secret" +defPassword :: PlainTextPassword6 +defPassword = plainTextPassword6Unsafe "topsecretdefaultpassword" randomEmail :: MonadIO m => m Email randomEmail = do @@ -2661,8 +2678,8 @@ checkUserUpdateEvent uid w = WS.assertMatch_ checkTimeout w $ \notif -> do etype @?= Just "user.update" euser @?= Just (UUID.toText (toUUID uid)) -checkUserDeleteEvent :: HasCallStack => UserId -> WS.WebSocket -> TestM () -checkUserDeleteEvent uid w = WS.assertMatch_ checkTimeout w $ \notif -> do +checkUserDeleteEvent :: HasCallStack => UserId -> WS.Timeout -> WS.WebSocket -> TestM () +checkUserDeleteEvent uid timeout_ w = WS.assertMatch_ timeout_ w $ \notif -> do let j = Object $ List1.head (ntfPayload notif) let etype = j ^? key "type" . _String let euser = j ^? key "id" . _String @@ -2699,6 +2716,19 @@ checkConvCreateEvent cid w = WS.assertMatch_ checkTimeout w $ \notif -> do Conv.EdConversation x -> (qUnqualified . cnvQualifiedId) x @?= cid other -> assertFailure $ "Unexpected event data: " <> show other +wsAssertConvCreate :: + HasCallStack => + Qualified ConvId -> + Qualified UserId -> + Notification -> + IO () +wsAssertConvCreate conv eventFrom n = do + let e = List1.head (WS.unpackPayload n) + ntfTransient n @?= False + evtConv e @?= conv + evtType e @?= Conv.ConvCreate + evtFrom e @?= eventFrom + wsAssertConvCreateWithRole :: HasCallStack => Qualified ConvId -> diff --git a/services/galley/test/unit/Test/Galley/API/Message.hs b/services/galley/test/unit/Test/Galley/API/Message.hs index e2b0f63e27..25575bdcb1 100644 --- a/services/galley/test/unit/Test/Galley/API/Message.hs +++ b/services/galley/test/unit/Test/Galley/API/Message.hs @@ -18,11 +18,12 @@ module Test.Galley.API.Message where import Control.Lens -import Data.Domain (Domain) -import Data.Id (ClientId, UserId) +import Data.Domain +import Data.Id import qualified Data.Map as Map import qualified Data.Set as Set import Data.Set.Lens +import Data.UUID.Types import Galley.API.Message import Imports import Test.Tasty @@ -40,7 +41,8 @@ tests = checkMessageClientEverythingReported, checkMessageClientRedundantSender, checkMessageClientMissingSubsetOfStrategy - ] + ], + testBuildFailedToSend ] flatten :: Map Domain (Map UserId (Set ClientId)) -> Set (Domain, UserId, ClientId) @@ -100,3 +102,53 @@ checkMessageClientMissingSubsetOfStrategy = testProperty "missing clients should (_, _, mismatch) = checkMessageClients sender expectedMap msg strat missing = flatten . qualifiedUserClients $ qmMissing mismatch in Set.isSubsetOf missing stratClients + +testBuildFailedToSend :: TestTree +testBuildFailedToSend = + testGroup + "build failed to send map for post message qualified" + [ testProperty + "Empty case - trivial" + $ collectFailedToSend [] + === mempty, + testProperty + "Empty case - single empty map" + $ collectFailedToSend [mempty] + === mempty, + testProperty + "Empty case - multiple empty maps" + $ collectFailedToSend [mempty, mempty] + === mempty, + testProperty + "Single domain" + $ collectFailedToSend [Map.singleton (Domain "foo") mempty] + === Map.singleton (Domain "foo") mempty, + testProperty + "Single domain duplicated" + $ collectFailedToSend [Map.singleton (Domain "foo") mempty, Map.singleton (Domain "foo") mempty] + === Map.singleton (Domain "foo") mempty, + testProperty + "Mutliple domains in multiple maps" + $ collectFailedToSend [Map.singleton (Domain "foo") mempty, Map.singleton (Domain "bar") mempty] + === Map.fromList [(Domain "foo", mempty), (Domain "bar", mempty)], + testProperty + "Mutliple domains in single map" + $ collectFailedToSend [Map.fromList [(Domain "foo", mempty), (Domain "bar", mempty)]] + === Map.fromList [(Domain "foo", mempty), (Domain "bar", mempty)], + testProperty + "Single domain duplicated with unique sub-maps" + $ collectFailedToSend + [ Map.singleton (Domain "foo") $ Map.singleton idA mempty, + Map.singleton (Domain "foo") $ Map.singleton idB mempty + ] + === Map.singleton + (Domain "foo") + ( Map.fromList + [ (idA, mempty), + (idB, mempty) + ] + ) + ] + where + idA = Id $ fromJust $ Data.UUID.Types.fromString "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" + idB = Id $ fromJust $ Data.UUID.Types.fromString "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb" diff --git a/services/gundeck/gundeck.cabal b/services/gundeck/gundeck.cabal index 598ad7ad60..c0df14e698 100644 --- a/services/gundeck/gundeck.cabal +++ b/services/gundeck/gundeck.cabal @@ -63,6 +63,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -74,6 +75,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -175,6 +177,7 @@ executable gundeck DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -186,6 +189,7 @@ executable gundeck MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -247,6 +251,7 @@ executable gundeck-integration DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -258,6 +263,7 @@ executable gundeck-integration MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -357,6 +363,7 @@ executable gundeck-schema DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -368,6 +375,7 @@ executable gundeck-schema MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -432,6 +440,7 @@ test-suite gundeck-tests DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -443,6 +452,7 @@ test-suite gundeck-tests MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -522,6 +532,7 @@ benchmark gundeck-bench DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -533,6 +544,7 @@ benchmark gundeck-bench MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/services/gundeck/src/Gundeck/Aws.hs b/services/gundeck/src/Gundeck/Aws.hs index b34f3bf749..7fea7d114e 100644 --- a/services/gundeck/src/Gundeck/Aws.hs +++ b/services/gundeck/src/Gundeck/Aws.hs @@ -355,6 +355,7 @@ data PublishError = EndpointDisabled !EndpointArn | InvalidEndpoint !EndpointArn | PayloadTooLarge !EndpointArn + | UnauthorisedEndpoint !EndpointArn newtype Attributes = Attributes { setAttributes :: Endo (HashMap Text SNS.MessageAttributeValue) @@ -424,6 +425,9 @@ publish arn txt attrs = do && AWS.newErrorCode "InvalidParameter" == e ^. serviceError_code && isArnError (e ^. serviceError_message) -> pure (Left (InvalidEndpoint arn)) + | is "SNS" 403 x + && AWS.newErrorCode "AuthorizationError" == e ^. serviceError_code -> + pure (Left (UnauthorisedEndpoint arn)) Left x -> throwM (GeneralError x) where -- Thank you Amazon for not having granular error codes! diff --git a/services/gundeck/src/Gundeck/Env.hs b/services/gundeck/src/Gundeck/Env.hs index c7aa7f318b..a161f6c62a 100644 --- a/services/gundeck/src/Gundeck/Env.hs +++ b/services/gundeck/src/Gundeck/Env.hs @@ -24,6 +24,7 @@ import Cassandra (ClientState, Keyspace (..)) import qualified Cassandra as C import qualified Cassandra.Settings as C import Control.AutoUpdate +import Control.Concurrent.Async (Async) import Control.Lens (makeLenses, (^.)) import Control.Retry (capDelay, exponentialBackoff) import Data.Default (def) @@ -65,7 +66,7 @@ makeLenses ''Env schemaVersion :: Int32 schemaVersion = 7 -createEnv :: Metrics -> Opts -> IO Env +createEnv :: Metrics -> Opts -> IO ([Async ()], Env) createEnv m o = do l <- Logger.mkLogger (o ^. optLogLevel) (o ^. optLogNetStrings) (o ^. optLogFormat) c <- @@ -81,13 +82,13 @@ createEnv m o = do managerResponseTimeout = responseTimeoutMicro 5000000 } - r <- createRedisPool l (o ^. optRedis) "main-redis" + (rThread, r) <- createRedisPool l (o ^. optRedis) "main-redis" - rAdditional <- case o ^. optRedisAdditionalWrite of - Nothing -> pure Nothing + (rAdditionalThreads, rAdditional) <- case o ^. optRedisAdditionalWrite of + Nothing -> pure ([], Nothing) Just additionalRedis -> do - rAdd <- createRedisPool l additionalRedis "additional-write-redis" - pure $ Just rAdd + (rAddThread, rAdd) <- createRedisPool l additionalRedis "additional-write-redis" + pure ([rAddThread], Just rAdd) p <- C.init @@ -110,13 +111,13 @@ createEnv m o = do { updateAction = Ms . round . (* 1000) <$> getPOSIXTime } mtbs <- mkThreadBudgetState `mapM` (o ^. optSettings . setMaxConcurrentNativePushes) - pure $! Env def m o l n p r rAdditional a io mtbs + pure $! (rThread : rAdditionalThreads,) $! Env def m o l n p r rAdditional a io mtbs reqIdMsg :: RequestId -> Logger.Msg -> Logger.Msg reqIdMsg = ("request" Logger..=) . unRequestId {-# INLINE reqIdMsg #-} -createRedisPool :: Logger.Logger -> RedisEndpoint -> ByteString -> IO Redis.RobustConnection +createRedisPool :: Logger.Logger -> RedisEndpoint -> ByteString -> IO (Async (), Redis.RobustConnection) createRedisPool l endpoint identifier = do let redisConnInfo = Redis.defaultConnectInfo diff --git a/services/gundeck/src/Gundeck/Push/Data.hs b/services/gundeck/src/Gundeck/Push/Data.hs index 6437e2466e..bc30021ccc 100644 --- a/services/gundeck/src/Gundeck/Push/Data.hs +++ b/services/gundeck/src/Gundeck/Push/Data.hs @@ -17,6 +17,7 @@ module Gundeck.Push.Data ( insert, + updateArn, delete, lookup, erase, @@ -29,7 +30,7 @@ import Data.ByteString.Conversion import Data.Id (ClientId, ConnId, UserId) import Gundeck.Instances () import Gundeck.Push.Native.Types -import Gundeck.Types +import Gundeck.Types hiding (token) import Imports hiding (lookup) import System.Logger.Class (MonadLogger, field, msg, val, (~~)) import qualified System.Logger.Class as Log @@ -47,6 +48,12 @@ insert u t a p e o c = retry x5 $ write q (params LocalQuorum (u, t, a, p, e, o, q :: PrepQuery W (UserId, Transport, AppName, Token, EndpointArn, ConnId, ClientId) () q = "insert into user_push (usr, transport, app, ptoken, arn, connection, client) values (?, ?, ?, ?, ?, ?, ?)" +updateArn :: MonadClient m => UserId -> Transport -> AppName -> Token -> EndpointArn -> m () +updateArn uid transport app token arn = retry x5 $ write q (params LocalQuorum (arn, uid, transport, app, token)) + where + q :: PrepQuery W (EndpointArn, UserId, Transport, AppName, Token) () + q = "update user_push set arn = ? where usr = ? and transport = ? and app = ? and ptoken = ?" + delete :: MonadClient m => UserId -> Transport -> AppName -> Token -> m () delete u t a p = retry x5 $ write q (params LocalQuorum (u, t, a, p)) where diff --git a/services/gundeck/src/Gundeck/Push/Native.hs b/services/gundeck/src/Gundeck/Push/Native.hs index c48fabd469..9590eb044f 100644 --- a/services/gundeck/src/Gundeck/Push/Native.hs +++ b/services/gundeck/src/Gundeck/Push/Native.hs @@ -43,7 +43,7 @@ import Gundeck.Push.Native.Types as Types import Gundeck.Types import Gundeck.Util import Imports -import System.Logger.Class (MonadLogger, field, msg, val, (~~)) +import System.Logger.Class (MonadLogger, field, msg, val, (.=), (~~)) import qualified System.Logger.Class as Log import UnliftIO (handleAny, mapConcurrently, pooledMapConcurrentlyN_) import Wire.API.Internal.Notification @@ -61,58 +61,103 @@ push m addrs = do Just chunkSize -> pooledMapConcurrentlyN_ chunkSize (push1 m) addrs push1 :: NativePush -> Address -> Gundeck () -push1 m a = do - e <- view awsEnv - r <- Aws.execute e $ publish m a - case r of - Success _ -> do - Log.debug $ - field "user" (toByteString (a ^. addrUser)) - ~~ field "notificationId" (toText (npNotificationid m)) - ~~ Log.msg (val "Native push success") - view monitor >>= counterIncr (path "push.native.success") - Failure EndpointDisabled _ -> onDisabled - Failure PayloadTooLarge _ -> onPayloadTooLarge - Failure EndpointInvalid _ -> onInvalidEndpoint - Failure (PushException ex) _ -> do - logError a "Native push failed" ex - view monitor >>= counterIncr (path "push.native.errors") +push1 = push1' 0 where - onDisabled = - handleAny (logError a "Failed to cleanup disabled endpoint") $ do - Log.info $ - field "user" (toByteString (a ^. addrUser)) - ~~ field "arn" (toText (a ^. addrEndpoint)) - ~~ field "cause" ("EndpointDisabled" :: Text) - ~~ msg (val "Removing disabled endpoint and token") - view monitor >>= counterIncr (path "push.native.disabled") - Data.delete (a ^. addrUser) (a ^. addrTransport) (a ^. addrApp) (a ^. addrToken) - onTokenRemoved - e <- view awsEnv - Aws.execute e (Aws.deleteEndpoint (a ^. addrEndpoint)) - onPayloadTooLarge = do - view monitor >>= counterIncr (path "push.native.too_large") - Log.warn $ - field "user" (toByteString (a ^. addrUser)) - ~~ field "arn" (toText (a ^. addrEndpoint)) - ~~ msg (val "Payload too large") - onInvalidEndpoint = - handleAny (logError a "Failed to cleanup orphaned push token") $ do - Log.warn $ - field "user" (toByteString (a ^. addrUser)) - ~~ field "arn" (toText (a ^. addrEndpoint)) - ~~ field "cause" ("InvalidEndpoint" :: Text) - ~~ msg (val "Invalid ARN. Deleting orphaned push token") - view monitor >>= counterIncr (path "push.native.invalid") - Data.delete (a ^. addrUser) (a ^. addrTransport) (a ^. addrApp) (a ^. addrToken) - onTokenRemoved - onTokenRemoved = do - i <- mkNotificationId - let c = a ^. addrClient - let r = singleton (target (a ^. addrUser) & targetClients .~ [c]) - let t = a ^. addrPushToken - let p = singletonPayload (PushRemove t) - Stream.add i r p =<< view (options . optSettings . setNotificationTTL) + push1' :: Int -> NativePush -> Address -> Gundeck () + push1' n m a = + if n > retryUnauthorisedThreshold + then onPersistentlyUnauthorisedEndpoint + else do + e <- view awsEnv + r <- Aws.execute e $ publish m a + case r of + Success _ -> onSuccess + Failure EndpointDisabled _ -> onDisabled + Failure PayloadTooLarge _ -> onPayloadTooLarge + Failure EndpointInvalid _ -> onInvalidEndpoint + Failure EndpointUnauthorised _ -> + if n < retryUnauthorisedThreshold + then onUnauthorisedEndpoint + else onPersistentlyUnauthorisedEndpoint + Failure (PushException ex) _ -> onPushException ex + where + onSuccess = do + Log.debug $ + field "user" (toByteString (a ^. addrUser)) + ~~ field "notificationId" (toText (npNotificationid m)) + ~~ Log.msg (val "Native push success") + view monitor >>= counterIncr (path "push.native.success") + onDisabled = + handleAny (logError a "Failed to cleanup disabled endpoint") $ do + Log.info $ + field "user" (toByteString (a ^. addrUser)) + ~~ field "arn" (toText (a ^. addrEndpoint)) + ~~ field "cause" ("EndpointDisabled" :: Text) + ~~ msg (val "Removing disabled endpoint and token") + view monitor >>= counterIncr (path "push.native.disabled") + Data.delete (a ^. addrUser) (a ^. addrTransport) (a ^. addrApp) (a ^. addrToken) + onTokenRemoved + e <- view awsEnv + Aws.execute e (Aws.deleteEndpoint (a ^. addrEndpoint)) + onPayloadTooLarge = do + view monitor >>= counterIncr (path "push.native.too_large") + Log.warn $ + field "user" (toByteString (a ^. addrUser)) + ~~ field "arn" (toText (a ^. addrEndpoint)) + ~~ msg (val "Payload too large") + onInvalidEndpoint = + handleAny (logError a "Failed to cleanup orphaned push token") $ do + Log.warn $ + field "user" (toByteString (a ^. addrUser)) + ~~ field "arn" (toText (a ^. addrEndpoint)) + ~~ field "cause" ("InvalidEndpoint" :: Text) + ~~ msg (val "Invalid ARN. Deleting orphaned push token") + view monitor >>= counterIncr (path "push.native.invalid") + Data.delete (a ^. addrUser) (a ^. addrTransport) (a ^. addrApp) (a ^. addrToken) + onTokenRemoved + retryUnauthorisedThreshold = 1 + onUnauthorisedEndpoint = do + -- try to recreate ARN (cf. Gundeck.Push.addToken.create) + let uid = a ^. addrUser + let t = a ^. addrPushToken + let trp = t ^. tokenTransport + let app = t ^. tokenApp + let tok = t ^. token + env <- view (options . optAws . awsArnEnv) + aws <- view awsEnv + ept <- Aws.execute aws (Aws.createEndpoint uid trp env app tok) + case ept of + Left (Aws.EndpointInUse arn) -> + Log.info $ "arn" .= toText arn ~~ msg (val "ARN in use") + Left (Aws.AppNotFound app') -> + Log.info $ msg ("Push token of unknown application: '" <> appNameText app' <> "'") + Left (Aws.InvalidToken _) -> + Log.info $ + "token" + .= tokenText tok + ~~ msg (val "Invalid push token.") + Left (Aws.TokenTooLong l) -> + Log.info $ msg ("Push token is too long: token length = " ++ show l) + Right arn -> do + Data.updateArn uid trp app tok arn + push1' (succ n) m (a & addrEndpoint .~ arn) -- try to send the push message with the new ARN + onPersistentlyUnauthorisedEndpoint = handleAny (logError a "Found orphaned push token") $ do + Log.warn $ + field "user" (toByteString (a ^. addrUser)) + ~~ field "arn" (toText (a ^. addrEndpoint)) + ~~ field "cause" ("UnauthorisedEndpoint" :: Text) + ~~ msg (val "Invalid ARN. Dropping push message.") + view monitor >>= counterIncr (path "push.native.unauthorized") + onPushException ex = do + logError a "Native push failed" ex + view monitor >>= counterIncr (path "push.native.errors") + onTokenRemoved = do + i <- mkNotificationId + let c = a ^. addrClient + let r = singleton (target (a ^. addrUser) & targetClients .~ [c]) + let t = a ^. addrPushToken + let p = singletonPayload (PushRemove t) + Stream.add i r p =<< view (options . optSettings . setNotificationTTL) publish :: NativePush -> Address -> Aws.Amazon Result publish m a = flip catches pushException $ do @@ -133,6 +178,7 @@ publish m a = flip catches pushException $ do toResult (Left (Aws.EndpointDisabled _)) = Failure EndpointDisabled a toResult (Left (Aws.PayloadTooLarge _)) = Failure PayloadTooLarge a toResult (Left (Aws.InvalidEndpoint _)) = Failure EndpointInvalid a + toResult (Left (Aws.UnauthorisedEndpoint _)) = Failure EndpointUnauthorised a toResult (Right ()) = Success a pushException = [ Handler (\(ex :: SomeAsyncException) -> throwM ex), diff --git a/services/gundeck/src/Gundeck/Push/Native/Types.hs b/services/gundeck/src/Gundeck/Push/Native/Types.hs index 07ac8a27af..d191bfb045 100644 --- a/services/gundeck/src/Gundeck/Push/Native/Types.hs +++ b/services/gundeck/src/Gundeck/Push/Native/Types.hs @@ -102,6 +102,7 @@ data Failure = PayloadTooLarge | EndpointInvalid | EndpointDisabled + | EndpointUnauthorised | PushException !SomeException deriving (Show) diff --git a/services/gundeck/src/Gundeck/Redis.hs b/services/gundeck/src/Gundeck/Redis.hs index 36298b0f67..c8f4b8e946 100644 --- a/services/gundeck/src/Gundeck/Redis.hs +++ b/services/gundeck/src/Gundeck/Redis.hs @@ -1,6 +1,6 @@ +{-# LANGUAGE NumDecimals #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} -- This file is part of the Wire Server implementation. @@ -22,16 +22,13 @@ module Gundeck.Redis ( RobustConnection, - rrConnection, - rrReconnect, connectRobust, runRobust, PingException, ) where -import Control.Concurrent.Extra (once) -import Control.Lens +import Control.Concurrent.Async (Async, async) import qualified Control.Monad.Catch as Catch import Control.Retry import Database.Redis @@ -44,16 +41,7 @@ import System.Logger.Extended import UnliftIO.Exception -- | Connection to Redis which allows reconnecting. -type RobustConnection = MVar ReConnection - -data ReConnection = ReConnection - { -- | established (and potentially breaking) connection to Redis - _rrConnection :: Connection, - -- | action which can be called to reconnect to Redis - _rrReconnect :: IO () - } - -makeLenses ''ReConnection +type RobustConnection = MVar Connection -- | Connection to Redis which can be reestablished on connection errors. -- @@ -69,11 +57,22 @@ connectRobust :: RetryPolicy -> -- | action returning a fresh initial 'Connection', e. g., @(checkedConnect connInfo)@ or @(checkedConnectCluster connInfo)@ IO Connection -> - IO RobustConnection + IO (Async (), RobustConnection) connectRobust l retryStrategy connectLowLevel = do - robustConnection <- newEmptyMVar @IO @ReConnection - retry $ reconnectRedis robustConnection - pure robustConnection + robustConnection <- newEmptyMVar @IO @Connection + thread <- + async $ safeForever $ do + Log.info l $ Log.msg (Log.val "connecting to Redis") + conn <- retry connectLowLevel + Log.info l $ Log.msg (Log.val "successfully connected to Redis") + putMVar robustConnection conn + catch + ( forever $ do + _ <- runRedis conn ping + threadDelay 1e6 + ) + $ \(_ :: SomeException) -> void $ takeMVar robustConnection + pure (thread, robustConnection) where retry = recovering -- retry connecting, e. g., with exponential back-off @@ -86,17 +85,6 @@ connectRobust l retryStrategy connectLowLevel = do const $ Catch.Handler (\(e :: IOException) -> logEx (Log.err l) e "network error when connecting to Redis" >> pure True) ] . const -- ignore RetryStatus - reconnectRedis robustConnection = do - Log.info l $ Log.msg (Log.val "connecting to Redis") - conn <- connectLowLevel - Log.info l $ Log.msg (Log.val "successfully connected to Redis") - - reconnectOnce <- once . retry $ reconnectRedis robustConnection -- avoid concurrent attempts to reconnect - let newReConnection = ReConnection {_rrConnection = conn, _rrReconnect = reconnectOnce} - unlessM (tryPutMVar robustConnection newReConnection) $ - void $ - swapMVar robustConnection newReConnection - logEx :: Show e => ((Msg -> Msg) -> IO ()) -> e -> ByteString -> IO () logEx lLevel e description = lLevel $ Log.msg (Log.val description) . Log.field "error" (show e) @@ -104,20 +92,20 @@ connectRobust l retryStrategy connectLowLevel = do -- -- Blocks on connection errors as long as the connection is not reestablished. -- Without externally enforcing timeouts, this may lead to leaking threads. -runRobust :: (MonadUnliftIO m, MonadLogger m) => RobustConnection -> Redis a -> m a -runRobust mvar action = do +runRobust :: (MonadUnliftIO m, MonadLogger m, Catch.MonadMask m) => RobustConnection -> Redis a -> m a +runRobust mvar action = retry $ do robustConnection <- readMVar mvar - catches - (liftIO $ runRedis (_rrConnection robustConnection) action) - [ logAndHandle $ Handler (\(_ :: ConnectionLostException) -> reconnectRetry robustConnection), -- Redis connection lost during request - logAndHandle $ Handler (\(_ :: IOException) -> reconnectRetry robustConnection) -- Redis unreachable - ] + liftIO $ runRedis robustConnection action where - reconnectRetry robustConnection = do - liftIO $ _rrReconnect robustConnection - runRobust mvar action - - logAndHandle (Handler handler) = + retryStrategy = capDelay 1000000 (exponentialBackoff 50000) + retry = + recovering -- retry connecting, e. g., with exponential back-off + retryStrategy + [ logAndHandle $ Catch.Handler (\(_ :: ConnectionLostException) -> pure True), + logAndHandle $ Catch.Handler (\(_ :: IOException) -> pure True) + ] + . const -- ignore RetryStatus + logAndHandle (Handler handler) _ = Handler $ \e -> do LogClass.err $ Log.msg (Log.val "Redis connection failed") . Log.field "error" (show e) handler e @@ -125,3 +113,13 @@ runRobust mvar action = do data PingException = PingException Reply deriving (Show) instance Exception PingException + +safeForever :: + forall m. + (MonadUnliftIO m) => + m () -> + m () +safeForever action = + forever $ + action `catchAny` \_ -> do + threadDelay 1e6 -- pause to keep worst-case noise in logs manageable diff --git a/services/gundeck/src/Gundeck/Run.hs b/services/gundeck/src/Gundeck/Run.hs index c8fc2eb908..66062f96e6 100644 --- a/services/gundeck/src/Gundeck/Run.hs +++ b/services/gundeck/src/Gundeck/Run.hs @@ -41,7 +41,6 @@ import qualified Gundeck.Env as Env import Gundeck.Monad import Gundeck.Options import Gundeck.React -import qualified Gundeck.Redis as Redis import Gundeck.ThreadBudget import Imports hiding (head) import Network.Wai as Wai @@ -59,7 +58,7 @@ import Wire.API.Routes.Version.Wai run :: Opts -> IO () run o = do m <- metrics - e <- createEnv m o + (rThreads, e) <- createEnv m o runClient (e ^. cstate) $ versionCheck schemaVersion let l = e ^. applog @@ -74,8 +73,9 @@ run o = do Async.cancel lst Async.cancel wCollectAuth forM_ wtbs Async.cancel - Redis.disconnect . (^. Redis.rrConnection) =<< takeMVar (e ^. rstate) - whenJust (e ^. rstateAdditionalWrite) $ (=<<) (Redis.disconnect . (^. Redis.rrConnection)) . takeMVar + forM_ rThreads Async.cancel + Redis.disconnect =<< takeMVar (e ^. rstate) + whenJust (e ^. rstateAdditionalWrite) $ (=<<) Redis.disconnect . takeMVar Log.close (e ^. applog) where middleware :: Env -> Wai.Middleware diff --git a/services/gundeck/test/integration/API.hs b/services/gundeck/test/integration/API.hs index 9e336b176d..52498d0fa4 100644 --- a/services/gundeck/test/integration/API.hs +++ b/services/gundeck/test/integration/API.hs @@ -1246,7 +1246,7 @@ randomUser = do object [ "name" .= e, "email" .= e, - "password" .= ("secret" :: Text) + "password" .= ("secret-8-chars-long-at-least" :: Text) ] r <- post (runBrigR br . path "/i/users" . json p) pure diff --git a/services/gundeck/test/integration/Util.hs b/services/gundeck/test/integration/Util.hs index fed87835bd..393486593e 100644 --- a/services/gundeck/test/integration/Util.hs +++ b/services/gundeck/test/integration/Util.hs @@ -24,7 +24,7 @@ withSettingsOverrides f action = do ts <- ask let opts = f (view tsOpts ts) m <- metrics - env <- liftIO $ createEnv m opts + (_rThreads, env) <- liftIO $ createEnv m opts liftIO . lowerCodensity $ do let app = mkApp env p <- withMockServer app diff --git a/services/nginz/integration-test/conf/nginz/nginx.conf b/services/nginz/integration-test/conf/nginz/nginx.conf index f6db101baf..a2449bbbd5 100644 --- a/services/nginz/integration-test/conf/nginz/nginx.conf +++ b/services/nginz/integration-test/conf/nginz/nginx.conf @@ -118,6 +118,8 @@ http { zauth_keystore resources/zauth/pubkeys.txt; zauth_acl conf/nginz/zauth_acl.txt; + # needs to be kept in sync with services/brig/test/resources/oauth/ed25519.jwk + oauth_pub_key resources/oauth/ed25519_public.jwk; location /status { set $sanitized_request $request; @@ -213,6 +215,7 @@ http { location ~* ^(/v[0-9]+)?/self$ { include common_response_with_zauth.conf; + oauth_scope self; proxy_pass http://brig; } @@ -271,11 +274,41 @@ http { proxy_pass http://brig; } - location /system/settings/unauthorized$ { + location ~* ^(/v[0-9]+)?/system/settings/unauthorized$ { include common_response_no_zauth.conf; proxy_pass http://brig; } + location ~* ^(/v[0-9]+)?/system/settings$ { + include common_response_with_zauth.conf; + proxy_pass http://brig; + } + + location ~* ^/oauth/clients/([^/]*)$ { + include common_response_with_zauth.conf; + proxy_pass http://brig; + } + + location ~* ^/oauth/authorization/codes$ { + include common_response_with_zauth.conf; + proxy_pass http://brig; + } + + location /oauth/token { + include common_response_no_zauth.conf; + proxy_pass http://brig; + } + + location /oauth/revoke { + include common_response_no_zauth.conf; + proxy_pass http://brig; + } + + location /oauth/applications { + include common_response_with_zauth.conf; + proxy_pass http://brig; + } + # Cargohold Endpoints location /assets { @@ -300,6 +333,18 @@ http { proxy_pass http://galley; } + location ~* ^(/v[0-9]+)?/conversations$ { + include common_response_with_zauth.conf; + oauth_scope conversations; + proxy_pass http://galley; + } + + location ~* ^(/v[0-9]+)?/conversations/([^/]*)/code { + include common_response_with_zauth.conf; + oauth_scope conversations_code; + proxy_pass http://galley; + } + location ~* ^(/v[0-9]+)?/conversations.* { include common_response_with_zauth.conf; proxy_pass http://galley; @@ -360,7 +405,13 @@ http { proxy_pass http://galley; } - location ~* ^/feature-configs(.*) { + location ~* ^(/v[0-9]+)?/feature-configs$ { + include common_response_with_zauth.conf; + oauth_scope feature_configs; + proxy_pass http://galley; + } + + location ~* ^(/v[0-9]+)?/feature-configs(.*) { include common_response_with_zauth.conf; proxy_pass http://galley; } diff --git a/services/nginz/integration-test/resources/oauth/ed25519_public.jwk b/services/nginz/integration-test/resources/oauth/ed25519_public.jwk new file mode 100644 index 0000000000..9ef33c1ce2 --- /dev/null +++ b/services/nginz/integration-test/resources/oauth/ed25519_public.jwk @@ -0,0 +1 @@ +{"kty":"OKP","crv":"Ed25519","x":"mhP-NgFw3ifIXGZqJVB0kemt9L3BtD5P8q4Gah4Iklc"} diff --git a/services/nginz/third_party/nginx-zauth-module/zauth_module.c b/services/nginz/third_party/nginx-zauth-module/zauth_module.c index c9d2ce3540..11e3eba09e 100644 --- a/services/nginz/third_party/nginx-zauth-module/zauth_module.c +++ b/services/nginz/third_party/nginx-zauth-module/zauth_module.c @@ -6,6 +6,32 @@ #include #include +typedef struct { + ZauthKeystore * keystore; + ZauthAcl * acl; + OAuthPubJwk * oauth_pub_key; +} ZauthServerConf; + +typedef struct { + ngx_flag_t zauth; // 1=on, 0=off + ngx_str_t oauth_scope; +} ZauthLocationConf; + +enum { + CONTEXT_ZAUTH, + CONTEXT_OAUTH +}; + +typedef struct { + ngx_int_t tag; + union { + // valid if tag == CONTEXT_ZAUTH + ZauthToken * token; + // valid if tag == CONTEXT_OAUTH + char * user_id; + }; +} ZauthContext; + // Configuration setup static void * create_srv_conf (ngx_conf_t *); static void * create_loc_conf (ngx_conf_t *); @@ -13,35 +39,35 @@ static char * merge_loc_conf (ngx_conf_t *, void *, void *); static char * merge_srv_conf (ngx_conf_t *, void *, void *); static char * load_keystore (ngx_conf_t *, ngx_command_t *, void *); static char * load_acl (ngx_conf_t *, ngx_command_t *, void *); +static char * load_oauth_key (ngx_conf_t *, ngx_command_t *, void *); static void delete_srv_conf (void *); // Module setup -static ngx_int_t zauth_init (ngx_conf_t *); -static ngx_int_t zauth_parse_request (ngx_http_request_t *); -static ngx_int_t zauth_handle_request (ngx_http_request_t *); +static ngx_int_t zauth_init (ngx_conf_t *); +static ngx_int_t zauth_parse_request (ngx_http_request_t *); +static ngx_int_t zauth_and_oauth_handle_request (ngx_http_request_t *); // Request Inspection -static ZauthResult token_from_header (ngx_str_t const *, ZauthToken **); -static ZauthResult token_from_query (ngx_str_t const *, ZauthToken **); -static void delete_token (void *); +static ZauthResult token_from_header (ngx_str_t const *, ZauthToken **); +static ZauthResult token_from_query (ngx_str_t const *, ZauthToken **); +static ZauthContext * alloc_zauth_context (ngx_http_request_t * r, ZauthToken *); +static ZauthContext * alloc_oauth_context (ngx_http_request_t * r, char *); +static ngx_int_t setup_zauth_context (ngx_http_request_t * , ZauthContext *); +static void delete_zauth_context (void *); // Variable manipulation static ngx_int_t zauth_variables (ngx_conf_t *); static ngx_int_t zauth_token_var (ngx_http_request_t *, ngx_http_variable_value_t *, uintptr_t); +static ngx_int_t zauth_token_var_user (ngx_http_request_t *, ngx_http_variable_value_t *, uintptr_t); static ngx_int_t zauth_token_var_conn (ngx_http_request_t *, ngx_http_variable_value_t *, uintptr_t); static ngx_int_t zauth_token_var_conv (ngx_http_request_t *, ngx_http_variable_value_t *, uintptr_t); static ngx_int_t zauth_token_typeinfo (ngx_http_request_t *, ngx_http_variable_value_t *, uintptr_t); static ngx_int_t zauth_set_var (ngx_pool_t *, ngx_http_variable_value_t *, Range); static void zauth_empty_val (ngx_http_variable_value_t *); -typedef struct { - ZauthKeystore * keystore; - ZauthAcl * acl; -} ZauthServerConf; - -typedef struct { - ngx_flag_t toggle; -} ZauthLocationConf; +// Utility functions +static ngx_int_t zauth_handle_request (ngx_http_request_t *, const ZauthServerConf *, ZauthToken const *); +static ngx_int_t oauth_handle_request(ngx_http_request_t *, OAuthPubJwk const *, ngx_str_t const); static ngx_http_module_t zauth_module_ctx = { zauth_variables // pre-configuration @@ -59,7 +85,15 @@ static ngx_command_t zauth_commands [] = { , NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1 , ngx_conf_set_flag_slot , NGX_HTTP_LOC_CONF_OFFSET - , offsetof (ZauthLocationConf, toggle) + , offsetof (ZauthLocationConf, zauth) + , NULL + } + + , { ngx_string ("oauth_scope") + , NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1 + , ngx_conf_set_str_slot + , NGX_HTTP_LOC_CONF_OFFSET + , offsetof (ZauthLocationConf, oauth_scope) , NULL } @@ -79,6 +113,14 @@ static ngx_command_t zauth_commands [] = { , NULL } + , { ngx_string ("oauth_pub_key") + , NGX_HTTP_SRV_CONF | NGX_CONF_TAKE1 + , load_oauth_key + , NGX_HTTP_SRV_CONF_OFFSET + , 0 + , NULL + } + , ngx_null_command }; @@ -135,6 +177,10 @@ static char * merge_srv_conf (ngx_conf_t * c, void * pc, void * cc) { child->acl = parent->acl; } + if (child->oauth_pub_key == NULL) { + child->oauth_pub_key = parent->oauth_pub_key; + } + if (child->keystore == NULL) { ngx_conf_log_error(NGX_LOG_EMERG, c, 0, "missing 'zauth_keystore'"); return NGX_CONF_ERROR; @@ -145,6 +191,10 @@ static char * merge_srv_conf (ngx_conf_t * c, void * pc, void * cc) { return NGX_CONF_ERROR; } + if (child->oauth_pub_key == NULL) { + ngx_conf_log_error(NGX_LOG_NOTICE, c, 0, "missing 'oauth_pub_key'"); + } + return NGX_CONF_OK; } @@ -156,6 +206,9 @@ static void delete_srv_conf (void * data) { if (c->acl != NULL) { zauth_acl_delete(c->acl); } + if (c->oauth_pub_key != NULL) { + oauth_key_delete(c->oauth_pub_key); + } } static void * create_loc_conf (ngx_conf_t * conf) { @@ -166,7 +219,7 @@ static void * create_loc_conf (ngx_conf_t * conf) { return NGX_CONF_ERROR; } - lc->toggle = NGX_CONF_UNSET; + lc->zauth = NGX_CONF_UNSET; return lc; } @@ -174,7 +227,8 @@ static void * create_loc_conf (ngx_conf_t * conf) { static char * merge_loc_conf (ngx_conf_t * _, void * pc, void * cc) { ZauthLocationConf * parent = pc; ZauthLocationConf * child = cc; - ngx_conf_merge_off_value(child->toggle, parent->toggle, 1); + ngx_conf_merge_off_value(child->zauth, parent->zauth, 1); + ngx_conf_merge_str_value(child->oauth_scope, parent->oauth_scope, NULL); return NGX_CONF_OK; } @@ -212,6 +266,22 @@ static char * load_acl (ngx_conf_t * conf, ngx_command_t * cmd, void * data) { return NGX_CONF_OK; } +static char * load_oauth_key (ngx_conf_t * conf, ngx_command_t * cmd, void * data) { + ZauthServerConf * sc = data; + if (sc == NULL) { + return NGX_CONF_ERROR; + } + + ngx_str_t * const fname = conf->args->elts; + OAuthResultStatus status = oauth_key_open(fname[1].data, fname[1].len, &sc->oauth_pub_key); + + if (status != OAUTH_OK || sc->oauth_pub_key == NULL) { + ngx_conf_log_error(NGX_LOG_NOTICE, conf, 0, "failed to load oauth key [%d]", status); + } + + return NGX_CONF_OK; +} + // Module setup ///////////////////////////////////////////////////////////// static ngx_int_t zauth_init (ngx_conf_t * conf) { @@ -236,14 +306,14 @@ static ngx_int_t zauth_init (ngx_conf_t * conf) { return NGX_ERROR; } - *h2 = zauth_handle_request; + *h2 = zauth_and_oauth_handle_request; return NGX_OK; } // Request Processing /////////////////////////////////////////////////////// -static ngx_int_t zauth_handle_request (ngx_http_request_t * r) { +static ngx_int_t zauth_and_oauth_handle_request (ngx_http_request_t * r) { ZauthServerConf const * sc = ngx_http_get_module_srv_conf(r, zauth_module); @@ -254,22 +324,33 @@ static ngx_int_t zauth_handle_request (ngx_http_request_t * r) { ZauthLocationConf const * lc = ngx_http_get_module_loc_conf(r, zauth_module); - if (lc == NULL || lc->toggle != 1) { + // if zauth is off (used for unauthenticated endpoints) we do not need to handle oauth + if (lc == NULL || lc->zauth != 1) { return NGX_DECLINED; } - ZauthToken const * tkn = ngx_http_get_module_ctx(r, zauth_module); + ZauthContext const * ctx = ngx_http_get_module_ctx(r, zauth_module); // internal redirects clear module contexts => try to parse again - if (tkn == NULL && r->internal) { + if (ctx == NULL && r->internal) { ngx_int_t status = zauth_parse_request(r); if (status != NGX_OK) { return status; } else { - tkn = ngx_http_get_module_ctx(r, zauth_module); + ctx = ngx_http_get_module_ctx(r, zauth_module); } } + if (ctx != NULL && ctx->tag == CONTEXT_ZAUTH) { + return zauth_handle_request(r, sc, ctx->token); + } else if (ctx == NULL) { + return oauth_handle_request(r, sc->oauth_pub_key, lc->oauth_scope); + } else { + return NGX_HTTP_UNAUTHORIZED; + } +} + +static ngx_int_t zauth_handle_request (ngx_http_request_t * r, const ZauthServerConf * sc, ZauthToken const * tkn) { if (tkn == NULL) { return NGX_HTTP_UNAUTHORIZED; } @@ -292,8 +373,80 @@ static ngx_int_t zauth_handle_request (ngx_http_request_t * r) { return NGX_OK; } -static void delete_token (void * data) { - zauth_token_delete((ZauthToken *) data); +ngx_int_t oauth_handle_request(ngx_http_request_t *r, OAuthPubJwk const * key, ngx_str_t const scope) { + if (r->headers_in.authorization == NULL) { + return NGX_HTTP_UNAUTHORIZED; + } + + ngx_str_t hdr = r->headers_in.authorization->value; + + if (strncmp((char const *) hdr.data, "Bearer ", 7) == 0) { + OAuthResult res = oauth_verify_token(key, &hdr.data[7], hdr.len - 7, scope.data, scope.len, r->method_name.data, r->method_name.len); + if (res.status == OAUTH_OK) { + ZauthContext * ctx = alloc_oauth_context(r, res.uid); + if (ctx == NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR; // for OOM-safety + ngx_int_t e = setup_zauth_context(r, ctx); + if (e != NGX_OK) { + ngx_free(ctx); + return e; + } + + return NGX_OK; + } else if (res.status == OAUTH_INSUFFICIENT_SCOPE) { + ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0, "OAuth insufficient scope"); + return NGX_HTTP_FORBIDDEN; + } else { + ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0, "OAuth token verification failed with: %d", res.status); + return NGX_HTTP_UNAUTHORIZED; + } + } else { + return NGX_HTTP_UNAUTHORIZED; + } +} + +static ngx_int_t setup_zauth_context(ngx_http_request_t * r, ZauthContext * ctx) { + ngx_pool_cleanup_t * finaliser = ngx_pool_cleanup_add(r->pool, 0); + if (finaliser == NULL) { + return NGX_ERROR; + } + + finaliser->handler = delete_zauth_context; + finaliser->data = ctx; + ngx_http_set_ctx(r, ctx, zauth_module); + + return NGX_OK; +} + +static ZauthContext * alloc_zauth_context(ngx_http_request_t * r, ZauthToken * token) { + ZauthContext * ctx = ngx_alloc(sizeof(ZauthContext), r->connection->log); + if (ctx == NULL) { + return ctx; + } + ctx->tag = CONTEXT_ZAUTH; + ctx->token = token; + return ctx; +} + + +static ZauthContext * alloc_oauth_context(ngx_http_request_t * r, char * user_id) { + ZauthContext * ctx = ngx_alloc(sizeof(ZauthContext), r->connection->log); + if (ctx == NULL) { + return ctx; + } + ctx->tag = CONTEXT_OAUTH; + ctx->user_id = user_id; + return ctx; +} + +static void delete_zauth_context(void * data) { + ZauthContext *ctx = data; + if (ctx->tag == CONTEXT_ZAUTH) { + zauth_token_delete(ctx->token); + } + else if (ctx->tag == CONTEXT_OAUTH) { + oauth_result_uid_delete(ctx->user_id); + } + ngx_free(ctx); } static ngx_int_t zauth_parse_request (ngx_http_request_t * r) { @@ -323,16 +476,17 @@ static ngx_int_t zauth_parse_request (ngx_http_request_t * r) { } if (res == ZAUTH_OK && tkn != NULL) { - ngx_pool_cleanup_t * finaliser = ngx_pool_cleanup_add(r->pool, 0); - if (finaliser == NULL) { - return NGX_ERROR; + ZauthContext * ctx = alloc_zauth_context(r, tkn); + if (ctx == NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR; // for OOM-safety + ngx_int_t e = setup_zauth_context(r, ctx); + if (e != NGX_OK) { + ngx_free(ctx); + return e; } - finaliser->handler = delete_token; - finaliser->data = tkn; - ngx_http_set_ctx(r, tkn, zauth_module); return NGX_OK; } + if (res != ZAUTH_OK) { ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0, "failed to parse token [%d]", res); } @@ -406,7 +560,7 @@ static ngx_int_t zauth_variables (ngx_conf_t * conf) { z_type_var->get_handler = zauth_token_typeinfo; z_bot_var->get_handler = zauth_token_var; z_bot_var->data = 'b'; - z_user_var->get_handler = zauth_token_var; + z_user_var->get_handler = zauth_token_var_user; z_user_var->data = 'u'; z_client_var->get_handler = zauth_token_var; z_client_var->data = 'i'; @@ -419,11 +573,14 @@ static ngx_int_t zauth_variables (ngx_conf_t * conf) { } static ngx_int_t zauth_token_typeinfo (ngx_http_request_t * r, ngx_http_variable_value_t * v, uintptr_t _) { - ZauthToken const * t = ngx_http_get_module_ctx(r, zauth_module); - if (t == NULL) { + ZauthContext const * ctx = ngx_http_get_module_ctx(r, zauth_module); + if (ctx == NULL) { return NGX_ERROR; } - switch (zauth_token_type(t)) { + if (ctx->tag != CONTEXT_ZAUTH) { + return NGX_OK; + } + switch (zauth_token_type(ctx->token)) { case ZAUTH_TOKEN_TYPE_BOT: { Range range = { (u_char*) "bot", 3 }; return zauth_set_var(r->pool, v, range); @@ -456,20 +613,73 @@ static ngx_int_t zauth_token_typeinfo (ngx_http_request_t * r, ngx_http_variable } static ngx_int_t zauth_token_var (ngx_http_request_t * r, ngx_http_variable_value_t * v, uintptr_t data) { - ZauthToken const * t = ngx_http_get_module_ctx(r, zauth_module); - if (t == NULL) { - return NGX_ERROR; + ZauthContext const * ctx = ngx_http_get_module_ctx(r, zauth_module); + + // this function checks if the signature has been validated successfully, + // and if access is allowed (endpoint is either allowed or not denied) according to the access control list (ACL) configuration + bool zauth_is_authorized_and_allowed() { + if (ctx == NULL || ctx->tag != CONTEXT_ZAUTH) { + return false; + } + + ZauthToken const * t = ctx->token; + + if (t == NULL) { + return false; + } + + if (zauth_token_verification(t) != ZAUTH_TOKEN_VERIFICATION_SUCCESS) { + return false; + } + + ZauthServerConf const * sc = + ngx_http_get_module_srv_conf(r, zauth_module); + + if (sc == NULL || sc->acl == NULL) { + return false; + } + + uint8_t is_allowed = 0; + + ngx_int_t res = zauth_token_allowed(t, sc->acl, r->uri.data, r->uri.len, &is_allowed); + + if (res != NGX_OK) { + return false; + } + + return is_allowed == 1; + } + + // in this function client, provider, and bot ID is retrieved from the ZAuth token + // and assigned to variables that are used in the nginx config to set the corresponding headers (e.g. Z-Client, Z-Provider, ...). + // therefore we want to make sure that the token is authorized (has a valid signature) + // and access is allowed (endpoint is either allowed or not denied) according to the access control list (ACL) configuration + // before we set the variable + // otherwise 'zauth_token_lookup' will crash for OAuth requests + if (ctx != NULL && ctx->tag == CONTEXT_ZAUTH && zauth_is_authorized_and_allowed()) { + return zauth_set_var(r->pool, v, zauth_token_lookup(ctx->token, data)); + } else { + zauth_empty_val(v); + return NGX_OK; } - return zauth_set_var(r->pool, v, zauth_token_lookup(t, data)); } -static ngx_int_t zauth_token_var_conn (ngx_http_request_t * r, ngx_http_variable_value_t * v, uintptr_t _) { - ZauthToken const * t = ngx_http_get_module_ctx(r, zauth_module); - if (t == NULL) { - return NGX_ERROR; +static ngx_int_t zauth_token_var_user (ngx_http_request_t * r, ngx_http_variable_value_t * v, uintptr_t _) { + ZauthContext const * ctx = ngx_http_get_module_ctx(r, zauth_module); + if (ctx != NULL && ctx->tag == CONTEXT_ZAUTH) { + return zauth_set_var(r->pool, v, zauth_token_lookup(ctx->token, 'u')); + } else if (ctx != NULL && ctx->tag == CONTEXT_OAUTH) { + return zauth_set_var(r->pool, v, (Range) { (u_char*) ctx->user_id, strlen(ctx->user_id) }); + } else { + zauth_empty_val(v); + return NGX_OK; } - if (zauth_token_type(t) == ZAUTH_TOKEN_TYPE_ACCESS || zauth_token_type(t) == ZAUTH_TOKEN_TYPE_LEGAL_HOLD_ACCESS) { - return zauth_set_var(r->pool, v, zauth_token_lookup(t, 'c')); +} + +static ngx_int_t zauth_token_var_conn (ngx_http_request_t * r, ngx_http_variable_value_t * v, uintptr_t _) { + ZauthContext const * ctx = ngx_http_get_module_ctx(r, zauth_module); + if (ctx != NULL && ctx->tag == CONTEXT_ZAUTH && (zauth_token_type(ctx->token) == ZAUTH_TOKEN_TYPE_ACCESS || zauth_token_type(ctx->token) == ZAUTH_TOKEN_TYPE_LEGAL_HOLD_ACCESS)) { + return zauth_set_var(r->pool, v, zauth_token_lookup(ctx->token, 'c')); } else { zauth_empty_val(v); return NGX_OK; @@ -477,12 +687,9 @@ static ngx_int_t zauth_token_var_conn (ngx_http_request_t * r, ngx_http_variable } static ngx_int_t zauth_token_var_conv (ngx_http_request_t * r, ngx_http_variable_value_t * v, uintptr_t _) { - ZauthToken const * t = ngx_http_get_module_ctx(r, zauth_module); - if (t == NULL) { - return NGX_ERROR; - } - if (zauth_token_type(t) == ZAUTH_TOKEN_TYPE_BOT) { - return zauth_set_var(r->pool, v, zauth_token_lookup(t, 'c')); + ZauthContext const * ctx = ngx_http_get_module_ctx(r, zauth_module); + if (ctx != NULL && ctx->tag == CONTEXT_ZAUTH && zauth_token_type(ctx->token) == ZAUTH_TOKEN_TYPE_BOT) { + return zauth_set_var(r->pool, v, zauth_token_lookup(ctx->token, 'c')); } else { zauth_empty_val(v); return NGX_OK; diff --git a/services/proxy/proxy.cabal b/services/proxy/proxy.cabal index 784b5d854a..4972afa21a 100644 --- a/services/proxy/proxy.cabal +++ b/services/proxy/proxy.cabal @@ -39,6 +39,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -50,6 +51,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -118,6 +120,7 @@ executable proxy DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -129,6 +132,7 @@ executable proxy MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/services/run-services b/services/run-services index 9fdbf894e6..aafd3c63b8 100755 --- a/services/run-services +++ b/services/run-services @@ -10,17 +10,17 @@ import shutil import socket import subprocess import yaml -import urllib.request -import urllib.error import sys import tempfile import time import traceback import threading +import requests @dataclass class SpawnFailException(Exception): failed_instances: object + failing_status_responses: object class Colors: GREEN = "\x1b[38;5;10m" @@ -51,7 +51,7 @@ class Service: return self.name else: return self._internal_name - + def path(self): return os.path.join(ROOT, "dist", self.name) @@ -107,6 +107,18 @@ class Nginz: if shutil.which("nginx") is None: raise Exception("nginx not found") +@dataclass +class StatusResponse: + http_response: object = None + good_status: list[int] = None + + def __bool__(self): + if self.http_response is None: + return False + else: + return self.http_response.status_code in self.good_status + + @dataclass(frozen=True) class Instance: service: Service @@ -122,9 +134,9 @@ class Instance: if not self.service.check_status: return True try: - with urllib.request.urlopen(f"http://localhost:{self.port}/i/status") as resp: - return resp.status in [200, 204] - except urllib.error.URLError: + resp = requests.get(f"http://localhost:{self.port}/i/status") + return StatusResponse(resp, [200, 204]) + except Exception as e: return False def spawn(self, service_map, environment, suffix, domain, backend_name): @@ -323,6 +335,9 @@ def start_backend(services, suffix, domain, backend_name): # check instances to_be_checked = [instance for instance in instances if instance.exception is None] + + failing_status_responses = {} + start_time = time.time() while to_be_checked: if time.time() - start_time >= 5: @@ -333,8 +348,10 @@ def start_backend(services, suffix, domain, backend_name): to_be_checked_again = set() for instance in to_be_checked: try: - if not instance.check_status(): + status = instance.check_status() + if not status: to_be_checked_again.add(instance) + failing_status_responses[instance] = status except Exception as e: failed_instances.append(replace(instance, exception=e)) @@ -346,7 +363,7 @@ def start_backend(services, suffix, domain, backend_name): if failed_instances: cleanup_instances(instances) - raise SpawnFailException(failed_instances) + raise SpawnFailException(failed_instances, failing_status_responses) return instances @@ -388,6 +405,7 @@ if __name__ == '__main__': Instance(CANNON2, 8183), Instance(CARGOHOLD, 8084), Instance(SPAR, 8088), + Instance(STERN, 8091), DummyInstance(PROXY, 8087), FederatorInstance(8097, 8098), NginzInstance( @@ -439,5 +457,10 @@ if __name__ == '__main__': for instance in e.failed_instances: print(f"{instance.service.name} at port {instance.port}" + (f" ({instance.exception})" if instance.exception else "")) + + for instance, status in e.failing_status_responses.items(): + if isinstance(status, StatusResponse): + print(f"{instance.service.name} responded with status " + + "{status.http_response.status_code} and body:\n " + status.http_response.text) finally: cleanup_instances(instances) diff --git a/services/spar/spar.cabal b/services/spar/spar.cabal index c3e43ca61b..f4c8a42053 100644 --- a/services/spar/spar.cabal +++ b/services/spar/spar.cabal @@ -92,6 +92,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -103,6 +104,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -211,6 +213,7 @@ executable spar DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -222,6 +225,7 @@ executable spar MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -350,6 +354,7 @@ executable spar-integration DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -361,6 +366,7 @@ executable spar-integration MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -490,6 +496,7 @@ executable spar-migrate-data DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -501,6 +508,7 @@ executable spar-migrate-data MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -634,6 +642,7 @@ executable spar-schema DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -645,6 +654,7 @@ executable spar-schema MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -770,6 +780,7 @@ test-suite spec DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -781,6 +792,7 @@ test-suite spec MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/services/spar/src/Spar/Intra/Brig.hs b/services/spar/src/Spar/Intra/Brig.hs index cc431a1d49..db6ea684d2 100644 --- a/services/spar/src/Spar/Intra/Brig.hs +++ b/services/spar/src/Spar/Intra/Brig.hs @@ -53,7 +53,7 @@ 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 Data.Misc (PlainTextPassword6) import qualified Data.Text.Lazy as Lazy import Imports import Network.HTTP.Types.Method @@ -355,7 +355,7 @@ deleteBrigUserInternal buid = do ensureReAuthorised :: (HasCallStack, MonadSparToBrig m) => Maybe UserId -> - Maybe PlainTextPassword -> + Maybe PlainTextPassword6 -> Maybe Code.Value -> Maybe VerificationAction -> m () diff --git a/services/spar/src/Spar/Scim.hs b/services/spar/src/Spar/Scim.hs index 43fba22000..c37857f398 100644 --- a/services/spar/src/Spar/Scim.hs +++ b/services/spar/src/Spar/Scim.hs @@ -1,4 +1,3 @@ -{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE OverloadedLists #-} {-# OPTIONS_GHC -Wno-orphans #-} diff --git a/services/spar/src/Spar/Scim/Types.hs b/services/spar/src/Spar/Scim/Types.hs index 0df82a6ecd..24862ba51e 100644 --- a/services/spar/src/Spar/Scim/Types.hs +++ b/services/spar/src/Spar/Scim/Types.hs @@ -1,5 +1,4 @@ {-# LANGUAGE DataKinds #-} -{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE LambdaCase #-} diff --git a/services/spar/src/Spar/Sem/BrigAccess.hs b/services/spar/src/Spar/Sem/BrigAccess.hs index 2d71f05387..38e1b65cc5 100644 --- a/services/spar/src/Spar/Sem/BrigAccess.hs +++ b/services/spar/src/Spar/Sem/BrigAccess.hs @@ -48,7 +48,7 @@ import Brig.Types.User import Data.Code as Code import Data.Handle (Handle) import Data.Id (TeamId, UserId) -import Data.Misc (PlainTextPassword) +import Data.Misc (PlainTextPassword6) import Imports import Polysemy import qualified SAML2.WebSSO as SAML @@ -76,7 +76,7 @@ data BrigAccess m a where GetRichInfo :: UserId -> BrigAccess m RichInfo CheckHandleAvailable :: Handle -> BrigAccess m Bool DeleteUser :: UserId -> BrigAccess m DeleteUserResult - EnsureReAuthorised :: Maybe UserId -> Maybe PlainTextPassword -> Maybe Code.Value -> Maybe VerificationAction -> BrigAccess m () + EnsureReAuthorised :: Maybe UserId -> Maybe PlainTextPassword6 -> 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/test-integration/Test/Spar/Scim/AuthSpec.hs b/services/spar/test-integration/Test/Spar/Scim/AuthSpec.hs index 5b3f6f810b..04616057de 100644 --- a/services/spar/test-integration/Test/Spar/Scim/AuthSpec.hs +++ b/services/spar/test-integration/Test/Spar/Scim/AuthSpec.hs @@ -35,7 +35,7 @@ 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.Misc import Data.Range (unsafeRange) import Data.String.Conversions (cs) import Data.Text.Ascii (AsciiChars (validate)) @@ -278,7 +278,7 @@ testCreateTokenRequiresPassword = do owner CreateScimToken { createScimTokenDescr = "testCreateTokenRequiresPassword", - createScimTokenPassword = Just (PlainTextPassword "wrong password"), + createScimTokenPassword = Just (plainTextPassword6Unsafe "wrong password"), createScimTokenCode = Nothing } (env ^. teSpar) diff --git a/services/spar/test-integration/Util/Core.hs b/services/spar/test-integration/Util/Core.hs index 322d810286..4b732a7094 100644 --- a/services/spar/test-integration/Util/Core.hs +++ b/services/spar/test-integration/Util/Core.hs @@ -156,7 +156,7 @@ import qualified Data.ByteString.Base64.Lazy as EL import Data.ByteString.Conversion import Data.Handle (Handle (Handle)) import Data.Id -import Data.Misc (PlainTextPassword (..)) +import Data.Misc (PlainTextPassword6, plainTextPassword6Unsafe) import Data.Proxy import Data.Range import Data.String.Conversions @@ -691,8 +691,8 @@ postUser name haveEmail ssoid teamid brig_ = do ] post (brig_ . path "/i/users" . contentJson . body p) -defPassword :: PlainTextPassword -defPassword = PlainTextPassword "secret" +defPassword :: PlainTextPassword6 +defPassword = plainTextPassword6Unsafe "topsecretdefaultpassword" defCookieLabel :: CookieLabel defCookieLabel = CookieLabel "auth" diff --git a/services/spar/test-integration/Util/Email.hs b/services/spar/test-integration/Util/Email.hs index 6e72c45d81..3e37393ea6 100644 --- a/services/spar/test-integration/Util/Email.hs +++ b/services/spar/test-integration/Util/Email.hs @@ -58,7 +58,7 @@ changeEmailBrig brig usr newEmail = do pure (decodeCookie rsp, decodeToken rsp) changeEmailBrigCreds brig cky tok newEmail where - emailLogin :: Email -> Misc.PlainTextPassword -> Maybe Auth.CookieLabel -> Auth.Login + emailLogin :: Email -> Misc.PlainTextPassword6 -> Maybe Auth.CookieLabel -> Auth.Login emailLogin e pw cl = Auth.PasswordLogin $ Auth.PasswordLoginData (Auth.LoginByEmail e) pw cl Nothing diff --git a/tools/api-simulations/api-simulations.cabal b/tools/api-simulations/api-simulations.cabal index c21e404fb8..a2939ec0ca 100644 --- a/tools/api-simulations/api-simulations.cabal +++ b/tools/api-simulations/api-simulations.cabal @@ -30,6 +30,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -41,6 +42,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -98,6 +100,7 @@ executable api-loadtest DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -109,6 +112,7 @@ executable api-loadtest MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -174,6 +178,7 @@ executable api-smoketest DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -185,6 +190,7 @@ executable api-smoketest MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/tools/db/assets/assets.cabal b/tools/db/assets/assets.cabal index ec68240c60..808ddbc7c0 100644 --- a/tools/db/assets/assets.cabal +++ b/tools/db/assets/assets.cabal @@ -62,6 +62,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -73,6 +74,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/tools/db/auto-whitelist/auto-whitelist.cabal b/tools/db/auto-whitelist/auto-whitelist.cabal index 39e886e058..cc4e7aa066 100644 --- a/tools/db/auto-whitelist/auto-whitelist.cabal +++ b/tools/db/auto-whitelist/auto-whitelist.cabal @@ -30,6 +30,7 @@ executable auto-whitelist DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -41,6 +42,7 @@ executable auto-whitelist MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/tools/db/billing-team-member-backfill/billing-team-member-backfill.cabal b/tools/db/billing-team-member-backfill/billing-team-member-backfill.cabal index ecb36e45b2..8b482b619d 100644 --- a/tools/db/billing-team-member-backfill/billing-team-member-backfill.cabal +++ b/tools/db/billing-team-member-backfill/billing-team-member-backfill.cabal @@ -30,6 +30,7 @@ executable billing-team-member-backfill DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -41,6 +42,7 @@ executable billing-team-member-backfill MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/tools/db/find-undead/find-undead.cabal b/tools/db/find-undead/find-undead.cabal index ce86b5fb5b..0a647b67b8 100644 --- a/tools/db/find-undead/find-undead.cabal +++ b/tools/db/find-undead/find-undead.cabal @@ -30,6 +30,7 @@ executable find-undead DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -41,6 +42,7 @@ executable find-undead MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/tools/db/inconsistencies/inconsistencies.cabal b/tools/db/inconsistencies/inconsistencies.cabal index 152c074468..2608bca9f6 100644 --- a/tools/db/inconsistencies/inconsistencies.cabal +++ b/tools/db/inconsistencies/inconsistencies.cabal @@ -33,6 +33,7 @@ executable inconsistencies DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -44,6 +45,7 @@ executable inconsistencies MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/tools/db/migrate-sso-feature-flag/migrate-sso-feature-flag.cabal b/tools/db/migrate-sso-feature-flag/migrate-sso-feature-flag.cabal index dc1e178d0a..7ed688aa81 100644 --- a/tools/db/migrate-sso-feature-flag/migrate-sso-feature-flag.cabal +++ b/tools/db/migrate-sso-feature-flag/migrate-sso-feature-flag.cabal @@ -32,6 +32,7 @@ executable migrate-sso-feature-flag DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -43,6 +44,7 @@ executable migrate-sso-feature-flag MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/tools/db/move-team/move-team.cabal b/tools/db/move-team/move-team.cabal index f69508987a..e1578422f9 100644 --- a/tools/db/move-team/move-team.cabal +++ b/tools/db/move-team/move-team.cabal @@ -33,6 +33,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -44,6 +45,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -111,6 +113,7 @@ executable move-team DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -122,6 +125,7 @@ executable move-team MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -190,6 +194,7 @@ executable move-team-generate DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -201,6 +206,7 @@ executable move-team-generate MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/tools/db/repair-handles/repair-handles.cabal b/tools/db/repair-handles/repair-handles.cabal index c0dc0a4e6f..8f1ab7f962 100644 --- a/tools/db/repair-handles/repair-handles.cabal +++ b/tools/db/repair-handles/repair-handles.cabal @@ -31,6 +31,7 @@ executable repair-handles DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -42,6 +43,7 @@ executable repair-handles MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/tools/db/service-backfill/service-backfill.cabal b/tools/db/service-backfill/service-backfill.cabal index 7d76f7902e..373abe3afa 100644 --- a/tools/db/service-backfill/service-backfill.cabal +++ b/tools/db/service-backfill/service-backfill.cabal @@ -30,6 +30,7 @@ executable service-backfill DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -41,6 +42,7 @@ executable service-backfill MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/tools/fedcalls/fedcalls.cabal b/tools/fedcalls/fedcalls.cabal index 4d308d7564..01846e6eac 100644 --- a/tools/fedcalls/fedcalls.cabal +++ b/tools/fedcalls/fedcalls.cabal @@ -27,6 +27,7 @@ executable fedcalls DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -38,6 +39,7 @@ executable fedcalls MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms diff --git a/tools/ormolu.sh b/tools/ormolu.sh index 44a9452e70..901baa11e0 100755 --- a/tools/ormolu.sh +++ b/tools/ormolu.sh @@ -61,9 +61,8 @@ if [ "$(git status -s | grep -v \?\?)" != "" ]; then fi fi -readarray -t EXTS < <(sed -n '/^default-extensions:/,$ { s/^- //p }' < package-defaults.yaml) echo "ormolu mode: $ARG_ORMOLU_MODE" -echo "language extensions: ${EXTS[@]}" +echo "language extensions are taken from the resp. cabal files" FAILURES=0 @@ -84,7 +83,7 @@ for hsfile in $files; do FAILED=0 # run in background so that we can detect Ctrl-C properly - ormolu --mode $ARG_ORMOLU_MODE --check-idempotence ${EXTS[@]/#/'-o -X'} "$hsfile" & + ormolu --mode $ARG_ORMOLU_MODE --check-idempotence "$hsfile" & wait $! && err=0 || err=$? if [ "$err" == "100" ]; then diff --git a/tools/stern/default.nix b/tools/stern/default.nix index 894be93283..a48f9e4a26 100644 --- a/tools/stern/default.nix +++ b/tools/stern/default.nix @@ -4,51 +4,116 @@ # dependencies are added or removed. { mkDerivation , aeson +, aeson-qq +, amazonka +, amazonka-sqs +, async , base +, base64-bytestring , bilge +, binary , brig-types , bytestring , bytestring-conversion +, call-stack +, case-insensitive +, cassandra-util +, cassava +, cereal +, comonad , containers +, cookie +, cryptonite +, currency-codes , data-default +, data-timeout +, directory , errors , exceptions , extended +, extra +, federator +, filepath +, galley , galley-types , gitignoreSource , gundeck-types +, hex +, HsOpenSSL +, HsOpenSSL-x509-system +, hspec , http-client +, http-client-openssl +, http-client-tls +, http-media , http-types , imports +, kan-extensions , lens +, lens-aeson , lib +, memory , metrics-wai , mtl +, network +, network-uri +, optparse-applicative +, pem +, process +, proto-lens +, protobuf +, QuickCheck +, quickcheck-instances +, random +, raw-strings-qq , retry +, safe +, saml2-web-sso , schema-profunctor , servant +, servant-client +, servant-client-core , servant-server , servant-swagger , servant-swagger-ui +, singletons +, singletons-th +, sop-core , split +, ssl-util +, streaming-commons , string-conversions , swagger2 +, tagged , tasty +, tasty-cannon , tasty-hunit +, temporary , text +, time , tinylog +, tls , transformers , types-common +, types-common-aws +, types-common-journal +, unix , unliftio , unordered-containers +, uri-bytestring , uuid +, uuid-types +, vector , wai , wai-extra , wai-predicates , wai-routing , wai-utilities , warp +, warp-tls , wire-api +, wire-api-federation +, wire-message-proto-lens , yaml }: mkDerivation { @@ -103,13 +168,112 @@ mkDerivation { yaml ]; executableHaskellDepends = [ + aeson + aeson-qq + amazonka + amazonka-sqs + async base + base64-bytestring + bilge + binary + brig-types + bytestring + bytestring-conversion + call-stack + case-insensitive + cassandra-util + cassava + cereal + comonad + containers + cookie + cryptonite + currency-codes + data-default + data-timeout + directory + errors + exceptions extended + extra + federator + filepath + galley + galley-types + gundeck-types + hex + HsOpenSSL + HsOpenSSL-x509-system + hspec + http-client + http-client-openssl + http-client-tls + http-media + http-types imports + kan-extensions + lens + lens-aeson + memory + metrics-wai + mtl + network + network-uri + optparse-applicative + pem + process + proto-lens + protobuf + QuickCheck + quickcheck-instances + random + raw-strings-qq + retry + safe + saml2-web-sso + schema-profunctor + servant + servant-client + servant-client-core + servant-server + servant-swagger + singletons + singletons-th + sop-core + ssl-util + streaming-commons + string-conversions + tagged + tasty + tasty-cannon + tasty-hunit + temporary + text + time + tinylog + tls + transformers types-common + types-common-aws + types-common-journal + unix unliftio + unordered-containers + uri-bytestring + uuid + uuid-types + vector + wai + wai-extra + wai-utilities + warp + warp-tls + wire-api + wire-api-federation + wire-message-proto-lens + yaml ]; testHaskellDepends = [ base tasty tasty-hunit wire-api ]; license = lib.licenses.agpl3Only; - mainProgram = "stern"; } diff --git a/tools/stern/src/Stern/API.hs b/tools/stern/src/Stern/API.hs index 97be2937f2..d948877871 100644 --- a/tools/stern/src/Stern/API.hs +++ b/tools/stern/src/Stern/API.hs @@ -215,8 +215,8 @@ searchOnBehalf revokeIdentity :: Maybe Email -> Maybe Phone -> Handler NoContent revokeIdentity mbe mbp = NoContent <$ (Intra.revokeIdentity =<< doubleMaybeToEither "email, phone" mbe mbp) -changeEmail :: UserId -> Maybe Bool -> EmailUpdate -> Handler NoContent -changeEmail = undefined -- uid validate upd = NoContent <$ Intra.changeEmail uid (fromMaybe False upd) validate +changeEmail :: UserId -> EmailUpdate -> Handler NoContent +changeEmail uid upd = NoContent <$ Intra.changeEmail uid upd changePhone :: UserId -> PhoneUpdate -> Handler NoContent changePhone uid upd = NoContent <$ Intra.changePhone uid upd diff --git a/tools/stern/src/Stern/API/Routes.hs b/tools/stern/src/Stern/API/Routes.hs index 525df34f9d..e0e6c3a4df 100644 --- a/tools/stern/src/Stern/API/Routes.hs +++ b/tools/stern/src/Stern/API/Routes.hs @@ -117,6 +117,7 @@ type SternAPI = :<|> Named "get-user-connections" ( Summary "Displays user's connections" + :> Description "[Deprecated] This is using API version V1 and will be removed in the future." :> "users" :> Capture "uid" UserId :> "connections" @@ -162,7 +163,6 @@ type SternAPI = :> "users" :> Capture "uid" UserId :> "email" - :> QueryParam' [Optional, Strict, Description "If set to true, a validation email will be sent to the new email address"] "validate" Bool :> Servant.ReqBody '[JSON] EmailUpdate :> Put '[JSON] NoContent ) diff --git a/tools/stern/src/Stern/Intra.hs b/tools/stern/src/Stern/Intra.hs index fe9429062d..bff40cb381 100644 --- a/tools/stern/src/Stern/Intra.hs +++ b/tools/stern/src/Stern/Intra.hs @@ -89,6 +89,7 @@ import Imports import Network.HTTP.Types.Method import Network.HTTP.Types.Status hiding (statusCode) import Network.Wai.Utilities (Error (..), mkError) +import Servant.API (toUrlPiece) import Stern.App import Stern.Types import System.Logger.Class hiding (Error, name, (.=)) @@ -119,11 +120,11 @@ import Wire.API.User.Search backendApiVersion :: Version backendApiVersion = V2 -path :: ByteString -> Request -> Request -path = Bilge.path . ((toPathComponent backendApiVersion <> "/") <>) +versionedPath :: ByteString -> Request -> Request +versionedPath = Bilge.path . ((cs (toUrlPiece backendApiVersion) <> "/") <>) -paths :: [ByteString] -> Request -> Request -paths = Bilge.paths . (toPathComponent backendApiVersion :) +versionedPaths :: [ByteString] -> Request -> Request +versionedPaths = Bilge.paths . (cs (toUrlPiece backendApiVersion) :) ------------------------------------------------------------------------------- @@ -137,7 +138,7 @@ putUser uid upd = do "brig" b ( method PUT - . path "/self" + . versionedPath "/self" . header "Z-User" (toByteString' uid) . header "Z-Connection" (toByteString' "") . lbytes (encode upd) @@ -155,7 +156,7 @@ putUserStatus status uid = do "brig" b ( method PUT - . paths ["/i/users", toByteString' uid, "status"] + . Bilge.paths ["i", "users", toByteString' uid, "status"] . lbytes (encode payload) . contentJson . expect2xx @@ -163,6 +164,7 @@ putUserStatus status uid = do where payload = AccountStatusUpdate status +-- This won't work anymore once API version V1 is not supported anymore getUserConnections :: UserId -> Handler [UserConnection] getUserConnections uid = do info $ msg "Getting user connections" @@ -185,7 +187,7 @@ getUserConnections uid = do b ( method GET . header "Z-User" (toByteString' uid) - . path "/connections" + . Bilge.paths ["v1", "connections"] . queryItem "size" (toByteString' batchSize) . maybe id (queryItem "start" . toByteString') start . expect2xx @@ -204,7 +206,7 @@ getUsersConnections uids = do "brig" b ( method POST - . path "/i/users/connections-status" + . Bilge.path "i/users/connections-status" . Bilge.json reqBody . expect2xx ) @@ -225,7 +227,7 @@ getUserProfiles uidsOrHandles = do "brig" b ( method GET - . path "/i/users" + . Bilge.path "i/users" . qry . expect2xx ) @@ -248,7 +250,7 @@ getUserProfilesByIdentity emailOrPhone = do "brig" b ( method GET - . path "/i/users" + . Bilge.path "i/users" . userKeyToParam emailOrPhone . expect2xx ) @@ -266,7 +268,7 @@ getEjpdInfo handles includeContacts = do "brig" b ( method POST - . path "/i/ejpd-request" + . Bilge.path "i/ejpd-request" . Bilge.json bdy . (if includeContacts then queryItem "include_contacts" "true" else id) . expect2xx @@ -283,7 +285,7 @@ getContacts u q s = do "brig" b ( method GET - . path "/search/contacts" + . versionedPath "search/contacts" . header "Z-User" (toByteString' u) . queryItem "q" (toByteString' q) . queryItem "size" (toByteString' s) @@ -300,7 +302,7 @@ revokeIdentity emailOrPhone = do "brig" b ( method POST - . path "/i/users/revoke-identity" + . Bilge.path "i/users/revoke-identity" . userKeyToParam emailOrPhone . expect2xx ) @@ -314,7 +316,7 @@ deleteAccount uid = do "brig" b ( method DELETE - . paths ["/i/users", toByteString' uid] + . Bilge.paths ["i", "users", toByteString' uid] . expect2xx ) @@ -327,7 +329,7 @@ setStatusBindingTeam tid status = do "galley" g ( method PUT - . paths ["/i/teams", toByteString' tid, "status"] + . Bilge.paths ["i", "teams", toByteString' tid, "status"] . Bilge.json (Team.TeamStatusUpdate status Nothing) . expect2xx ) @@ -341,7 +343,7 @@ deleteBindingTeam tid = do "galley" g ( method DELETE - . paths ["/i/teams", toByteString' tid] + . Bilge.paths ["i", "teams", toByteString' tid] . expect2xx ) @@ -355,13 +357,13 @@ deleteBindingTeamForce tid = do "galley" g ( method DELETE - . paths ["/i/teams", toByteString' tid] + . Bilge.paths ["i", "teams", toByteString' tid] . queryItem "force" "true" . expect2xx ) -changeEmail :: UserId -> EmailUpdate -> Bool -> Handler () -changeEmail u upd validate = do +changeEmail :: UserId -> EmailUpdate -> Handler () +changeEmail u upd = do info $ msg "Updating email address" b <- view brig void . catchRpcErrors $ @@ -369,8 +371,7 @@ changeEmail u upd validate = do "brig" b ( method PUT - . path "i/self/email" - . (if validate then queryItem "validate" "true" else id) + . Bilge.path "i/self/email" . header "Z-User" (toByteString' u) . header "Z-Connection" (toByteString' "") . lbytes (encode upd) @@ -387,7 +388,7 @@ changePhone u upd = do "brig" b ( method PUT - . path "/self/phone" + . versionedPath "/self/phone" . header "Z-User" (toByteString' u) . header "Z-Connection" (toByteString' "") . lbytes (encode upd) @@ -411,7 +412,7 @@ getUserBindingTeam u = do "galley" g ( method GET - . path "teams" + . versionedPath "teams" . header "Z-User" (toByteString' u) . header "Z-Connection" (toByteString' "") . expect2xx @@ -433,7 +434,7 @@ getInvoiceUrl tid iid = do "ibis" i ( method GET - . paths ["i", "team", toByteString' tid, "invoice", toByteString' iid] + . Bilge.paths ["i", "team", toByteString' tid, "invoice", toByteString' iid] . noRedirect . expectStatus (== 307) ) @@ -449,7 +450,7 @@ getTeamBillingInfo tid = do "ibis" i ( method GET - . paths ["i", "team", toByteString' tid, "billing"] + . Bilge.paths ["i", "team", toByteString' tid, "billing"] ) case Bilge.statusCode r of 200 -> Just <$> parseResponse (mkError status502 "bad-upstream") r @@ -465,7 +466,7 @@ setTeamBillingInfo tid tbu = do "ibis" i ( method PUT - . paths ["i", "team", toByteString' tid, "billing"] + . Bilge.paths ["i", "team", toByteString' tid, "billing"] . lbytes (encode tbu) . contentJson . expect2xx @@ -481,7 +482,7 @@ isBlacklisted emailOrPhone = do "brig" b ( method HEAD - . path "i/users/blacklist" + . Bilge.path "i/users/blacklist" . userKeyToParam emailOrPhone ) case Bilge.statusCode r of @@ -498,7 +499,7 @@ setBlacklistStatus status emailOrPhone = do "brig" b ( method (statusToMethod status) - . path "i/users/blacklist" + . Bilge.path "i/users/blacklist" . userKeyToParam emailOrPhone . expect2xx ) @@ -519,7 +520,7 @@ getTeamFeatureFlag tid = do gly <- view galley let req = method GET - . paths ["/i/teams", toByteString' tid, "features", Public.featureNameBS @cfg] + . Bilge.paths ["i", "teams", toByteString' tid, "features", Public.featureNameBS @cfg] resp <- catchRpcErrors $ rpc' "galley" gly req case Bilge.statusCode resp of 200 -> pure $ responseJsonUnsafe @(Public.WithStatus cfg) resp @@ -540,7 +541,7 @@ setTeamFeatureFlag tid status = do gly <- view galley let req = method PUT - . paths ["/i/teams", toByteString' tid, "features", Public.featureNameBS @cfg] + . Bilge.paths ["i", "teams", toByteString' tid, "features", Public.featureNameBS @cfg] . Bilge.json status . contentJson resp <- catchRpcErrors $ rpc' "galley" gly req @@ -567,7 +568,7 @@ getSearchVisibility tid = do "galley" gly ( method GET - . paths ["/i/teams", toByteString' tid, "search-visibility"] + . Bilge.paths ["i", "teams", toByteString' tid, "search-visibility"] . expect2xx ) where @@ -584,7 +585,7 @@ setSearchVisibility tid typ = do "galley" gly ( method PUT - . paths ["/i/teams", toByteString' tid, "search-visibility"] + . Bilge.paths ["i", "teams", toByteString' tid, "search-visibility"] . lbytes (encode $ TeamSearchVisibilityView typ) . contentJson ) @@ -628,7 +629,7 @@ getTeamData tid = do "galley" g ( method GET - . paths ["i", "teams", toByteString' tid] + . Bilge.paths ["i", "teams", toByteString' tid] . expectStatus (`elem` [200, 404]) ) case Bilge.statusCode r of @@ -645,7 +646,7 @@ getTeamMembers tid = do "galley" g ( method GET - . paths ["i", "teams", toByteString' tid, "members"] + . Bilge.paths ["i", "teams", toByteString' tid, "members"] . expect2xx ) parseResponse (mkError status502 "bad-upstream") r @@ -660,7 +661,7 @@ getEmailConsentLog email = do "galeb" g ( method GET - . paths ["/i/consent/logs/emails", toByteString' email] + . Bilge.paths ["i", "consent", "logs", "emails", toByteString' email] . expect2xx ) parseResponse (mkError status502 "bad-upstream") r @@ -679,7 +680,7 @@ getUserConsentValue uid = do g ( method GET . header "Z-User" (toByteString' uid) - . path "/self/consent" + . versionedPath "/self/consent" . expect2xx ) parseResponse (mkError status502 "bad-upstream") r @@ -694,7 +695,7 @@ getMarketoResult email = do "galeb" g ( method GET - . paths ["/i/marketo/emails", toByteString' email] + . Bilge.paths ["i", "marketo", "emails", toByteString' email] . expectStatus (`elem` [200, 404]) ) -- 404 is acceptable when marketo doesn't know about this user, return an empty result @@ -715,7 +716,7 @@ getUserConsentLog uid = do "galeb" g ( method GET - . paths ["/i/consent/logs/users", toByteString' uid] + . Bilge.paths ["i", "consent", "logs", "users", toByteString' uid] . expect2xx ) parseResponse (mkError status502 "bad-upstream") r @@ -731,7 +732,7 @@ getUserCookies uid = do g ( method GET . header "Z-User" (toByteString' uid) - . path "/cookies" + . versionedPath "/cookies" . expect2xx ) parseResponse (mkError status502 "bad-upstream") r @@ -757,7 +758,7 @@ getUserConversations uid = do b ( method GET . header "Z-User" (toByteString' uid) - . path "conversations" + . versionedPath "conversations" . queryItem "size" (toByteString' batchSize) . maybe id (queryItem "start" . toByteString') start . expect2xx @@ -776,7 +777,7 @@ getUserClients uid = do b ( method GET . header "Z-User" (toByteString' uid) - . path "/clients" + . versionedPath "/clients" . expect2xx ) info $ msg ("Response" ++ show r) @@ -793,7 +794,7 @@ getUserProperties uid = do b ( method GET . header "Z-User" (toByteString' uid) - . path "/properties" + . versionedPath "/properties" . expect2xx ) info $ msg ("Response" ++ show r) @@ -809,7 +810,7 @@ getUserProperties uid = do b ( method GET . header "Z-User" (toByteString' uid) - . paths ["/properties", toByteString' x] + . versionedPaths ["/properties", toByteString' x] . expect2xx ) info $ msg ("Response" ++ show r) @@ -837,7 +838,7 @@ getUserNotifications uid = do b ( method GET . header "Z-User" (toByteString' uid) - . path "/notifications" + . versionedPath "/notifications" . queryItem "size" (toByteString' batchSize) . maybe id (queryItem "since" . toByteString') start . expectStatus (`elem` [200, 404]) diff --git a/tools/stern/stern.cabal b/tools/stern/stern.cabal index ba6bb8772c..c12a3f0d63 100644 --- a/tools/stern/stern.cabal +++ b/tools/stern/stern.cabal @@ -39,6 +39,7 @@ library DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -50,6 +51,7 @@ library MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -134,6 +136,7 @@ executable stern DeriveTraversable DerivingStrategies DerivingVia + DuplicateRecordFields EmptyCase FlexibleContexts FlexibleInstances @@ -145,6 +148,7 @@ executable stern MultiParamTypeClasses MultiWayIf NamedFieldPuns + OverloadedRecordDot OverloadedStrings PackageImports PatternSynonyms @@ -192,3 +196,171 @@ test-suite stern-tests , tasty , tasty-hunit , wire-api + +executable stern-integration + main-is: Main.hs + + -- cabal-fmt: expand test/integration + other-modules: + API + Main + TestSetup + Util + + hs-source-dirs: test/integration + default-extensions: + NoImplicitPrelude + AllowAmbiguousTypes + BangPatterns + ConstraintKinds + DataKinds + DefaultSignatures + DeriveFunctor + DeriveGeneric + DeriveLift + DeriveTraversable + DerivingStrategies + DerivingVia + DuplicateRecordFields + EmptyCase + FlexibleContexts + FlexibleInstances + FunctionalDependencies + GADTs + InstanceSigs + KindSignatures + LambdaCase + MultiParamTypeClasses + MultiWayIf + NamedFieldPuns + OverloadedRecordDot + OverloadedStrings + PackageImports + PatternSynonyms + PolyKinds + QuasiQuotes + RankNTypes + ScopedTypeVariables + StandaloneDeriving + TupleSections + TypeApplications + TypeFamilies + TypeFamilyDependencies + TypeOperators + UndecidableInstances + ViewPatterns + + ghc-options: + -O2 -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates + -Wpartial-fields -fwarn-tabs -optP-Wno-nonportable-include-path + -threaded -with-rtsopts=-N -Wredundant-constraints + + build-depends: + aeson + , aeson-qq + , amazonka + , amazonka-sqs + , async + , base + , base64-bytestring + , bilge + , binary + , brig-types + , bytestring + , bytestring-conversion + , call-stack + , case-insensitive + , cassandra-util + , cassava + , cereal + , comonad + , containers + , cookie + , cryptonite + , currency-codes + , data-default + , data-timeout + , directory + , errors + , exceptions + , extended + , extra >=1.3 + , federator + , filepath + , galley + , galley-types + , gundeck-types + , hex + , HsOpenSSL + , HsOpenSSL-x509-system + , hspec + , http-client + , http-client-openssl + , http-client-tls + , http-media + , http-types + , imports + , kan-extensions + , lens + , lens-aeson + , memory + , metrics-wai + , mtl + , network + , network-uri + , optparse-applicative + , pem + , process + , proto-lens + , protobuf + , QuickCheck + , quickcheck-instances + , random + , raw-strings-qq >=1.0 + , retry + , safe >=0.3 + , saml2-web-sso >=0.19 + , schema-profunctor + , servant + , servant-client + , servant-client-core + , servant-server + , servant-swagger + , singletons + , singletons-th + , sop-core + , ssl-util + , stern + , streaming-commons + , string-conversions + , tagged + , tasty >=0.8 + , tasty-cannon >=0.3.2 + , tasty-hunit >=0.9 + , temporary + , text + , time + , tinylog + , tls >=1.3.8 + , transformers + , types-common + , types-common-aws + , types-common-journal + , unix + , unliftio + , unordered-containers + , uri-bytestring + , uuid + , uuid-types + , vector + , wai + , wai-extra + , wai-utilities + , warp + , warp-tls >=3.2 + , wire-api + , wire-api-federation + , wire-message-proto-lens + , yaml + + default-language: Haskell2010 diff --git a/tools/stern/stern.example.yaml b/tools/stern/stern.example.yaml deleted file mode 100644 index f9f926e6a6..0000000000 --- a/tools/stern/stern.example.yaml +++ /dev/null @@ -1,28 +0,0 @@ -stern: - host: 127.0.0.1 - port: 8091 - -brig: - host: 127.0.0.1 - port: 8082 - -galley: - host: 127.0.0.1 - port: 8085 - -gundeck: - host: 127.0.0.1 - port: 8086 - -# Both ibis and galeb should be made optional for -# installations where these services are not available -galeb: - host: 127.0.0.1 - port: 8089 - -ibis: - host: 127.0.0.1 - port: 8090 - -logLevel: Info -logNetStrings: false diff --git a/tools/stern/test/integration/API.hs b/tools/stern/test/integration/API.hs new file mode 100644 index 0000000000..1390c208e9 --- /dev/null +++ b/tools/stern/test/integration/API.hs @@ -0,0 +1,540 @@ +{-# LANGUAGE OverloadedRecordDot #-} +{-# OPTIONS_GHC -Wno-redundant-constraints #-} +{-# OPTIONS_GHC -fno-warn-incomplete-patterns #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} + +-- 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 API where -- todo(leif): export only test + +import Bilge +import Brig.Types.Intra +import Control.Applicative +import Control.Lens hiding ((.=)) +import Data.ByteString.Conversion +import Data.Handle +import Data.Id +import Data.Schema +import qualified Data.Set as Set +import Data.String.Conversions +import GHC.TypeLits +import Imports +import Stern.API.Routes (UserConnectionGroups (..)) +import Stern.Types +import Test.Tasty +import Test.Tasty.HUnit +import TestSetup +import Util +import Wire.API.Routes.Internal.Brig.Connection +import qualified Wire.API.Routes.Internal.Brig.EJPD as EJPD +import Wire.API.Team.Feature +import qualified Wire.API.Team.Feature as Public +import Wire.API.Team.SearchVisibility +import Wire.API.User +import Wire.API.User.Search + +tests :: IO TestSetup -> TestTree +tests s = + testGroup + "API tests" + [ test s "GET /i/status" testGetStatus, + test s "POST /users/:uid/suspend" testSuspendUser, + test s "POST /users/:uid/unsuspend" testUnsuspendUser, + test s "GET /users/by-email" testGetUsersByEmail, + test s "GET /users/by-phone" testGetUsersByPhone, + test s "GET /users/by-ids" testGetUsersByIds, + test s "GET /users/by-handles" testGetUsersByHandles, + test s "GET /users/:id/connections" testGetConnections, + test s "GET /users/connections?ids=..." testGetConnectionsByIds, + test s "GET /users/:uid/search" testSearchUsers, + test s "POST /users/revoke-identity?email=..." testRevokeIdentity, + test s "PUT /users/:uid/email" testPutEmail, + test s "PUT /users/:uid/phone" testPutPhone, + test s "DELETE /users/:uid" testDeleteUser, + test s "PUT /teams/:tid/suspend" testSuspendTeam, + test s "PUT /teams/:tid/unsuspend" testUnsuspendTeam, + test s "DELETE /teams/:tid" testDeleteTeam, + test s "GET /ejpd-info" testEjpdInfo, + test s "HEAD /users/blacklist" testUserBlacklistHead, + test s "POST /users/blacklist" testPostUserBlacklist, + test s "DELETE /users/blacklist" testDeleteUserBlacklist, + test s "GET /teams" testGetTeamInfoByMemberEmail, + test s "GET /teams/:tid/admins" testGetTeamAdminInfo, + test s "GET /teams/:tid/features/legalhold" testGetLegalholdConfig, + test s "PUT /teams/:tid/features/legalhold" testPutLegalholdConfig, + test s "GET /teams/:tid/features/sso" testGetSSOConfig, + test s "PUT /teams/:tid/features/sso" testPutSSOConfig, + test s "PUT /teams/:tid/features/search-visibility-available" testPutSearchVisibilityAvailableConfig, + test s "PUT /teams/:tid/features/validate-saml-emails" testPutValidateSAMLEmailsConfig, + test s "PUT /teams/:tid/features/digital-signatures" testPutDigitalSignaturesConfig, + test s "PUT /teams/:tid/features/file-sharing" testPutFileSharingConfig, + test s "PUT /teams/:tid/features/conference-calling" testPutConferenceCallingConfig, + test s "PUT /teams/:tid/features/:feature" testPutFeatureConfig, + test s "GET /teams/:tid/search-visibility" testGetSearchVisibility, + test s "PUT /teams/:tid/search-visibility" testPutSearchVisibility, + test s "GET /teams/:tid/invoice/:inr" testGetTeamInvoice, + test s "GET /teams/:tid/billing" testGetTeamBillingInfo, + test s "PUT /teams/:tid/billing" testPutTeamBillingInfo, + test s "POST /teams/:tid/billing" testPostTeamBillingInfo, + test s "GET /i/consent" testGetConsentLog, + test s "GET /teams/:id" testGetTeamInfo + ] + +testPutPhone :: TestM () +testPutPhone = pure () + +testDeleteUser :: TestM () +testDeleteUser = pure () + +testSuspendTeam :: TestM () +testSuspendTeam = pure () + +testUnsuspendTeam :: TestM () +testUnsuspendTeam = pure () + +testDeleteTeam :: TestM () +testDeleteTeam = pure () + +testEjpdInfo :: TestM () +testEjpdInfo = pure () + +testUserBlacklistHead :: TestM () +testUserBlacklistHead = pure () + +testPostUserBlacklist :: TestM () +testPostUserBlacklist = pure () + +testDeleteUserBlacklist :: TestM () +testDeleteUserBlacklist = pure () + +testGetTeamInfoByMemberEmail :: TestM () +testGetTeamInfoByMemberEmail = pure () + +testGetTeamAdminInfo :: TestM () +testGetTeamAdminInfo = pure () + +testGetLegalholdConfig :: TestM () +testGetLegalholdConfig = pure () + +testPutLegalholdConfig :: TestM () +testPutLegalholdConfig = pure () + +testGetSSOConfig :: TestM () +testGetSSOConfig = pure () + +testPutSSOConfig :: TestM () +testPutSSOConfig = pure () + +testPutSearchVisibilityAvailableConfig :: TestM () +testPutSearchVisibilityAvailableConfig = pure () + +testPutValidateSAMLEmailsConfig :: TestM () +testPutValidateSAMLEmailsConfig = pure () + +testPutDigitalSignaturesConfig :: TestM () +testPutDigitalSignaturesConfig = pure () + +testPutFileSharingConfig :: TestM () +testPutFileSharingConfig = pure () + +testPutConferenceCallingConfig :: TestM () +testPutConferenceCallingConfig = pure () + +testPutFeatureConfig :: TestM () +testPutFeatureConfig = pure () + +testGetSearchVisibility :: TestM () +testGetSearchVisibility = pure () + +testPutSearchVisibility :: TestM () +testPutSearchVisibility = pure () + +testGetTeamInvoice :: TestM () +testGetTeamInvoice = pure () + +testGetTeamBillingInfo :: TestM () +testGetTeamBillingInfo = pure () + +testPutTeamBillingInfo :: TestM () +testPutTeamBillingInfo = pure () + +testPostTeamBillingInfo :: TestM () +testPostTeamBillingInfo = pure () + +testGetConsentLog :: TestM () +testGetConsentLog = pure () + +testGetConnectionsByIds :: TestM () +testGetConnectionsByIds = do + uids <- sequence [randomUser, randomUser, randomUser] + connections <- getConnectionsByUserIds uids + liftIO $ connections @?= [] + +testGetConnections :: TestM () +testGetConnections = do + uid <- randomUser + connections <- getConnections uid + liftIO $ connections @?= UserConnectionGroups 0 0 0 0 0 0 0 + +testGetUsersByHandles :: TestM () +testGetUsersByHandles = do + uid <- randomUser + h <- randomHandle + void $ setHandle uid h + [ua] <- getUsersByHandles h + liftIO $ ua.accountUser.userId @?= uid + +testGetUsersByPhone :: TestM () +testGetUsersByPhone = do + (uid, phone) <- randomPhoneUser + [ua] <- getUsersByPhone phone + liftIO $ ua.accountUser.userId @?= uid + +testGetUsersByEmail :: TestM () +testGetUsersByEmail = do + (uid, email) <- randomEmailUser + [ua] <- getUsersByEmail email + liftIO $ ua.accountUser.userId @?= uid + +testUnsuspendUser :: TestM () +testUnsuspendUser = do + uid <- randomUser + void $ postSupendUser uid + do + [ua] <- getUsersByIds [uid] + liftIO $ ua.accountStatus @?= Suspended + void $ postUnsuspendUser uid + do + [ua] <- getUsersByIds [uid] + liftIO $ ua.accountStatus @?= Active + +testSuspendUser :: TestM () +testSuspendUser = do + uid <- randomUser + void $ postSupendUser uid + [ua] <- getUsersByIds [uid] + liftIO $ ua.accountStatus @?= Suspended + +testGetStatus :: TestM () +testGetStatus = do + r <- getStatus + liftIO $ do + statusCode r @?= 200 + +testGetUsersByIds :: TestM () +testGetUsersByIds = do + uid1 <- randomUser + uid2 <- randomUser + uas <- getUsersByIds [uid1, uid2] + liftIO $ do + length uas @?= 2 + Set.fromList ((.accountUser.userId) <$> uas) @?= Set.fromList [uid1, uid2] + +testGetTeamInfo :: TestM () +testGetTeamInfo = do + (_, tid, _) <- createBindingTeamWithNMembers 10 + info <- getTeamInfo tid + liftIO $ length info.tiMembers @?= 11 + +testSearchUsers :: TestM () +testSearchUsers = do + uid <- randomUser + result <- searchUsers uid + liftIO $ do + result.searchFound @?= 0 + +testRevokeIdentity :: TestM () +testRevokeIdentity = do + (_, (email, phone)) <- randomEmailPhoneUser + do + [ua] <- getUsersByEmail email + liftIO $ do + ua.accountStatus @?= Active + isJust ua.accountUser.userIdentity @?= True + void $ revokeIdentity (Left email) + void $ revokeIdentity (Right phone) + do + [ua] <- getUsersByEmail email + liftIO $ do + ua.accountStatus @?= Active + isJust ua.accountUser.userIdentity @?= False + +testPutEmail :: TestM () +testPutEmail = do + uid <- randomUser + email <- randomEmail + -- If the user has a pending email validation, the validation email will be resent. But we simply test that this call returns 200 + putEmail uid (EmailUpdate email) + +------------------------------------------------------------------------------- +-- API Calls + +instance (ToByteString a) => ToByteString [a] where + builder xs = builder $ cs @String @ByteString $ intercalate "," (cs . toByteString' <$> xs) + +getConnectionsByUserIds :: [UserId] -> TestM [ConnectionStatus] +getConnectionsByUserIds uids = do + s <- view tsStern + r <- get (s . paths ["users", "connections"] . queryItem "ids" (toByteString' uids) . expect2xx) + pure $ responseJsonUnsafe r + +getConnections :: UserId -> TestM UserConnectionGroups +getConnections uid = do + s <- view tsStern + r <- get (s . paths ["users", toByteString' uid, "connections"] . expect2xx) + pure $ responseJsonUnsafe r + +getUsersByHandles :: Text -> TestM [UserAccount] +getUsersByHandles h = do + stern <- view tsStern + r <- get (stern . paths ["users", "by-handles"] . queryItem "handles" (cs h) . expect2xx) + pure $ responseJsonUnsafe r + +getUsersByPhone :: Phone -> TestM [UserAccount] +getUsersByPhone phone = do + stern <- view tsStern + r <- get (stern . paths ["users", "by-phone"] . queryItem "phone" (toByteString' phone) . expect2xx) + pure $ responseJsonUnsafe r + +getUsersByEmail :: Email -> TestM [UserAccount] +getUsersByEmail email = do + stern <- view tsStern + r <- get (stern . paths ["users", "by-email"] . queryItem "email" (toByteString' email) . expect2xx) + pure $ responseJsonUnsafe r + +postUnsuspendUser :: UserId -> TestM ResponseLBS +postUnsuspendUser uid = do + stern <- view tsStern + post (stern . paths ["users", toByteString' uid, "unsuspend"] . expect2xx) + +postSupendUser :: UserId -> TestM ResponseLBS +postSupendUser uid = do + stern <- view tsStern + post (stern . paths ["users", toByteString' uid, "suspend"] . expect2xx) + +getStatus :: TestM ResponseLBS +getStatus = do + stern <- view tsStern + get (stern . paths ["i", "status"] . expect2xx) + +getUsersByIds :: [UserId] -> TestM [UserAccount] +getUsersByIds uids = do + stern <- view tsStern + r <- get (stern . paths ["users", "by-ids"] . queryItem "ids" (toByteString' uids) . expect2xx) + pure $ responseJsonUnsafe r + +getTeamInfo :: TeamId -> TestM TeamInfo +getTeamInfo tid = do + stern <- view tsStern + r <- get (stern . paths ["teams", toByteString' tid] . expect2xx) + pure $ responseJsonUnsafe r + +searchUsers :: UserId -> TestM (SearchResult Contact) +searchUsers uid = do + s <- view tsStern + r <- get (s . paths ["users", toByteString' uid, "search"] . expect2xx) + pure $ responseJsonUnsafe r + +revokeIdentity :: Either Email Phone -> TestM () +revokeIdentity emailOrPhone = do + s <- view tsStern + void $ post (s . paths ["users", "revoke-identity"] . mkQueryParam emailOrPhone . expect2xx) + +mkQueryParam :: Either Email Phone -> Request -> Request +mkQueryParam = \case + Left email -> queryItem "email" (toByteString' email) + Right phone -> queryItem "phone" (toByteString' phone) + +putEmail :: UserId -> EmailUpdate -> TestM () +putEmail uid emailUpdate = do + s <- view tsStern + void $ put (s . paths ["users", toByteString' uid, "email"] . json emailUpdate . expect2xx) + +putPhone :: UserId -> PhoneUpdate -> TestM () +putPhone uid phoneUpdate = do + s <- view tsStern + void $ put (s . paths ["users", toByteString' uid, "phone"] . json phoneUpdate . expect2xx) + +deleteUser :: UserId -> Either Email Phone -> TestM () +deleteUser uid emailOrPhone = do + s <- view tsStern + void $ delete (s . paths ["users", toByteString' uid] . mkQueryParam emailOrPhone . expect2xx) + +suspendTeam :: TeamId -> TestM () +suspendTeam tid = do + s <- view tsStern + void $ put (s . paths ["teams", toByteString' tid, "suspend"] . expect2xx) + +unsuspendTeam :: TeamId -> TestM () +unsuspendTeam tid = do + s <- view tsStern + void $ put (s . paths ["teams", toByteString' tid, "unsuspend"] . expect2xx) + +deleteTeam :: TeamId -> Bool -> Email -> TestM () +deleteTeam tid force email = do + s <- view tsStern + void $ delete (s . paths ["teams", toByteString' tid] . queryItem "force" (toByteString' force) . queryItem "email" (toByteString' email) . expect2xx) + +ejpdInfo :: Bool -> [Handle] -> TestM EJPD.EJPDResponseBody +ejpdInfo includeContacts handles = do + s <- view tsStern + r <- get (s . paths ["ejpd-info"] . queryItem "include_contacts" (toByteString' includeContacts) . queryItem "handles" (toByteString' handles) . expect2xx) + pure $ responseJsonUnsafe r + +userBlacklistHead :: Either Email Phone -> TestM () +userBlacklistHead emailOrPhone = do + s <- view tsStern + void $ Bilge.head (s . paths ["users", "blacklist"] . mkQueryParam emailOrPhone . expect2xx) + +postUserBlacklist :: Either Email Phone -> TestM () +postUserBlacklist emailOrPhone = do + s <- view tsStern + void $ post (s . paths ["users", "blacklist"] . mkQueryParam emailOrPhone . expect2xx) + +deleteUserBlacklist :: Either Email Phone -> TestM () +deleteUserBlacklist emailOrPhone = do + s <- view tsStern + void $ delete (s . paths ["users", "blacklist"] . mkQueryParam emailOrPhone . expect2xx) + +getTeamInfoByMemberEmail :: Email -> TestM TeamInfo +getTeamInfoByMemberEmail email = do + s <- view tsStern + r <- get (s . paths ["teams"] . queryItem "email" (toByteString' email) . expect2xx) + pure $ responseJsonUnsafe r + +getTeamAdminInfo :: TeamId -> TestM TeamAdminInfo +getTeamAdminInfo tid = do + s <- view tsStern + r <- get (s . paths ["teams", toByteString' tid, "admins"] . expect2xx) + pure $ responseJsonUnsafe r + +getFeatureConfig :: + forall cfg. + ( KnownSymbol (FeatureSymbol cfg), + ToSchema cfg, + Typeable cfg, + IsFeatureConfig cfg + ) => + TeamId -> + TestM (WithStatus cfg) +getFeatureConfig tid = do + s <- view tsStern + r <- get (s . paths ["teams", toByteString' tid, "features", Public.featureNameBS @cfg] . expect2xx) + pure $ responseJsonUnsafe r + +putLegalholdConfig :: TeamId -> FeatureStatus -> TestM () +putLegalholdConfig tid status = do + s <- view tsStern + void $ put (s . paths ["teams", toByteString' tid, "features", "legalhold"] . queryItem "status" (toByteString' status) . expect2xx) + +getSSOConfig :: TeamId -> TestM (WithStatus SSOConfig) +getSSOConfig tid = do + s <- view tsStern + r <- get (s . paths ["teams", toByteString' tid, "features", "sso"] . expect2xx) + pure $ responseJsonUnsafe r + +putSSOConfig :: TeamId -> FeatureStatus -> TestM () +putSSOConfig tid cfg = do + s <- view tsStern + void $ put (s . paths ["teams", toByteString' tid, "features", "sso"] . queryItem "status" (toByteString' cfg) . expect2xx) + +putSearchVisibilityAvailableConfig :: TeamId -> FeatureStatus -> TestM () +putSearchVisibilityAvailableConfig tid cfg = do + s <- view tsStern + void $ put (s . paths ["teams", toByteString' tid, "features", "search-visibility-available"] . queryItem "status" (toByteString' cfg) . expect2xx) + +putValidateSAMLEmailsConfig :: TeamId -> FeatureStatus -> TestM () +putValidateSAMLEmailsConfig tid cfg = do + s <- view tsStern + void $ put (s . paths ["teams", toByteString' tid, "features", "validate-saml-emails"] . queryItem "status" (toByteString' cfg) . expect2xx) + +putDigitalSignaturesConfig :: TeamId -> FeatureStatus -> TestM () +putDigitalSignaturesConfig tid cfg = do + s <- view tsStern + void $ put (s . paths ["teams", toByteString' tid, "features", "digital-signatures"] . queryItem "status" (toByteString' cfg) . expect2xx) + +putFileSharingConfig :: TeamId -> FeatureStatus -> TestM () +putFileSharingConfig tid cfg = do + s <- view tsStern + void $ put (s . paths ["teams", toByteString' tid, "features", "file-sharing"] . queryItem "status" (toByteString' cfg) . expect2xx) + +putConferenceCallingConfig :: TeamId -> FeatureStatus -> FeatureTTLDays -> TestM () +putConferenceCallingConfig tid cfg ttl = do + s <- view tsStern + void $ put (s . paths ["teams", toByteString' tid, "features", "conference-calling"] . queryItem "status" (toByteString' cfg) . queryItem "ttl" (toByteString' ttl) . expect2xx) + +putFeatureConfig :: + forall cfg. + ( KnownSymbol (FeatureSymbol cfg), + ToSchema cfg, + Typeable cfg, + IsFeatureConfig cfg + ) => + TeamId -> + WithStatusNoLock cfg -> + TestM () +putFeatureConfig tid cfg = do + s <- view tsStern + void $ put (s . paths ["teams", toByteString' tid, "features", Public.featureNameBS @cfg] . json cfg . expect2xx) + +getSearchVisibility :: TeamId -> TestM TeamSearchVisibilityView +getSearchVisibility tid = do + s <- view tsStern + r <- get (s . paths ["teams", toByteString' tid, "search-visibility"] . expect2xx) + pure $ responseJsonUnsafe r + +putSearchVisibility :: TeamId -> TeamSearchVisibility -> TestM () +putSearchVisibility tid vis = do + s <- view tsStern + void $ put (s . paths ["teams", toByteString' tid, "search-visibility"] . json vis . expect2xx) + +getTeamInvoice :: TeamId -> InvoiceId -> TestM Text +getTeamInvoice tid inr = do + s <- view tsStern + r <- get (s . paths ["teams", toByteString' tid, "invoice", toByteString' inr] . expect2xx) + pure $ responseJsonUnsafe r + +getTeamBillingInfo :: TeamId -> TestM TeamBillingInfo +getTeamBillingInfo tid = do + s <- view tsStern + r <- get (s . paths ["teams", toByteString' tid, "billing"] . expect2xx) + pure $ responseJsonUnsafe r + +putTeamBillingInfo :: TeamId -> TeamBillingInfoUpdate -> TestM TeamBillingInfo +putTeamBillingInfo tid upd = do + s <- view tsStern + r <- put (s . paths ["teams", toByteString' tid, "billing"] . json upd . expect2xx) + pure $ responseJsonUnsafe r + +postTeamBillingInfo :: TeamId -> TeamBillingInfo -> TestM TeamBillingInfo +postTeamBillingInfo tid upd = do + s <- view tsStern + r <- post (s . paths ["teams", toByteString' tid, "billing"] . json upd . expect2xx) + pure $ responseJsonUnsafe r + +getConsentLog :: Email -> TestM ConsentLogAndMarketo +getConsentLog email = do + s <- view tsStern + r <- get (s . paths ["i", "consent"] . queryItem "email" (toByteString' email) . expect2xx) + pure $ responseJsonUnsafe r + +getUserMetaInfo :: UserId -> TestM UserMetaInfo +getUserMetaInfo uid = do + s <- view tsStern + r <- post (s . paths ["i", "user", "meta-info"] . queryItem "id" (toByteString' uid) . expect2xx) + pure $ responseJsonUnsafe r diff --git a/tools/stern/test/integration/Main.hs b/tools/stern/test/integration/Main.hs new file mode 100644 index 0000000000..ebd27e8040 --- /dev/null +++ b/tools/stern/test/integration/Main.hs @@ -0,0 +1,101 @@ +-- 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 + ( main, + ) +where + +import qualified API +import Bilge hiding (body, header) +import Data.Aeson +import Data.Proxy +import Data.Tagged +import Data.Text.Encoding (encodeUtf8) +import Data.Yaml (decodeFileEither) +import Imports hiding (local) +import Network.HTTP.Client (responseTimeoutMicro) +import Network.HTTP.Client.TLS +import OpenSSL (withOpenSSL) +import Options.Applicative +import qualified System.Logger as Logger +import Test.Tasty +import Test.Tasty.Options +import TestSetup +import Util.Options +import Util.Test + +data IntegrationConfig = IntegrationConfig + { stern :: Endpoint, + brig :: Endpoint, + galley :: Endpoint + } + deriving (Show, Generic) + +instance FromJSON IntegrationConfig + +newtype ServiceConfigFile = ServiceConfigFile String + deriving (Eq, Ord, Typeable) + +instance IsOption ServiceConfigFile where + defaultValue = ServiceConfigFile "/etc/wire/integration/integration.yaml" + parseValue = fmap ServiceConfigFile . safeRead + optionName = pure "service-config" + optionHelp = pure "Service config file to read from" + optionCLParser = + ServiceConfigFile + <$> strOption + ( short (untag (pure 's' :: Tagged ServiceConfigFile Char)) + <> long (untag (optionName :: Tagged ServiceConfigFile String)) + <> help (untag (optionHelp :: Tagged ServiceConfigFile String)) + ) + +runTests :: (String -> TestTree) -> IO () +runTests run = defaultMainWithIngredients ings $ + askOption $ + \(ServiceConfigFile c) -> run c + where + ings = + includingOptions + [ Option (Proxy :: Proxy ServiceConfigFile), + Option (Proxy :: Proxy IntegrationConfigFile) + ] + : defaultIngredients + +main :: IO () +main = withOpenSSL $ runTests go + where + go i = withResource (getOpts i) releaseOpts $ \opts -> + testGroup + "Stern" + [ API.tests opts + ] + getOpts :: FilePath -> IO TestSetup + getOpts iFile = do + m <- + newManager + tlsManagerSettings + { managerResponseTimeout = responseTimeoutMicro 300000000 + } + iConf <- handleParseError =<< decodeFileEither iFile + let s = mkRequest $ stern iConf + let b = mkRequest $ brig iConf + let g = mkRequest $ galley iConf + lg <- Logger.new Logger.defSettings + pure $ TestSetup m s b g lg + releaseOpts _ = pure () + mkRequest (Endpoint h p) = host (encodeUtf8 h) . port p diff --git a/tools/stern/test/integration/TestSetup.hs b/tools/stern/test/integration/TestSetup.hs new file mode 100644 index 0000000000..1e099da4bf --- /dev/null +++ b/tools/stern/test/integration/TestSetup.hs @@ -0,0 +1,84 @@ +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE TemplateHaskell #-} +{-# OPTIONS_GHC -fprint-potential-instances #-} + +-- 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 TestSetup + ( test, + tsManager, + tsLogger, + tsStern, + tsBrig, + tsGalley, + TestM (..), + TestSetup (..), + Stern, + Galley, + Brig, + ) +where + +import Bilge (HttpT (..), Manager, MonadHttp, Request, runHttpT) +import Control.Lens (makeLenses, (^.)) +import Control.Monad.Catch (MonadCatch, MonadMask, MonadThrow) +import Imports +import qualified System.Logger as Log +import Test.Tasty (TestName, TestTree) +import Test.Tasty.HUnit (Assertion, testCase) + +newtype TestM a = TestM + { runTestM :: ReaderT TestSetup (HttpT IO) a + } + deriving + ( Functor, + Applicative, + Monad, + MonadReader TestSetup, + MonadIO, + MonadCatch, + MonadThrow, + MonadMask, + MonadHttp, + MonadUnliftIO, + MonadFail + ) + +type Stern = Request -> Request + +type Brig = Request -> Request + +type Galley = Request -> Request + +data TestSetup = TestSetup + { _tsManager :: Manager, + _tsStern :: Stern, + _tsBrig :: Brig, + _tsGalley :: Galley, + _tsLogger :: Log.Logger + } + +makeLenses ''TestSetup + +test :: IO TestSetup -> TestName -> TestM a -> TestTree +test mkSetup testName testAction = testCase testName runTest + where + runTest :: Assertion + runTest = do + setup <- mkSetup + void . runHttpT (setup ^. tsManager) . flip runReaderT setup . runTestM $ testAction diff --git a/tools/stern/test/integration/Util.hs b/tools/stern/test/integration/Util.hs new file mode 100644 index 0000000000..102908030e --- /dev/null +++ b/tools/stern/test/integration/Util.hs @@ -0,0 +1,242 @@ +{-# LANGUAGE OverloadedRecordDot #-} +{-# OPTIONS_GHC -Wno-redundant-constraints #-} +{-# OPTIONS_GHC -fno-warn-incomplete-patterns #-} + +-- 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 Util where + +import Bilge +import Bilge.Assert +import Control.Applicative +import Control.Lens hiding ((.=)) +import Control.Monad.Catch +import Control.Retry +import Data.Aeson +import Data.Aeson.Lens +import Data.ByteString.Conversion +import Data.Id +import Data.Misc +import Data.Qualified +import Data.Range +import Data.String.Conversions +import qualified Data.Text as Text +import qualified Data.Text.Encoding as Text +import Data.Tuple.Extra +import qualified Data.UUID as UUID +import Data.UUID.V4 +import Imports +import System.Random +import Test.Tasty.HUnit +import TestSetup +import Web.Cookie +import Wire.API.Team +import Wire.API.Team.Invitation +import Wire.API.Team.Member +import qualified Wire.API.Team.Member as Team +import Wire.API.Team.Role +import Wire.API.User + +createBindingTeamWithNMembers :: HasCallStack => Int -> TestM (UserId, TeamId, [UserId]) +createBindingTeamWithNMembers n = do + (owner, tid) <- createBindingTeam + mems <- replicateM n $ do + mem <- addUserToTeam owner tid + pure (mem ^. Team.userId) + pure (owner, tid, mems) + +createBindingTeam :: HasCallStack => TestM (UserId, TeamId) +createBindingTeam = do + first (.userId) <$> createBindingTeam' + +createBindingTeam' :: HasCallStack => TestM (User, TeamId) +createBindingTeam' = do + owner <- randomTeamCreator' + refreshIndex + pure (owner, fromMaybe (error "createBindingTeam: no team id") (owner.userTeam)) + +randomTeamCreator' :: HasCallStack => TestM User +randomTeamCreator' = randomUser'' True True True + +randomUser :: HasCallStack => TestM UserId +randomUser = qUnqualified <$> randomUser' False True True + +randomUser' :: HasCallStack => Bool -> Bool -> Bool -> TestM (Qualified UserId) +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 = randomUserProfile'' isCreator hasPassword hasEmail <&> fst + +randomUserProfile'' :: HasCallStack => Bool -> Bool -> Bool -> TestM (SelfProfile, (Email, Phone)) +randomUserProfile'' isCreator hasPassword hasEmail = do + b <- view tsBrig + e <- liftIO randomEmail + p <- liftIO randomPhone + let pl = + object $ + ["name" .= fromEmail e] + <> ["password" .= defPassword | hasPassword] + <> ["email" .= fromEmail e | hasEmail] + <> ["phone" .= fromPhone p] + <> ["team" .= BindingNewTeam (newNewTeam (unsafeRange "teamName") DefaultIcon) | isCreator] + (,(e, p)) . responseJsonUnsafe <$> (post (b . path "/i/users" . Bilge.json pl) m Phone +randomPhone = liftIO $ do + nrs <- map show <$> replicateM 14 (randomRIO (0, 9) :: IO Int) + let phone = parsePhone . Text.pack $ "+0" ++ concat nrs + pure $ fromMaybe (error "Invalid random phone#") phone + +randomEmailUser :: HasCallStack => TestM (UserId, Email) +randomEmailUser = randomUserProfile'' False False True <&> first ((.userId) . selfUser) <&> second fst + +randomPhoneUser :: HasCallStack => TestM (UserId, Phone) +randomPhoneUser = randomUserProfile'' False False True <&> first ((.userId) . selfUser) <&> second snd + +randomEmailPhoneUser :: HasCallStack => TestM (UserId, (Email, Phone)) +randomEmailPhoneUser = randomUserProfile'' False False True <&> first ((.userId) . selfUser) + +defPassword :: PlainTextPassword8 +defPassword = plainTextPassword8Unsafe "topsecretdefaultpassword" + +randomEmail :: MonadIO m => m Email +randomEmail = do + uid <- liftIO nextRandom + pure $ Email ("success+" <> UUID.toText uid) "simulator.amazonses.com" + +setHandle :: UserId -> Text -> TestM () +setHandle uid h = do + b <- view tsBrig + put + ( b + . paths ["/i/users", toByteString' uid, "handle"] + . Bilge.json (HandleUpdate h) + ) + !!! do + const 200 === statusCode + +randomHandle :: MonadIO m => m Text +randomHandle = liftIO $ do + nrs <- replicateM 21 (randomRIO (97, 122)) -- a-z + pure (Text.pack (map chr nrs)) + +refreshIndex :: TestM () +refreshIndex = do + brig <- view tsBrig + post (brig . path "/i/index/refresh") !!! const 200 === statusCode + +addUserToTeam :: HasCallStack => UserId -> TeamId -> TestM TeamMember +addUserToTeam = addUserToTeamWithRole Nothing + +addUserToTeamWithRole :: HasCallStack => Maybe Role -> UserId -> TeamId -> TestM TeamMember +addUserToTeamWithRole role inviter tid = do + (inv, rsp2) <- addUserToTeamWithRole' role inviter tid + let invitee :: User = responseJsonUnsafe rsp2 + inviteeId = invitee.userId + let invmeta = Just (inviter, inCreatedAt inv) + mem <- getTeamMember inviter tid inviteeId + liftIO $ assertEqual "Member has no/wrong invitation metadata" invmeta (mem ^. Team.invitation) + let zuid = parseSetCookie <$> getHeader "Set-Cookie" rsp2 + liftIO $ assertEqual "Wrong cookie" (Just "zuid") (setCookieName <$> zuid) + pure mem + +addUserToTeamWithRole' :: HasCallStack => Maybe Role -> UserId -> TeamId -> TestM (Invitation, ResponseLBS) +addUserToTeamWithRole' role inviter tid = do + brig <- view tsBrig + inviteeEmail <- randomEmail + let invite = InvitationRequest Nothing role Nothing inviteeEmail Nothing + invResponse <- postInvitation tid inviter invite + inv <- responseJsonError invResponse + inviteeCode <- getInvitationCode tid (inInvitation inv) + r <- + post + ( brig + . path "/register" + . contentJson + . body (acceptInviteBody inviteeEmail inviteeCode) + ) + pure (inv, r) + +acceptInviteBody :: Email -> InvitationCode -> RequestBody +acceptInviteBody email code = + RequestBodyLBS . encode $ + object + [ "name" .= Name "bob", + "email" .= fromEmail email, + "password" .= defPassword, + "team_code" .= code + ] + +getInvitationCode :: HasCallStack => TeamId -> InvitationId -> TestM InvitationCode +getInvitationCode t ref = do + brig <- view tsBrig + let getm :: TestM (Maybe InvitationCode) + getm = do + r <- + get + ( brig + . path "/i/teams/invitation-code" + . queryItem "team" (toByteString' t) + . queryItem "invitation_id" (toByteString' ref) + ) + let lbs = fromMaybe "" $ responseBody r + pure $ fromByteString . Text.encodeUtf8 =<< lbs ^? key "code" . _String + + fromMaybe (error "No code?") + <$> retrying + (constantDelay 800000 <> limitRetries 3) + (\_ -> pure . isNothing) + (const getm) + +postInvitation :: TeamId -> UserId -> InvitationRequest -> TestM ResponseLBS +postInvitation t u i = do + brig <- view tsBrig + post $ + brig + . paths ["teams", toByteString' t, "invitations"] + . contentJson + . body (RequestBodyLBS $ encode i) + . zAuthAccess u "conn" + +zAuthAccess :: UserId -> ByteString -> (Request -> Request) +zAuthAccess u conn = + zUser u + . zConn conn + . zType "access" + +zUser :: UserId -> Request -> Request +zUser = header "Z-User" . toByteString' + +zConn :: ByteString -> Request -> Request +zConn = header "Z-Connection" + +zType :: ByteString -> Request -> Request +zType = header "Z-Type" + +getTeamMember :: HasCallStack => UserId -> TeamId -> UserId -> TestM TeamMember +getTeamMember getter tid gettee = do + g <- view tsGalley + getTeamMember' g getter tid gettee + +getTeamMember' :: (HasCallStack, MonadHttp m, MonadIO m, MonadCatch m) => Galley -> UserId -> TeamId -> UserId -> m TeamMember +getTeamMember' g getter tid gettee = do + r <- get (g . paths ["teams", toByteString' tid, "members", toByteString' gettee] . zUser getter)