diff --git a/CHANGELOG.md b/CHANGELOG.md index 171867e5223..dcc5938707f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,56 @@ +# [2022-02-21] + +## Release notes + +* Upgrade team-settings version to 4.6.1-v0.29.3-0-28cbbd7 (#2106) +* Upgrade webapp version to 2022-02-08-production.0-v0.29.2-0-4d437bb (#2107) +* Change the default set of TLS ciphers (both for the client and the federation APIs) to be compliant to the recommendations of [TR-02102-2](https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/TechGuidelines/TG02102/BSI-TR-02102-2.html). (#2112) +* For wire.com operators: make sure that nginz is deployed. (#2116, #2124) +* Optional team feature config `validateSAMLEmails` added to galley.yaml. + The feature was disabled by default before this release and is now enabled by default. The server wide default can be changed in galley.yaml. Please refer to [/docs/reference/config-options.md#validate-saml-emails](https://github.com/wireapp/wire-server/blob/develop/docs/reference/config-options.md#validate-saml-emails) (#2117) + +## API changes + +* Added minimal API version support: a list of supported API versions can be found at the endpoint `GET /api-version`. Versions can be selected by adding a prefix of the form `/vN` to every route, where `N` is the desired version number (so for example `/v1/conversations` to access version 1 of the `/conversations` endpoint). (#2116) +* Delete `GET /self/name` endpoint (#2101) +* New endpoint (`POST /verification-code/send`) for generating and sending a verification code for 2nd factor authentication actions. (#2124) + +## Features + +* Add freetext search results to "search-users" federation endpoint (#2085) + +## Bug fixes and other updates + +* Ensure empty responses show up without a schema in swagger. They were shown as empty arrays before. (#2104) +* Require the guest links feature is enabled when someone joins by code. (#2084) +* Escape disallowed characters at the beginning of CSV cells to prevent CSV injection vulnerability. (#2096) +* The field `icon` in the body of the `PUT /team/:tid` endpoint is now typed to prevent potential injection attacks. (#2103) + +## Internal changes + +* Enforce conversation access roles more tightly on the backend (was previously only enforce on client): if a guests or non-team-members are not allowed, block guest link creation (new behavior) as well as ephemeral users joining (old behavior). (#2076) +* Remove uses of servant-generics from brig (#2100, #2086) +* Migrate more API end-points to servant. (#2016, #2081, #2091) +* Introduce the row type variable in Brig monads (#2140) +* Build ubuntu20 docker images with cabal instead of stack (#2119, #2060) +* Drop managed conversations (#2125) +* To investigate issues related to push notifications, adjust Gundeck `Debug` leveled logs to not print the message itself. So, that it can safely be turned on in production environments. Add a log entry when a bulk notification is pushed to Cannon. (#2053) +* Add integration tests for scim/saml user creation (#2123) +* Wrap stack with NIX_BUILD_SHELL set to LD_LIBRARY_PATH compatible shell (#2105) +* Removed redundant `setDefaultTemplateLocale` config from the brig helm template. (#2099) +* [not done yet, please do not enable] Optional team feature config `sndFactorPasswordChallenge` added to galley.yaml. + The feature is disabled by default. The server wide default can be changed in galley.yaml. Please refer to [/docs/reference/config-options.md#2nd-factor-password-challenge](https://github.com/wireapp/wire-server/blob/develop/docs/reference/config-options.md#2nd-factor-password-challenge) (#2138) +* Prometheus: Ignore RawResponses (e.g. cannon's await responses) from metrics (#2108) +* Refactor internal handlers for Proteus conversation creation (#2125) +* Specify (in a test) how a message to a deleted legalhold device is refused to be sent. (#2131) + +## Federation changes + +* Add `setSftListAllServers` config flag to brig (#2139) +* Revert restund to 0.4.17. (#2114) + + + # [2022-02-02] ## Release notes diff --git a/Makefile b/Makefile index 7cdd010749a..4ea6e2e0e7f 100644 --- a/Makefile +++ b/Makefile @@ -130,7 +130,11 @@ add-license: # Clean .PHONY: clean clean: +ifeq ($(WIRE_BUILD_WITH_CABAL), 1) + cabal clean +else stack clean +endif $(MAKE) -C services/nginz clean -rm -rf dist -rm -f .metadata @@ -245,9 +249,9 @@ run-docker-builder: .PHONY: git-add-cassandra-schema git-add-cassandra-schema: db-reset git-add-cassandra-schema-impl -CASSANDRA_CONTAINER := $(shell docker ps | grep '/cassandra:' | perl -ne '/^(\S+)\s/ && print $$1') .PHONY: git-add-cassandra-schema-impl git-add-cassandra-schema-impl: + $(eval CASSANDRA_CONTAINER := $(shell docker ps | grep '/cassandra:' | perl -ne '/^(\S+)\s/ && print $$1')) ( echo '-- automatically generated with `make git-add-cassandra-schema`' ; docker exec -i $(CASSANDRA_CONTAINER) /usr/bin/cqlsh -e "DESCRIBE schema;" ) > ./docs/reference/cassandra-schema.cql git add ./docs/reference/cassandra-schema.cql @@ -256,6 +260,7 @@ git-add-cassandra-schema-cabal: db-reset-cabal git-add-cassandra-schema-impl .PHONY: cqlsh cqlsh: + $(eval CASSANDRA_CONTAINER := $(shell docker ps | grep '/cassandra:' | perl -ne '/^(\S+)\s/ && print $$1')) @echo "make sure you have ./deploy/dockerephemeral/run.sh running in another window!" docker exec -it $(CASSANDRA_CONTAINER) /usr/bin/cqlsh diff --git a/build/ubuntu/Dockerfile.builder b/build/ubuntu/Dockerfile.builder index f34e4fc91d5..f6f83ba48e5 100644 --- a/build/ubuntu/Dockerfile.builder +++ b/build/ubuntu/Dockerfile.builder @@ -11,6 +11,7 @@ WORKDIR / ARG wire_server_branch=develop ARG THREADS=4 +ARG CABAL_BUILD_ARGS= RUN set -x && \ echo ${wire_server_branch} && \ git clone -b ${wire_server_branch} https://github.com/wireapp/wire-server.git && \ @@ -21,6 +22,7 @@ RUN set -x && \ stack build haskell-src-exts && \ stack build --pedantic --test --no-run-tests --bench --no-run-benchmarks --dependencies-only -j${THREADS} && \ stack install ormolu && \ + cabal build all --dependencies-only ${CABAL_BUILD_ARGS} && \ cd / && \ # we run the build only to cache the built source in /root/.stack, we can remove the source code itself rm -rf /wire-server diff --git a/build/ubuntu/Dockerfile.fast-intermediate b/build/ubuntu/Dockerfile.fast-intermediate index a4563d0ffca..28668d439da 100644 --- a/build/ubuntu/Dockerfile.fast-intermediate +++ b/build/ubuntu/Dockerfile.fast-intermediate @@ -1,5 +1,5 @@ ARG builder=quay.io/wire/ubuntu20-builder -ARG deps=quay.io/wire/ubuntu-deps +ARG deps=quay.io/wire/ubuntu20-deps:develop #--- Builder stage --- FROM ${builder} as builder @@ -8,7 +8,9 @@ WORKDIR /wire-server/ COPY . /wire-server/ -RUN make clean fast +RUN echo "optimization: False" > ./cabal.project.local && \ + ./hack/bin/cabal-project-local-template.sh "ghc-options: -O0" >> ./cabal.project.local && \ + WIRE_BUILD_WITH_CABAL=1 make clean install #--- Minified stage --- FROM ${deps} diff --git a/build/ubuntu/Dockerfile.intermediate b/build/ubuntu/Dockerfile.intermediate index 61562ab4f07..58edbc4415a 100644 --- a/build/ubuntu/Dockerfile.intermediate +++ b/build/ubuntu/Dockerfile.intermediate @@ -6,7 +6,7 @@ # docker build -f build/alpine/Dockerfile.intermediate ARG builder=quay.io/wire/ubuntu20-builder -ARG deps=quay.io/wire/ubuntu20-deps +ARG deps=quay.io/wire/ubuntu20-deps:develop #--- Builder stage --- FROM ${builder} as builder @@ -15,7 +15,7 @@ WORKDIR /wire-server/ COPY . /wire-server/ -RUN make clean install +RUN WIRE_BUILD_WITH_CABAL=1 make clean install #--- Minified stage --- FROM ${deps} diff --git a/build/ubuntu/Dockerfile.prebuilder b/build/ubuntu/Dockerfile.prebuilder index 22f8dc04c67..4f407d4d253 100644 --- a/build/ubuntu/Dockerfile.prebuilder +++ b/build/ubuntu/Dockerfile.prebuilder @@ -57,15 +57,19 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libncurses-dev \ libncurses5 \ libtinfo5 \ - protobuf-compiler + protobuf-compiler \ + rsync ARG GHC_VERSION=8.10.7 RUN curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org \ | BOOTSTRAP_HASKELL_NONINTERACTIVE=1 BOOTSTRAP_HASKELL_GHC_VERSION=${GHC_VERSION} sh -ENV PATH=/root/.ghcup/bin:${PATH} \ +ENV PATH=/root/.ghcup/bin:/root/.cabal/bin:${PATH} \ LANG=C.UTF-8 \ LC_ALL=C.UTF-8 ARG STACK_VERSION=2.7.3 -RUN ghcup install stack ${STACK_VERSION} +ARG CABAL_VERSION=3.6.2.0 +RUN ghcup install stack ${STACK_VERSION} && \ + ghcup install cabal ${CABAL_VERSION} && \ + cabal install cabal-plan diff --git a/cabal.project b/cabal.project index be2bc1ee317..09c5a8a6a80 100644 --- a/cabal.project +++ b/cabal.project @@ -69,6 +69,15 @@ source-repository-package location: https://github.com/haskell-servant/servant-swagger tag: bb0a84faa073fa9530f60337610d7da3d5b9393c +source-repository-package + type: git + location: https://github.com/haskell-servant/servant.git + tag: 75db4a5327d6d04ae2460bd5ffd008fe16197ba8 + subdir: servant + servant-client + servant-client-core + servant-server + source-repository-package type: git location: https://github.com/kim/hs-collectd @@ -142,15 +151,6 @@ source-repository-package location: https://github.com/wireapp/saml2-web-sso tag: 4227e38be5c0810012dc472fc6931f6087fbce68 -source-repository-package - type: git - location: https://github.com/wireapp/servant.git - tag: a4e15fe75f294816d9ead19ed8a48cae8e0b76e7 - subdir: servant - servant-client - servant-client-core - servant-server - source-repository-package type: git location: https://github.com/wireapp/snappy diff --git a/charts/brig/templates/configmap.yaml b/charts/brig/templates/configmap.yaml index f2374810aba..9f19a4056df 100644 --- a/charts/brig/templates/configmap.yaml +++ b/charts/brig/templates/configmap.yaml @@ -204,7 +204,6 @@ data: suspendTimeout: {{ .setSuspendInactiveUsers.suspendTimeout }} {{- end }} setRichInfoLimit: {{ .setRichInfoLimit }} - setDefaultTemplateLocale: en setDefaultUserLocale: {{ .setDefaultUserLocale }} setMaxTeamSize: {{ .setMaxTeamSize }} setMaxConvSize: {{ .setMaxConvSize }} @@ -229,5 +228,8 @@ data: {{- if .setSftStaticUrl }} setSftStaticUrl: {{ .setSftStaticUrl }} {{- end }} + {{- if .setSftListAllServers }} + setSftListAllServers: {{ .setSftListAllServers }} + {{- end }} {{- end }} {{- end }} diff --git a/charts/brig/values.yaml b/charts/brig/values.yaml index bb67cc4c495..9751bc1cff1 100644 --- a/charts/brig/values.yaml +++ b/charts/brig/values.yaml @@ -63,7 +63,6 @@ config: stdDev: 3000 retryAfter: 86400 setRichInfoLimit: 5000 - setDefaultTemplateLocale: en setDefaultUserLocale: en setMaxTeamSize: 500 setMaxConvSize: 500 diff --git a/charts/galley/templates/configmap.yaml b/charts/galley/templates/configmap.yaml index 548db7f85e9..ba36849e6c5 100644 --- a/charts/galley/templates/configmap.yaml +++ b/charts/galley/templates/configmap.yaml @@ -68,5 +68,9 @@ data: fileSharing: {{- toYaml .settings.featureFlags.fileSharing | nindent 10 }} {{- end }} + {{- if .settings.featureFlags.sndFactorPasswordChallenge }} + sndFactorPasswordChallenge: + {{- toYaml .settings.featureFlags.sndFactorPasswordChallenge | nindent 10 }} + {{- end }} {{- end }} {{- end }} diff --git a/charts/galley/values.yaml b/charts/galley/values.yaml index 45e9c0ca77e..26546dba980 100644 --- a/charts/galley/values.yaml +++ b/charts/galley/values.yaml @@ -39,7 +39,12 @@ config: # fileSharing: # defaults: # status: enabled - # lockStatus: unlocked + # lockStatus: unlocked + # sndFactorPasswordChallenge setting is optional + # sndFactorPasswordChallenge: + # defaults: + # status: disabled + # lockStatus: unlocked aws: region: "eu-west-1" proxy: {} diff --git a/charts/nginx-ingress-controller/values.yaml b/charts/nginx-ingress-controller/values.yaml index a785cd545a3..1c455d2fb6f 100644 --- a/charts/nginx-ingress-controller/values.yaml +++ b/charts/nginx-ingress-controller/values.yaml @@ -3,8 +3,12 @@ nginx-ingress: controller: config: - # NOTE: These are some sane defaults, you may want to overrride them on your own installation - ssl-ciphers: "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256" + # 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-ciphers: "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256" http2-max-field-size: 16k http2-max-header-size: 32k proxy-buffer-size: 16k @@ -28,11 +32,11 @@ nginx-ingress: # 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: +# 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 +# There are also downsides to setting externalTrafficPolicy: Local # Please look at the following blog post, which very clearly explains the upsides and # downsides of this setting # https://www.asykim.com/blog/deep-dive-into-kubernetes-external-traffic-policies diff --git a/charts/nginz/templates/conf/_nginx.conf.tpl b/charts/nginz/templates/conf/_nginx.conf.tpl index 58cade243b0..c6c9969e127 100644 --- a/charts/nginz/templates/conf/_nginx.conf.tpl +++ b/charts/nginz/templates/conf/_nginx.conf.tpl @@ -207,7 +207,7 @@ http { } {{ range $path := .Values.nginx_conf.disabled_paths }} - location {{ $path }} { + location ~* ^(/v[0-9]+)?{{ $path }} { return 404; } @@ -227,7 +227,10 @@ http { rewrite ^/api-docs{{ $location.path }} {{ $location.path }}/api-docs?base_url=https://{{ $.Values.nginx_conf.env }}-nginz-https.{{ $.Values.nginx_conf.external_env_domain }}/ break; {{- end }} - location {{ $location.path }} { + {{- $versioned := ternary $location.versioned true (hasKey $location "versioned") -}} + {{- $path := printf "%s%s" (ternary "(/v[0-9]+)?" "" $versioned) $location.path }} + + location ~* ^{{ $path }} { # remove access_token from logs, see 'Note sanitized_request' above. set $sanitized_request $request; diff --git a/charts/nginz/values.yaml b/charts/nginz/values.yaml index 3cf782871e6..877b63c11f6 100644 --- a/charts/nginz/values.yaml +++ b/charts/nginz/values.yaml @@ -43,13 +43,13 @@ nginx_conf: # title: "Production" disabled_paths: - /conversations/last-events - - ~* ^/conversations/([^/]*)/knock - - ~* ^/conversations/([^/]*)/hot-knock - - ~* ^/conversations/([^/]*)/messages - - ~* ^/conversations/([^/]*)/client-messages - - ~* ^/conversations/([^/]*)/events - - ~* ^/conversations/([^/]*)/call - - ~* ^/conversations/([^/]*)/call/state + - /conversations/([^/]*)/knock + - /conversations/([^/]*)/hot-knock + - /conversations/([^/]*)/messages + - /conversations/([^/]*)/client-messages + - /conversations/([^/]*)/events + - /conversations/([^/]*)/call + - /conversations/([^/]*)/call/state - /search/top - /search/common # -- The origins from which we allow CORS requests. These are combined with 'external_env_domain' to form a full url @@ -59,12 +59,12 @@ nginx_conf: - account upstreams: cargohold: - - path: ~* ^/conversations/([^/]*)/assets + - path: /conversations/([^/]*)/assets envs: - all max_body_size: "0" disable_request_buffering: true - - path: ~* ^/conversations/([^/]*)/otr/assets + - path: /conversations/([^/]*)/otr/assets envs: - all max_body_size: "0" @@ -87,6 +87,10 @@ nginx_conf: max_body_size: "0" disable_request_buffering: true brig: + - path: /api-version + envs: + - all + disable_zauth: true - path: /users envs: - all @@ -96,7 +100,7 @@ nginx_conf: - path: /list-users envs: - all - - path: ~* ^/api/swagger.json$ + - path: /api/swagger.json$ disable_zauth: true envs: - all @@ -110,7 +114,7 @@ nginx_conf: - path: /connections envs: - all - - path: ~* ^/list-connections$ + - path: /list-connections$ envs: - all - path: /invitations @@ -162,7 +166,7 @@ nginx_conf: - path: /bot/users envs: - all - - path: ~* ^/conversations/([^/]*)/bots + - path: /conversations/([^/]*)/bots envs: - all - path: /invitations/info @@ -196,41 +200,49 @@ nginx_conf: - staging disable_zauth: true basic_auth: true + versioned: false - path: /i/users/login-code envs: - staging disable_zauth: true basic_auth: true + versioned: false - path: /i/users/invitation-code envs: - staging disable_zauth: true basic_auth: true - - path: ~* ^/i/users/([^/]*)/rich-info + versioned: false + - path: /i/users/([^/]*)/rich-info envs: - staging disable_zauth: true basic_auth: true - - path: ~* ^/i/teams/([^/]*)/suspend + versioned: false + - path: /i/teams/([^/]*)/suspend envs: - staging disable_zauth: true basic_auth: true - - path: ~* ^/i/teams/([^/]*)/unsuspend + versioned: false + - path: /i/teams/([^/]*)/unsuspend envs: - staging disable_zauth: true basic_auth: true + versioned: false - path: /i/provider/activation-code envs: - staging disable_zauth: true basic_auth: true - - path: ~* ^/i/legalhold/whitelisted-teams(.*) + versioned: false + - path: /i/legalhold/whitelisted-teams(.*) envs: - staging disable_zauth: true basic_auth: true + versioned: false - path: /cookies envs: - all @@ -253,17 +265,17 @@ nginx_conf: - path: /search envs: - all - - path: ~* ^/teams/([^/]*)/invitations(.*) + - path: /teams/([^/]*)/invitations(.*) envs: - all - - path: ~* ^/teams/([^/]*)/services(.*) + - path: /teams/([^/]*)/services(.*) envs: - all - - path: ~* ^/teams/invitations/info$ + - path: /teams/invitations/info$ envs: - all disable_zauth: true - - path: ~* ^/teams/invitations/by-email$ + - path: /teams/invitations/by-email$ envs: - all disable_zauth: true @@ -272,26 +284,31 @@ nginx_conf: - staging disable_zauth: true basic_auth: true + versioned: false - path: /calls envs: - all - - path: ~* ^/teams/([^/]*)/size$ + - path: /teams/([^/]*)/size$ envs: - all - - path: ~* ^/teams/([^/]*)/search$ + - path: /teams/([^/]*)/search$ envs: - all + - path: /verification-code/send + envs: + - all + disable_zauth: true galley: - path: /conversations/code-check disable_zauth: true envs: - all - - path: ~* ^/conversations/([^/]*)/otr/messages + - path: /conversations/([^/]*)/otr/messages envs: - all max_body_size: 40m body_buffer_size: 256k - - path: ~* ^/conversations/([^/]*)/([^/]*)/proteus/messages + - path: /conversations/([^/]*)/([^/]*)/proteus/messages envs: - all max_body_size: 40m @@ -313,57 +330,60 @@ nginx_conf: envs: - all doc: true - - path: ~* ^/teams$ + - path: /teams$ envs: - all - - path: ~* ^/teams/([^/]*)$ + - path: /teams/([^/]*)$ envs: - all - - path: ~* ^/teams/([^/]*)/members(.*) + - path: /teams/([^/]*)/members(.*) envs: - all - - path: ~* ^/teams/([^/]*)/get-members-by-ids-using-post(.*) + - path: /teams/([^/]*)/get-members-by-ids-using-post(.*) envs: - all - - path: ~* ^/teams/([^/]*)/conversations(.*) + - path: /teams/([^/]*)/conversations(.*) envs: - all - - path: ~* ^/teams/([^/]*)/members/csv$ + - path: /teams/([^/]*)/members/csv$ envs: - all - - path: ~* ^/teams/([^/]*)/legalhold(.*) + - path: /teams/([^/]*)/legalhold(.*) envs: - all - - path: ~* ^/i/teams/([^/]*)/legalhold(.*) + - path: /i/teams/([^/]*)/legalhold(.*) envs: - staging disable_zauth: true basic_auth: true - - path: ~* ^/custom-backend/by-domain/([^/]*)$ + versioned: false + - path: /custom-backend/by-domain/([^/]*)$ disable_zauth: true envs: - all - - path: ~* ^/i/custom-backend/by-domain/([^/]*)$ + - path: /i/custom-backend/by-domain/([^/]*)$ disable_zauth: true basic_auth: true envs: - staging - - path: ~* ^/teams/api-docs + versioned: false + - path: /teams/api-docs envs: - all disable_zauth: true - - path: ~* ^/teams/([^/]*)/features + - path: /teams/([^/]*)/features envs: - all - - path: ~* ^/teams/([^/]*)/features/([^/])* + - path: /teams/([^/]*)/features/([^/])* envs: - all - - path: ~* /i/teams/([^/]*)/features/([^/]*) + - path: /i/teams/([^/]*)/features/([^/]*) envs: - staging disable_zauth: true basic_auth: true - - path: ~* ^/feature-configs(.*) + versioned: false + - path: /feature-configs(.*) envs: - all - path: /galley-api/swagger-ui @@ -391,6 +411,7 @@ nginx_conf: basic_auth: true envs: - staging + versioned: false - path: /sso-initiate-bind envs: - all @@ -432,7 +453,7 @@ nginx_conf: envs: - all disable_zauth: true - - path: ~* ^/teams/([^/]*)/billing(.*) + - path: /teams/([^/]*)/billing(.*) envs: - all calling-test: diff --git a/charts/team-settings/values.yaml b/charts/team-settings/values.yaml index cfd6ddd2656..cd16b0e039a 100644 --- a/charts/team-settings/values.yaml +++ b/charts/team-settings/values.yaml @@ -9,7 +9,7 @@ resources: cpu: "1" image: repository: quay.io/wire/team-settings - tag: "4.3.0-v0.28.28-a2f11cf" + tag: "4.6.1-v0.29.3-0-28cbbd7" service: https: externalPort: 443 diff --git a/charts/webapp/values.yaml b/charts/webapp/values.yaml index b956b323de3..fe3cdbf7dec 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: "2022-01-27-production.0-v0.28.29-0-42c9a1e" + tag: "2022-02-08-production.0-v0.29.2-0-4d437bb" service: https: externalPort: 443 diff --git a/deploy/services-demo/conf/nginz/nginx.conf b/deploy/services-demo/conf/nginz/nginx.conf index aa85a7cca58..0b86eb2c1bf 100644 --- a/deploy/services-demo/conf/nginz/nginx.conf +++ b/deploy/services-demo/conf/nginz/nginx.conf @@ -219,6 +219,11 @@ http { proxy_pass http://brig; } + location /verification-code/send { + include common_response_no_zauth.conf; + proxy_pass http://brig; + } + ## brig authenticated endpoints location /self { diff --git a/dev-packages.nix b/dev-packages.nix index 96e37719841..21e897d1d91 100644 --- a/dev-packages.nix +++ b/dev-packages.nix @@ -141,6 +141,7 @@ let pkgs.libsodium.dev pkgs.libxml2.dev pkgs.ncurses.dev + pkgs.niv.out pkgs.openssl.dev pkgs.pcre.dev pkgs.snappy.dev @@ -162,12 +163,20 @@ let export CONFIG_SHELL="${compile-deps}/bin/sh" exec "${pkgs.cabal-install}/bin/cabal" "$@" ''; + + # stack-deps.nix sets LD_LIBRARY_PATH, which could be incompatible with the + # system bash. To ensure that nix-shell invoked by stack uses the correct + # shell to build we set NIX_BUILD_SHELL here. + stack-wrapper = pkgs.writeShellScriptBin "stack" '' + export NIX_BUILD_SHELL="${pkgs.bash}/bin/bash" + exec "${pinned.stack}/bin/stack" "$@" + ''; in [ pkgs.cfssl pkgs.docker-compose pkgs.gnumake - (pkgs.haskell-language-server.override {supportedGhcVersions = ["8107"];}) + (pkgs.haskell-language-server.override { supportedGhcVersions = [ "8107" ]; }) pkgs.jq pkgs.ormolu pkgs.telepresence @@ -179,7 +188,7 @@ in # To actually run buildah on nixos, I had to follow this: https://gist.github.com/alexhrescale/474d55635154e6b2cd6362c3bb403faf pkgs.buildah - pinned.stack + stack-wrapper pinned.helm pinned.helmfile pinned.kubectl diff --git a/docs/developer/servant.md b/docs/developer/servant.md index 07e003facbc..6873f446b3b 100644 --- a/docs/developer/servant.md +++ b/docs/developer/servant.md @@ -4,7 +4,7 @@ We currently use Servant for the public (i.e. client-facing) API in brig, galley Client-facing APIs are defined in `Wire.API.Routes.Public.{Brig,Galley}`. Internal APIs are all over the place at the moment. Federation APIs are in `Wire.API.Federation.API.{Brig,Galley}`. -Our APIs are able to generate Swagger documentation semi-automatically using `servant-swagger2`. The `schema-profunctor` library (see README in libs/schema-profunctor) is used to create "schemas" for the input and output types used in the Servant APIs. A schema contains all the information needed to serialise/deserialise JSON values, as well as the documentation and metadata needed to generate Swagger. +Our APIs are able to generate Swagger documentation semi-automatically using `servant-swagger2`. The `schema-profunctor` library (see [`README.md`](/libs/schema-profunctor/README.md) in `libs/schema-profunctor`) is used to create "schemas" for the input and output types used in the Servant APIs. A schema contains all the information needed to serialise/deserialise JSON values, as well as the documentation and metadata needed to generate Swagger. # Combinators @@ -17,6 +17,7 @@ This is a family of combinators to handle the headers that nginx adds to request - `ZUser`: extracts the `UserId` in the `Z-User` header. - `ZLocalUser`: same as `ZUser`, but as a `Local` object (i.e. qualified by the local domain); this is useful when writing federation-aware handlers. - `ZConn`: extracts the `ConnId` in the `Z-Connection` header. + - `ZConversation`: extracts the `ConvId` in the `Z-Conversation` header. ## `MultiVerb` diff --git a/docs/reference/cassandra-schema.cql b/docs/reference/cassandra-schema.cql index 5aee8661a51..ac4a6ce6461 100644 --- a/docs/reference/cassandra-schema.cql +++ b/docs/reference/cassandra-schema.cql @@ -429,6 +429,8 @@ CREATE TABLE galley_test.team_features ( self_deleting_messages_lock_status int, self_deleting_messages_status int, self_deleting_messages_ttl int, + snd_factor_password_challenge_lock_status int, + snd_factor_password_challenge_status int, sso_status int, validate_saml_emails int ) WITH bloom_filter_fp_chance = 0.1 diff --git a/docs/reference/config-options.md b/docs/reference/config-options.md index 85592137db8..0e5e21fe381 100644 --- a/docs/reference/config-options.md +++ b/docs/reference/config-options.md @@ -167,6 +167,34 @@ The lock status for individual teams can be changed via the internal API (`PUT / The feature status for individual teams can be changed via the public API (if the feature is unlocked). +### Validate SAML Emails + +If this is enabled, if a new user account is created with an email address as SAML NameID or SCIM externalId, users will receive a validation email. If they follow the validation procedure, they will be able to receive emails about their account, eg., if a new device is associated with the account. If the user does not validate their email address, they can still use it to login. + +Validate SAML emails is enabled by default; this is almost always what you want. If you want a different configuration, use the following syntax: + +```yaml +# galley.yaml +validateSAMLEmails: + defaults: + status: disabled +``` + +### 2nd Factor Password Challenge + +By default Wire enforces a 2nd factor authentication for certain user operations like e.g. activating an account, changing email or password, or deleting an account. +If this feature is enabled, a 2nd factor password challenge will be performed for a set of additional user operations like e.g. for generating SCIM tokens, login, or adding a client. + +Usually the default is what you want. If you explicitly want to enable the feature, use the following syntax: + +```yaml +# galley.yaml +sndFactorPasswordChallenge: + defaults: + status: disabled|enabled + lockStatus: locked|unlocked +``` + ### Federation Domain Regardless of whether a backend wants to enable federation or not, the operator @@ -334,6 +362,35 @@ When a `null` value is encountered, it is assumed to be (Introduced in https://github.com/wireapp/wire-server/pull/1811.) +### SFT configuration + +Configuring SFT load balancing can be done in two (mutually exclusive) settings: + +1) Configuring a SRV DNS record based load balancing setting + +``` +# [brig.yaml] +sft: + sftBaseDomain: sft.wire.example.yourcloud.comk + sftSRVServiceName: sft + sftDiscoveryIntervalSeconds: 10 + sftListLength: 20 +``` + +or + +2) Configuring a HTTP-based load balancing setting + +``` +# [brig.yaml] +settings: + setSftStaticUrl: https://sftd.wire.yourcloud.com +``` + +This setting assumes that the sft load balancer has been deployed witht hte `sftd` helm chart. + +Additionally if `setSftListAllServers` is set to `enabled` (disabled by default) then the `/calls/config/v2` endpoint will include a list of all servers that are load balanced by `setSftStaticUrl` at field `sft_servers_all`. This is required to enable calls between federated instances of Wire. + ### Locale diff --git a/docs/reference/provisioning/scim-via-curl.md b/docs/reference/provisioning/scim-via-curl.md index c39fd9ec6ae..aaa2a9eea56 100644 --- a/docs/reference/provisioning/scim-via-curl.md +++ b/docs/reference/provisioning/scim-via-curl.md @@ -1,198 +1 @@ -# Using the SCIM API with curl {#RefScimViaCurl} - -_Author: Matthias Fischmann_ - ---- - -This page shows you how to communicate with the wire backend through -the [SCIM API](http://www.simplecloud.info/) by example. All examples -are [curl](https://curl.haxx.se/) (in bash syntax). - -We support setting the handle and user name in wire (the thing with -`@` and the longer thing without `@`). There is also support for -setting rich-info. Group provisioning is planned, but the release -date hasn't been fixed yet. - -If you want to dive into the backend code, start [reading here in our -backend](https://github.com/wireapp/wire-server/blob/develop/services/spar/src/Spar/Scim.hs) -and [our hscim library](https://github.com/wireapp/hscim). - - -## Creating an SCIM token - -First, we need a little shell environment: - -```bash -export WIRE_BACKEND=https://prod-nginz-https.wire.com -export WIRE_ADMIN=... -export WIRE_PASSWD=... -``` - -SCIM currently supports a variant of HTTP basic auth. In order to -create a token in your team, you need to authenticate using your team -admin credentials. The way this works behind the scenes in your -browser or cell phone, and in plain sight if you want to use curl, is -you need to get a wire token. - -```bash -export BEARER=$(curl -X POST \ - --header 'Content-Type: application/json' \ - --header 'Accept: application/json' \ - -d '{"email":"'"$WIRE_ADMIN"'","password":"'"$WIRE_PASSWD"'"}' \ - $WIRE_BACKEND/login'?persist=false' | jq -r .access_token) -``` - -This token will be good for 15 minutes; after that, just repeat. -(Note that SCIM requests are authenticated with a SCIM token, see -below. SCIM tokens do not expire, but need to be deleted explicitly.) - -If you don't want to install [jq](https://stedolan.github.io/jq/), you -can just call the `curl` command and copy the access token into the -shell variable manually. - -A quick test that you're logged in: - -```bash -curl -X GET --header "Authorization: Bearer $BEARER" \ - $WIRE_BACKEND/self -``` - -Now you are ready to create a SCIM token: - -```bash -export SCIM_TOKEN_FULL=$(curl -X POST \ - --header "Authorization: Bearer $BEARER" \ - --header 'Content-Type: application/json;charset=utf-8' \ - -d '{ "description": "test '"`date`"'", "password": "'"$WIRE_PASSWD"'" }' \ - $WIRE_BACKEND/scim/auth-tokens) -export SCIM_TOKEN=$(echo $SCIM_TOKEN_FULL | jq -r .token) -export SCIM_TOKEN_ID=$(echo $SCIM_TOKEN_FULL | jq -r .info.id) -``` - -... and look it up again: - -```bash -curl -X GET --header "Authorization: Bearer $BEARER" \ - $WIRE_BACKEND/scim/auth-tokens -``` - -... and delete it: - -```bash -curl -X DELETE --header "Authorization: Bearer $BEARER" \ - $WIRE_BACKEND/scim/auth-tokens?id=$SCIM_TOKEN_ID -``` - -## CRUD - -### JSON encoding of SCIM Users - -A minimal definition of a user looks like this: - -```bash -export SCIM_USER='{ - "schemas" : ["urn:ietf:params:scim:schemas:core:2.0:User"], - "externalId" : "nick@example.com", - "userName" : "nick", - "displayName" : "The Nick" -}' -``` - -The `externalId` is used to construct a saml identity. Two cases are -currently supported: - -1. `externalId` contains a valid email address. The SAML `NameID` has -the form `me@example.com`. -2. `externalId` contains anything that is *not* an email address. The -SAML `NameID` has the form `...`. - -*NOTE: It is important to configure your SAML provider to use -`nameid-format:emailAddress` or `nameid-format:unspecified`. Other -nameid formats are not supported at this moment*. -See also: https://github.com/wireapp/wire-server/blob/c507ed64a7d4f0af2bffe2f9c3eb4b5f89a477c0/services/spar/src/Spar/Scim/User.hs#L149-L158 - -We also support custom fields that are used in rich profiles in this -form [see {#RefRichInfo}](../user/rich-info.md): - -```bash -export SCIM_USER='{ - "schemas" : ["urn:ietf:params:scim:schemas:core:2.0:User", "urn:wire:scim:schemas:profile:1.0"], - "externalId" : "rnick@example.com", - "userName" : "rnick", - "displayName" : "The Rich Nick", - "urn:wire:scim:schemas:profile:1.0": { - "richInfo": [ - { - "type": "Department", - "value": "Sales & Marketing" - }, - { - "type": "Favorite color", - "value": "Blue" - } - ] - } -}' -``` - -### create user - -```bash -export STORED_USER=$(curl -X POST \ - --header "Authorization: Bearer $SCIM_TOKEN" \ - --header 'Content-Type: application/json;charset=utf-8' \ - -d "$SCIM_USER" \ - $WIRE_BACKEND/scim/v2/Users) -export STORED_USER_ID=$(echo $STORED_USER | jq -r .id) -``` - -### get specific user - -```bash -curl -X GET \ - --header "Authorization: Bearer $SCIM_TOKEN" \ - --header 'Content-Type: application/json;charset=utf-8' \ - $WIRE_BACKEND/scim/v2/Users/$STORED_USER_ID -``` - -### get all users - -There is a way to do this in the SCIM protocol, but it's not -implemented in wire for performance reasons. If you need a complete -list of your team members, try the CSV download button in the team -management app. - -### update user - -For each put request, you need to provide the full json object. All -omitted fields will be set to `null`. (If you do not have an -up-to-date user present, just `GET` one right before the `PUT`.) - -```bash -export SCIM_USER='{ - "schemas" : ["urn:ietf:params:scim:schemas:core:2.0:User"], - "externalId" : "rnick@example.com", - "userName" : "newnick", - "displayName" : "The New Nick" -}' - -curl -X PUT \ - --header "Authorization: Bearer $SCIM_TOKEN" \ - --header 'Content-Type: application/json;charset=utf-8' \ - -d "$SCIM_USER" \ - $WIRE_BACKEND/scim/v2/Users/$STORED_USER_ID -``` - -### delete user - -``` -curl -X DELETE \ - --header "Authorization: Bearer $SCIM_TOKEN" \ - $WIRE_BACKEND/scim/v2/Users/$STORED_USER_ID -``` - -### groups - -**Not implemented yet.** +# This page [has gone here](https://docs.wire.com/understand/single-sign-on/main.html#using-scim-via-curl). diff --git a/docs/reference/user/registration.md b/docs/reference/user/registration.md index 59c464c05fb..fee8c310227 100644 --- a/docs/reference/user/registration.md +++ b/docs/reference/user/registration.md @@ -34,7 +34,7 @@ POST /register "password": "secret", // 6-digit 'email_code' or 'phone_code' - "email_code": 123456 + "email_code": "123456" } ``` diff --git a/hack/helm_vars/wire-server/values.yaml.gotmpl b/hack/helm_vars/wire-server/values.yaml.gotmpl index 98810a0b212..ffc2b22184c 100644 --- a/hack/helm_vars/wire-server/values.yaml.gotmpl +++ b/hack/helm_vars/wire-server/values.yaml.gotmpl @@ -18,7 +18,7 @@ cassandra-migrations: imagePullPolicy: {{ .Values.imagePullPolicy }} cassandra: host: cassandra-ephemeral - replicaCount: 1 + replicationFactor: 1 elasticsearch-index: imagePullPolicy: {{ .Values.imagePullPolicy }} elasticsearch: diff --git a/hack/helmfile-single.yaml b/hack/helmfile-single.yaml index 8d5c81a0413..c6107920b38 100644 --- a/hack/helmfile-single.yaml +++ b/hack/helmfile-single.yaml @@ -13,6 +13,7 @@ environments: values: - namespace: {{ requiredEnv "NAMESPACE" }} - federationDomain: {{ requiredEnv "FEDERATION_DOMAIN" }} + - imagePullPolicy: Always repositories: - name: stable diff --git a/libs/api-bot/src/Network/Wire/Bot/Monad.hs b/libs/api-bot/src/Network/Wire/Bot/Monad.hs index 79bc2792167..da64c0cebe6 100644 --- a/libs/api-bot/src/Network/Wire/Bot/Monad.hs +++ b/libs/api-bot/src/Network/Wire/Bot/Monad.hs @@ -689,7 +689,7 @@ mkBot :: BotTag -> User -> PlainTextPassword -> BotNet Bot mkBot tag user pw = do log Info $ botLogFields (userId user) tag . msg (val "Login") let ident = fromMaybe (error "No email") (userEmail user) - let cred = PasswordLogin (LoginByEmail ident) pw Nothing + let cred = PasswordLogin (LoginByEmail ident) pw Nothing Nothing auth <- login cred >>= maybe (throwM LoginFailed) return aref <- nextAuthRefresh auth env <- BotNet ask diff --git a/libs/api-client/src/Network/Wire/Client/API/Conversation.hs b/libs/api-client/src/Network/Wire/Client/API/Conversation.hs index 2fffb2f5528..e034aa95069 100644 --- a/libs/api-client/src/Network/Wire/Client/API/Conversation.hs +++ b/libs/api-client/src/Network/Wire/Client/API/Conversation.hs @@ -140,6 +140,6 @@ createConv users name = sessionRequest req rsc readBody method POST . path "conversations" . acceptJson - . json (NewConvUnmanaged (NewConv users [] name mempty Nothing Nothing Nothing Nothing roleNameWireAdmin)) + . json (NewConv users [] name mempty Nothing Nothing Nothing Nothing roleNameWireAdmin) $ empty rsc = status201 :| [] diff --git a/libs/galley-types/src/Galley/Types.hs b/libs/galley-types/src/Galley/Types.hs index 018af4ff9ab..b6e94f6bbd0 100644 --- a/libs/galley-types/src/Galley/Types.hs +++ b/libs/galley-types/src/Galley/Types.hs @@ -66,8 +66,6 @@ module Galley.Types CustomBackend (..), Invite (..), NewConv (..), - NewConvManaged (..), - NewConvUnmanaged (..), MemberUpdate (..), OtherMemberUpdate (..), MutedStatus (..), diff --git a/libs/galley-types/src/Galley/Types/Teams.hs b/libs/galley-types/src/Galley/Types/Teams.hs index d4f7b0b984b..7c106a93d30 100644 --- a/libs/galley-types/src/Galley/Types/Teams.hs +++ b/libs/galley-types/src/Galley/Types/Teams.hs @@ -33,6 +33,8 @@ module Galley.Types.Teams flagConferenceCalling, flagSelfDeletingMessages, flagConversationGuestLinks, + flagsTeamFeatureValidateSAMLEmailsStatus, + flagTeamFeatureSndFactorPasswordChallengeStatus, Defaults (..), unDefaults, FeatureSSO (..), @@ -79,7 +81,6 @@ module Galley.Types.Teams TeamConversation, newTeamConversation, conversationId, - managedConversation, TeamConversationList, newTeamConversationList, teamConversations, @@ -217,7 +218,9 @@ data FeatureFlags = FeatureFlags _flagFileSharing :: !(Defaults (TeamFeatureStatus 'WithLockStatus 'TeamFeatureFileSharing)), _flagConferenceCalling :: !(Defaults (TeamFeatureStatus 'WithoutLockStatus 'TeamFeatureConferenceCalling)), _flagSelfDeletingMessages :: !(Defaults (TeamFeatureStatus 'WithLockStatus 'TeamFeatureSelfDeletingMessages)), - _flagConversationGuestLinks :: !(Defaults (TeamFeatureStatus 'WithLockStatus 'TeamFeatureGuestLinks)) + _flagConversationGuestLinks :: !(Defaults (TeamFeatureStatus 'WithLockStatus 'TeamFeatureGuestLinks)), + _flagsTeamFeatureValidateSAMLEmailsStatus :: !(Defaults (TeamFeatureStatus 'WithoutLockStatus 'TeamFeatureValidateSAMLEmails)), + _flagTeamFeatureSndFactorPasswordChallengeStatus :: !(Defaults (TeamFeatureStatus 'WithLockStatus 'TeamFeatureSndFactorPasswordChallenge)) } deriving (Eq, Show, Generic) @@ -265,20 +268,37 @@ instance FromJSON FeatureFlags where <*> (fromMaybe (Defaults (TeamFeatureStatusNoConfig TeamFeatureEnabled)) <$> (obj .:? "conferenceCalling")) <*> (fromMaybe (Defaults defaultSelfDeletingMessagesStatus) <$> (obj .:? "selfDeletingMessages")) <*> (fromMaybe (Defaults defaultGuestLinksStatus) <$> (obj .:? "conversationGuestLinks")) + <*> (fromMaybe (Defaults defaultTeamFeatureValidateSAMLEmailsStatus) <$> (obj .:? "validateSAMLEmails")) + <*> (fromMaybe (Defaults defaultTeamFeatureSndFactorPasswordChallengeStatus) <$> (obj .:? "sndFactorPasswordChallenge")) instance ToJSON FeatureFlags where - toJSON (FeatureFlags sso legalhold searchVisibility appLock classifiedDomains fileSharing conferenceCalling selfDeletingMessages guestLinks) = - object $ - [ "sso" .= sso, - "legalhold" .= legalhold, - "teamSearchVisibility" .= searchVisibility, - "appLock" .= appLock, - "classifiedDomains" .= classifiedDomains, - "fileSharing" .= fileSharing, - "conferenceCalling" .= conferenceCalling, - "selfDeletingMessages" .= selfDeletingMessages, - "conversationGuestLinks" .= guestLinks - ] + toJSON + ( FeatureFlags + sso + legalhold + searchVisibility + appLock + classifiedDomains + fileSharing + conferenceCalling + selfDeletingMessages + guestLinks + validateSAMLEmails + sndFactorPasswordChallenge + ) = + object + [ "sso" .= sso, + "legalhold" .= legalhold, + "teamSearchVisibility" .= searchVisibility, + "appLock" .= appLock, + "classifiedDomains" .= classifiedDomains, + "fileSharing" .= fileSharing, + "conferenceCalling" .= conferenceCalling, + "selfDeletingMessages" .= selfDeletingMessages, + "conversationGuestLinks" .= guestLinks, + "validateSAMLEmails" .= validateSAMLEmails, + "sndFactorPasswordChallenge" .= sndFactorPasswordChallenge + ] instance FromJSON FeatureSSO where parseJSON (String "enabled-by-default") = pure FeatureSSOEnabledByDefault @@ -372,6 +392,7 @@ roleHiddenPermissions role = HiddenPermissions p p ChangeTeamFeature TeamFeatureClassifiedDomains {- the features not listed here can only be changed in stern -}, ChangeTeamFeature TeamFeatureSelfDeletingMessages, ChangeTeamFeature TeamFeatureGuestLinks, + ChangeTeamFeature TeamFeatureSndFactorPasswordChallenge, ChangeTeamMemberProfiles, ReadIdp, CreateUpdateDeleteIdp, @@ -394,6 +415,7 @@ roleHiddenPermissions role = HiddenPermissions p p ViewTeamFeature TeamFeatureConferenceCalling, ViewTeamFeature TeamFeatureSelfDeletingMessages, ViewTeamFeature TeamFeatureGuestLinks, + ViewTeamFeature TeamFeatureSndFactorPasswordChallenge, ViewLegalHoldUserSettings, ViewTeamSearchVisibility ] diff --git a/libs/galley-types/test/unit/Test/Galley/Types.hs b/libs/galley-types/test/unit/Test/Galley/Types.hs index 91d766c02dc..43681bdf5ca 100644 --- a/libs/galley-types/test/unit/Test/Galley/Types.hs +++ b/libs/galley-types/test/unit/Test/Galley/Types.hs @@ -98,3 +98,5 @@ instance Arbitrary FeatureFlags where <*> arbitrary <*> arbitrary <*> arbitrary + <*> arbitrary + <*> arbitrary diff --git a/libs/metrics-wai/src/Data/Metrics/Servant.hs b/libs/metrics-wai/src/Data/Metrics/Servant.hs index 6f12519e283..2208561b097 100644 --- a/libs/metrics-wai/src/Data/Metrics/Servant.hs +++ b/libs/metrics-wai/src/Data/Metrics/Servant.hs @@ -44,7 +44,7 @@ import Servant.Multipart -- | This does not catch errors, so it must be called outside of 'WU.catchErrors'. servantPrometheusMiddleware :: forall proxy api. (RoutesToPaths api) => proxy api -> Wai.Middleware -servantPrometheusMiddleware _ = Promth.prometheus conf . Promth.instrumentHandlerValue promthNormalize +servantPrometheusMiddleware _ = Promth.prometheus conf . instrument promthNormalize where promthNormalize :: Wai.Request -> Text promthNormalize req = pathInfo @@ -52,6 +52,9 @@ servantPrometheusMiddleware _ = Promth.prometheus conf . Promth.instrumentHandle mPathInfo = Metrics.treeLookup (routesToPaths @api) $ cs <$> Wai.pathInfo req pathInfo = cs $ fromMaybe "N/A" mPathInfo + -- See Note [Raw Response] + instrument = Promth.instrumentHandlerValueWithFilter Promth.ignoreRawResponses + servantPlusWAIPrometheusMiddleware :: forall proxy api a m b. (RoutesToPaths api, Monad m) => Routes a m b -> proxy api -> Wai.Middleware servantPlusWAIPrometheusMiddleware routes _ = do Promth.prometheus conf . instrument (normalizeWaiRequestRoute paths) diff --git a/libs/wire-api/package.yaml b/libs/wire-api/package.yaml index c616fb4d1c2..8a369f82054 100644 --- a/libs/wire-api/package.yaml +++ b/libs/wire-api/package.yaml @@ -41,6 +41,7 @@ library: - currency-codes >=2.0 - deriving-aeson >=0.2 - deriving-swagger2 + - either - email-validate >=2.0 - errors - extended @@ -89,6 +90,8 @@ library: - wire-message-proto-lens - x509 - wai + - wai-extra + - wai-utilities - wai-websockets - websockets @@ -105,6 +108,7 @@ tests: - base - bytestring - bytestring-conversion + - bytestring-arbitrary >=0.1.3 - cassava - currency-codes - directory diff --git a/libs/wire-api/src/Wire/API/Conversation.hs b/libs/wire-api/src/Wire/API/Conversation.hs index 29cf0dab1dc..f89fa2f1261 100644 --- a/libs/wire-api/src/Wire/API/Conversation.hs +++ b/libs/wire-api/src/Wire/API/Conversation.hs @@ -57,8 +57,6 @@ module Wire.API.Conversation -- * create NewConv (..), - NewConvManaged (..), - NewConvUnmanaged (..), ConvTeamInfo (..), -- * invite @@ -110,8 +108,7 @@ import qualified Data.Swagger as S import qualified Data.Swagger.Build.Api as Doc import Imports import System.Random (randomRIO) -import qualified Test.QuickCheck as QC -import Wire.API.Arbitrary (Arbitrary (arbitrary), GenericUniform (..)) +import Wire.API.Arbitrary import Wire.API.Conversation.Member import Wire.API.Conversation.Role (RoleName, roleNameWireAdmin) import Wire.API.Routes.MultiTablePaging @@ -589,60 +586,7 @@ instance ToSchema ReceiptMode where -------------------------------------------------------------------------------- -- create -{- Note [managed conversations] -~~~~~~~~~~~~~~~~~~~~~~ - -Managed conversations are conversations where every team member is present -automatically. They have been implemented on the backend but never used in -production, and as of July 2, 2018 no managed conversations exist "in the -wild". They also prevent us from decoupling team size and conversation size --- by essentially demanding that they be equal, while in reality allowing -huge teams is much easier than allowing huge conversations and we want to -use that fact. - -For the reason above, it's been decided to remove support for creating -managed conversations from the backend. However, we are not 100% sure that -we won't introduce them again in the future, and so we'd like to retain all -the logic and tests that we have now. - -To that end we have the following types: - - * data NewConv -- allows both managed and unmanaged conversations; - * newtype NewConvUnmanaged -- only unmanaged; - * newtype NewConvManaged -- only managed. - -Those are invariants enforced on the 'FromJSON' level. For convenience, the -newtype constructors have not been hidden. - -The public POST /conversations endpoint only allows unmanaged conversations. -For creating managed conversations we provide an internal endpoint called -POST /i/conversations/managed. When an endpoint receives payload -corresponding to a forbidden conversation type, it throws a JSON parsing -error, which is not optimal but it doesn't matter since nobody is trying to -create managed conversations anyway. --} - -newtype NewConvManaged = NewConvManaged NewConv - deriving stock (Eq, Show) - deriving (FromJSON, ToJSON) via Schema NewConvManaged - -instance ToSchema NewConvManaged where - schema = NewConvManaged <$> unwrap .= newConvSchema `withParser` check - where - unwrap (NewConvManaged c) = c - check c - | newConvIsManaged c = pure c - | otherwise = fail "only managed conversations are allowed here" - -instance Arbitrary NewConvManaged where - arbitrary = - NewConvManaged <$> (arbitrary `QC.suchThat` newConvIsManaged) - -newtype NewConvUnmanaged = NewConvUnmanaged NewConv - deriving stock (Eq, Show) - deriving (FromJSON, ToJSON, S.ToSchema) via Schema NewConvUnmanaged - --- | Used to describe a 'NewConvUnmanaged'. +-- | Used to describe a 'NewConv'. modelNewConversation :: Doc.Model modelNewConversation = Doc.defineModel "NewConversation" $ do Doc.description "JSON object to create a new conversation" @@ -665,19 +609,6 @@ modelNewConversation = Doc.defineModel "NewConversation" $ do Doc.description "Conversation receipt mode" Doc.optional -instance ToSchema NewConvUnmanaged where - schema = NewConvUnmanaged <$> unwrap .= newConvSchema `withParser` check - where - unwrap (NewConvUnmanaged c) = c - check c - | newConvIsManaged c = - fail "managed conversations have been deprecated" - | otherwise = pure c - -instance Arbitrary NewConvUnmanaged where - arbitrary = - NewConvUnmanaged <$> (arbitrary `QC.suchThat` (not . newConvIsManaged)) - data NewConv = NewConv { newConvUsers :: [UserId], -- | A list of qualified users, which can include some local qualified users @@ -694,72 +625,69 @@ data NewConv = NewConv } deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform NewConv) + deriving (FromJSON, ToJSON, S.ToSchema) via (Schema NewConv) -newConvSchema :: ValueSchema NamedSwaggerDoc NewConv -newConvSchema = - objectWithDocModifier - "NewConv" - (description ?~ "JSON object to create a new conversation. When using 'qualified_users' (preferred), you can omit 'users'") - $ NewConv - <$> newConvUsers - .= ( fieldWithDocModifier - "users" - (description ?~ usersDesc) - (array schema) - <|> pure [] - ) - <*> newConvQualifiedUsers - .= ( fieldWithDocModifier - "qualified_users" - (description ?~ qualifiedUsersDesc) - (array schema) - <|> pure [] - ) - <*> newConvName .= maybe_ (optField "name" schema) - <*> (Set.toList . newConvAccess) - .= (fromMaybe mempty <$> optField "access" (Set.fromList <$> array schema)) - <*> newConvAccessRoles .= accessRolesSchemaOpt - <*> newConvTeam - .= maybe_ - ( optFieldWithDocModifier - "team" - (description ?~ "Team information of this conversation") - schema - ) - <*> newConvMessageTimer - .= maybe_ - ( optFieldWithDocModifier - "message_timer" - (description ?~ "Per-conversation message timer") - schema - ) - <*> newConvReceiptMode .= maybe_ (optField "receipt_mode" schema) - <*> newConvUsersRole - .= ( fieldWithDocModifier "conversation_role" (description ?~ usersRoleDesc) schema - <|> pure roleNameWireAdmin - ) - where - usersDesc = - "List of user IDs (excluding the requestor) to be \ - \part of this conversation (deprecated)" - qualifiedUsersDesc = - "List of qualified user IDs (excluding the requestor) \ - \to be part of this conversation" - usersRoleDesc :: Text - usersRoleDesc = - cs $ - "The conversation permissions the users \ - \added in this request should have. \ - \Optional, defaults to '" - <> show roleNameWireAdmin - <> "' if unset." - -newConvIsManaged :: NewConv -> Bool -newConvIsManaged = maybe False cnvManaged . newConvTeam - -data ConvTeamInfo = ConvTeamInfo - { cnvTeamId :: TeamId, - cnvManaged :: Bool +instance ToSchema NewConv where + schema = + objectWithDocModifier + "NewConv" + (description ?~ "JSON object to create a new conversation. When using 'qualified_users' (preferred), you can omit 'users'") + $ NewConv + <$> newConvUsers + .= ( fieldWithDocModifier + "users" + (description ?~ usersDesc) + (array schema) + <|> pure [] + ) + <*> newConvQualifiedUsers + .= ( fieldWithDocModifier + "qualified_users" + (description ?~ qualifiedUsersDesc) + (array schema) + <|> pure [] + ) + <*> newConvName .= maybe_ (optField "name" schema) + <*> (Set.toList . newConvAccess) + .= (fromMaybe mempty <$> optField "access" (Set.fromList <$> array schema)) + <*> newConvAccessRoles .= accessRolesSchemaOpt + <*> newConvTeam + .= maybe_ + ( optFieldWithDocModifier + "team" + (description ?~ "Team information of this conversation") + schema + ) + <*> newConvMessageTimer + .= maybe_ + ( optFieldWithDocModifier + "message_timer" + (description ?~ "Per-conversation message timer") + schema + ) + <*> newConvReceiptMode .= maybe_ (optField "receipt_mode" schema) + <*> newConvUsersRole + .= ( fieldWithDocModifier "conversation_role" (description ?~ usersRoleDesc) schema + <|> pure roleNameWireAdmin + ) + where + usersDesc = + "List of user IDs (excluding the requestor) to be \ + \part of this conversation (deprecated)" + qualifiedUsersDesc = + "List of qualified user IDs (excluding the requestor) \ + \to be part of this conversation" + usersRoleDesc :: Text + usersRoleDesc = + cs $ + "The conversation permissions the users \ + \added in this request should have. \ + \Optional, defaults to '" + <> show roleNameWireAdmin + <> "' if unset." + +newtype ConvTeamInfo = ConvTeamInfo + { cnvTeamId :: TeamId } deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform ConvTeamInfo) @@ -772,13 +700,15 @@ instance ToSchema ConvTeamInfo where (description ?~ "Team information") $ ConvTeamInfo <$> cnvTeamId .= field "teamid" schema - <*> cnvManaged + <* const () .= ( fieldWithDocModifier "managed" - (description ?~ "Whether this is a managed team conversation") - schema - <|> pure False + (description ?~ "(Not parsed any more) Whether this is a managed team conversation") + (c (False :: Bool)) ) + where + c :: ToJSON a => a -> ValueSchema SwaggerDoc () + c val = mkSchema mempty (const (pure ())) (const (pure (toJSON val))) modelTeamInfo :: Doc.Model modelTeamInfo = Doc.defineModel "TeamInfo" $ do diff --git a/libs/wire-api/src/Wire/API/ErrorDescription.hs b/libs/wire-api/src/Wire/API/ErrorDescription.hs index 4cd0b9be10e..ef4d77d4b6e 100644 --- a/libs/wire-api/src/Wire/API/ErrorDescription.hs +++ b/libs/wire-api/src/Wire/API/ErrorDescription.hs @@ -211,11 +211,15 @@ type ConvNotFound = ErrorDescription 404 "no-conversation" "Conversation not fou type ConvMemberNotFound = ErrorDescription 404 "no-conversation-member" "Conversation member not found" +type TooManyMembers = ErrorDescription 403 "too-many-members" "Maximum number of members per conversation reached." + type UnknownClient = ErrorDescription 403 "unknown-client" "Unknown Client" type ClientNotFound = ErrorDescription 404 "client-not-found" "Client not found" -type TeamNotFound = ErrorDescription 404 "no-team" "team not found" +type TeamNotFound = ErrorDescription 404 "no-team" "Team not found" + +type NonBindingTeam = ErrorDescription 404 "non-binding-team" "Not member of a binding team" type NotConnected = ErrorDescription 403 "not-connected" "Users are not connected" @@ -329,8 +333,46 @@ type InvalidOpConnectConv = InvalidOp "invalid operation for connect conversatio type InvalidTargetAccess = InvalidOp "invalid target access" +type InvalidAccessOp = InvalidOp "invalid operation for conversation without 'code' access" + type AssetTooLarge = ErrorDescription 413 "client-error" "Asset too large" type InvalidLength = ErrorDescription 400 "invalid-length" "Invalid content length" type AssetNotFound = ErrorDescription 404 "not-found" "Asset not found" + +type NameManagedByScim = ErrorDescription 403 "managed-by-scim" "Updating name is not allowed, because it is managed by SCIM" + +type HandleManagedByScim = ErrorDescription 403 "managed-by-scim" "Updating handle is not allowed, because it is managed by SCIM" + +type InvalidPhone = ErrorDescription 400 "invalid-phone" "Invalid mobile phone number" + +type UserKeyExists = ErrorDescription 409 "key-exists" "The given e-mail address or phone number is in use." + +type BlacklistedPhone = ErrorDescription 403 "blacklisted-phone" "The given phone number has been blacklisted due to suspected abuse or a complaint." + +type LastIdentity = ErrorDescription 403 "last-identity" "The last user identity (email or phone number) cannot be removed." + +type NoPassword = ErrorDescription 403 "no-password" "The user has no password." + +type ChangePasswordMustDiffer = ErrorDescription 409 "password-must-differ" "For password change, new and old password must be different." + +type HandleExists = ErrorDescription 409 "handle-exists" "The given handle is already taken." + +type InvalidHandle = ErrorDescription 400 "invalid-handle" "The given handle is invalid." + +type PresenceNotRegistered = ErrorDescription 404 "not-found" "presence not registered" + +type ClientGone = ErrorDescription 410 "general" "client gone" + +type BroadcastLimitExceeded = + ErrorDescription + 400 + "too-many-users-to-broadcast" + "Too many users to fan out the broadcast event to." + +type InvalidAction = ErrorDescription 403 "invalid-actions" "The specified actions are invalid." + +type PasswordAuthenticationFailed = ErrorDescription 403 "password-authentication-failed" "Password authentication failed." + +type CodeAuthenticationFailed = ErrorDescription 403 "code-authentication-failed" "Code authentication failed." diff --git a/libs/wire-api/src/Wire/API/Event/Conversation.hs b/libs/wire-api/src/Wire/API/Event/Conversation.hs index fe6331e58b0..b87b9369ee0 100644 --- a/libs/wire-api/src/Wire/API/Event/Conversation.hs +++ b/libs/wire-api/src/Wire/API/Event/Conversation.hs @@ -23,6 +23,7 @@ module Wire.API.Event.Conversation Event (..), EventType (..), EventData (..), + AddCodeResult (..), -- * Event lenses _EdMembersJoin, @@ -450,6 +451,10 @@ modelMemberUpdateData = Doc.defineModel "MemberUpdateData" $ do Doc.description "Name of the conversation role to update to" Doc.optional +data AddCodeResult + = CodeAdded Event + | CodeAlreadyExisted ConversationCode + data OtrMessage = OtrMessage { otrSender :: ClientId, otrRecipient :: ClientId, diff --git a/libs/wire-api/src/Wire/API/Event/FeatureConfig.hs b/libs/wire-api/src/Wire/API/Event/FeatureConfig.hs index f9bfca464f3..6a3a9752fde 100644 --- a/libs/wire-api/src/Wire/API/Event/FeatureConfig.hs +++ b/libs/wire-api/src/Wire/API/Event/FeatureConfig.hs @@ -77,6 +77,7 @@ taggedEventDataSchema = TeamFeatureConferenceCalling -> tag _EdFeatureWithoutConfigChanged (unnamed schema) TeamFeatureSelfDeletingMessages -> tag _EdFeatureSelfDeletingMessagesChanged (unnamed schema) TeamFeatureGuestLinks -> tag _EdFeatureWithoutConfigAndLockStatusChanged (unnamed schema) + TeamFeatureSndFactorPasswordChallenge -> tag _EdFeatureWithoutConfigAndLockStatusChanged (unnamed schema) eventObjectSchema :: ObjectSchema SwaggerDoc Event eventObjectSchema = diff --git a/libs/wire-api/src/Wire/API/RawJson.hs b/libs/wire-api/src/Wire/API/RawJson.hs new file mode 100644 index 00000000000..295202c1ed0 --- /dev/null +++ b/libs/wire-api/src/Wire/API/RawJson.hs @@ -0,0 +1,29 @@ +-- 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.RawJson where + +import Imports +import Servant + +-- | Wrap json content as plain 'LByteString' +-- This type is intented to be used to receive json content as 'LByteString'. +-- Warning: There is no validation of the json content. It may be any string. +newtype RawJson = RawJson {rawJsonBytes :: LByteString} + +instance {-# OVERLAPPING #-} MimeUnrender JSON RawJson where + mimeUnrender _ = pure . RawJson diff --git a/libs/wire-api/src/Wire/API/Routes/Internal/Cargohold.hs b/libs/wire-api/src/Wire/API/Routes/Internal/Cargohold.hs new file mode 100644 index 00000000000..5900b31d96c --- /dev/null +++ b/libs/wire-api/src/Wire/API/Routes/Internal/Cargohold.hs @@ -0,0 +1,24 @@ +-- 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.Cargohold where + +import Servant +import Wire.API.Routes.MultiVerb + +type InternalAPI = + "i" :> "status" :> MultiVerb 'GET '() '[RespondEmpty 200 "OK"] () diff --git a/libs/wire-api/src/Wire/API/Routes/Internal/LegalHold.hs b/libs/wire-api/src/Wire/API/Routes/Internal/LegalHold.hs new file mode 100644 index 00000000000..74f92ab591c --- /dev/null +++ b/libs/wire-api/src/Wire/API/Routes/Internal/LegalHold.hs @@ -0,0 +1,29 @@ +-- 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.LegalHold where + +import Data.Id +import Servant.API hiding (Header) +import Wire.API.Team.Feature + +type InternalLegalHoldAPI = + "i" :> "teams" :> Capture "tid" TeamId :> "legalhold" + :> Get '[JSON] (TeamFeatureStatus 'WithLockStatus 'TeamFeatureLegalHold) + :<|> "i" :> "teams" :> Capture "tid" TeamId :> "legalhold" + :> ReqBody '[JSON] (TeamFeatureStatus 'WithoutLockStatus 'TeamFeatureLegalHold) + :> Put '[] NoContent diff --git a/libs/wire-api/src/Wire/API/Routes/MultiVerb.hs b/libs/wire-api/src/Wire/API/Routes/MultiVerb.hs index 590c583e32c..80d4a70b4d6 100644 --- a/libs/wire-api/src/Wire/API/Routes/MultiVerb.hs +++ b/libs/wire-api/src/Wire/API/Routes/MultiVerb.hs @@ -31,6 +31,8 @@ module Wire.API.Routes.MultiVerb AsUnion (..), eitherToUnion, eitherFromUnion, + maybeToUnion, + maybeFromUnion, AsConstructor (..), GenericAsConstructor (..), GenericAsUnion (..), @@ -50,6 +52,7 @@ import Data.ByteString.Builder import qualified Data.ByteString.Lazy as LBS import qualified Data.CaseInsensitive as CI import Data.Containers.ListUtils +import Data.Either.Combinators (leftToMaybe) import Data.HashMap.Strict.InsOrd (InsOrdHashMap) import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap import Data.Metrics.Servant @@ -193,7 +196,7 @@ simpleResponseSwagger = do & S.schema ?~ ref instance - (KnownStatus s, KnownSymbol desc, S.ToSchema a) => + (KnownSymbol desc, S.ToSchema a) => IsSwaggerResponse (Respond s desc a) where responseSwagger = simpleResponseSwagger @a @desc @@ -241,11 +244,20 @@ instance KnownStatus s => IsResponse cs (RespondAs '() s desc ()) where guard (responseStatusCode output == statusVal (Proxy @s)) instance - (KnownStatus s, KnownSymbol desc, S.ToSchema a) => - IsSwaggerResponse (RespondAs ct s desc a) + (KnownSymbol desc, S.ToSchema a) => + IsSwaggerResponse (RespondAs (ct :: *) s desc a) where responseSwagger = simpleResponseSwagger @a @desc +instance + (KnownSymbol desc) => + IsSwaggerResponse (RespondEmpty s desc) + where + responseSwagger = + pure $ + mempty + & S.description .~ Text.pack (symbolVal (Proxy @desc)) + type instance ResponseType (RespondStreaming s desc framing ct) = SourceIO ByteString instance @@ -529,6 +541,21 @@ instance EitherFromUnion as bs => EitherFromUnion (a ': as) bs where eitherFromUnion f _ (Z x) = Left (f (Z x)) eitherFromUnion f g (S x) = eitherFromUnion @as @bs (f . S) g x +maybeToUnion :: + forall as a. + (InjectAfter as '[()], InjectBefore as '[()]) => + (a -> Union as) -> + (Maybe a -> Union (as .++ '[()])) +maybeToUnion f (Just a) = injectBefore @as @'[()] (f a) +maybeToUnion _ Nothing = injectAfter @as @'[()] (Z (I ())) + +maybeFromUnion :: + forall as a. + EitherFromUnion as '[()] => + (Union as -> a) -> + (Union (as .++ '[()]) -> Maybe a) +maybeFromUnion f = leftToMaybe . eitherFromUnion @as @'[()] f (const (Z (I ()))) + -- | This class can be instantiated to get automatic derivation of 'AsUnion' -- instances via 'GenericAsUnion'. The idea is that one has to make sure that for -- each response @r@ in a 'MultiVerb' endpoint, there is an instance of @@ -607,6 +634,23 @@ instance toUnion (GenericAsUnion x) = fromSOP @xss @rs (GSOP.from x) fromUnion = GenericAsUnion . GSOP.to . toSOP @xss @rs +-- | A handler for a pair of empty responses can be implemented simply by +-- returning a boolean value. The convention is that the "failure" case, normally +-- represented by 'False', corresponds to the /first/ response. +instance + AsUnion + '[ RespondEmpty s1 desc1, + RespondEmpty s2 desc2 + ] + Bool + where + toUnion False = Z (I ()) + toUnion True = S (Z (I ())) + + fromUnion (Z (I ())) = False + fromUnion (S (Z (I ()))) = True + fromUnion (S (S x)) = case x of + -- | A handler for a pair of responses where the first is empty can be -- implemented simply by returning a 'Maybe' value. The convention is that the -- "failure" case, normally represented by 'Nothing', corresponds to the /first/ diff --git a/libs/wire-api/src/Wire/API/Routes/Public.hs b/libs/wire-api/src/Wire/API/Routes/Public.hs index 569e3aba901..0fe291c1f2e 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public.hs @@ -26,6 +26,7 @@ module Wire.API.Routes.Public ZOptUser, ZOptConn, ZBot, + ZConversation, ZProvider, -- * Swagger combinators @@ -75,6 +76,7 @@ data ZType | -- | Get a 'ConnId' from the Z-Conn header ZAuthConn | ZAuthBot + | ZAuthConv | ZAuthProvider class @@ -122,6 +124,13 @@ instance IsZType 'ZAuthBot ctx where qualifyZParam _ = id +instance IsZType 'ZAuthConv ctx where + type ZHeader 'ZAuthConv = "Z-Conversation" + type ZParam 'ZAuthConv = ConvId + type ZQualifiedParam 'ZAuthConv = ConvId + + qualifyZParam _ = id + instance HasTokenType 'ZAuthBot where tokenType = Just "bot" @@ -153,6 +162,8 @@ type ZConn = ZAuthServant 'ZAuthConn InternalAuthDefOpts type ZBot = ZAuthServant 'ZAuthBot InternalAuthDefOpts +type ZConversation = ZAuthServant 'ZAuthConv InternalAuthDefOpts + type ZProvider = ZAuthServant 'ZAuthProvider InternalAuthDefOpts type ZOptUser = ZAuthServant 'ZAuthUser '[Servant.Optional, Servant.Strict] 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 c800e770072..9859e947211 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs @@ -1,4 +1,3 @@ -{-# LANGUAGE DerivingVia #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} -- This file is part of the Wire Server implementation. @@ -33,12 +32,12 @@ import Data.Swagger hiding (Contact, Header) import Imports hiding (head) import Servant (JSON) import Servant hiding (Handler, JSON, addHeader, respond) -import Servant.API.Generic import Servant.Swagger (HasSwagger (toSwagger)) import Servant.Swagger.Internal.Orphans () import Wire.API.Connection import Wire.API.ErrorDescription import Wire.API.Routes.MultiVerb +import Wire.API.Routes.Named import Wire.API.Routes.Public (ZConn, ZUser) import Wire.API.Routes.Public.Util import Wire.API.Routes.QualifiedCapture @@ -89,219 +88,301 @@ instance AsUnion DeleteSelfResponses (Maybe Timeout) where type ConnectionUpdateResponses = UpdateResponses "Connection unchanged" "Connection updated" UserConnection -data Api routes = Api - { -- See Note [ephemeral user sideeffect] - getUserUnqualified :: - routes - :- Summary "Get a user by UserId (deprecated)" +type UserAPI = + -- See Note [ephemeral user sideeffect] + Named + "get-user-unqualified" + ( Summary "Get a user by UserId (deprecated)" :> ZUser :> "users" :> CaptureUserId "uid" - :> GetUserVerb, + :> GetUserVerb + ) + :<|> -- See Note [ephemeral user sideeffect] - getUserQualified :: - routes - :- Summary "Get a user by Domain and UserId" - :> ZUser - :> "users" - :> QualifiedCaptureUserId "uid" - :> GetUserVerb, - getSelf :: - routes - :- Summary "Get your own profile" + Named + "get-user-qualified" + ( Summary "Get a user by Domain and UserId" + :> ZUser + :> "users" + :> QualifiedCaptureUserId "uid" + :> GetUserVerb + ) + :<|> Named + "update-user-email" + ( Summary "Resend email address validation email." + :> Description "If the user has a pending email validation, the validation email will be resent." + :> ZUser + :> "users" + :> CaptureUserId "uid" + :> "email" + :> ReqBody '[JSON] EmailUpdate + :> Put '[JSON] () + ) + :<|> Named + "get-handle-info-unqualified" + ( Summary "(deprecated, use /search/contacts) Get information on a user handle" + :> ZUser + :> "users" + :> "handles" + :> Capture' '[Description "The user handle"] "handle" Handle + :> MultiVerb + 'GET + '[JSON] + '[ HandleNotFound, + Respond 200 "User found" UserHandleInfo + ] + (Maybe UserHandleInfo) + ) + :<|> Named + "get-user-by-handle-qualified" + ( Summary "(deprecated, use /search/contacts) Get information on a user handle" + :> ZUser + :> "users" + :> "by-handle" + :> QualifiedCapture' '[Description "The user handle"] "handle" Handle + :> MultiVerb + 'GET + '[JSON] + '[ HandleNotFound, + Respond 200 "User found" UserProfile + ] + (Maybe UserProfile) + ) + :<|> + -- See Note [ephemeral user sideeffect] + Named + "list-users-by-unqualified-ids-or-handles" + ( Summary "List users (deprecated)" + :> Description "The 'ids' and 'handles' parameters are mutually exclusive." + :> ZUser + :> "users" + :> QueryParam' [Optional, Strict, Description "User IDs of users to fetch"] "ids" (CommaSeparatedList UserId) + :> 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] + ) + :<|> + -- See Note [ephemeral user sideeffect] + Named + "list-users-by-ids-or-handles" + ( Summary "List users" + :> Description "The 'qualified_ids' and 'qualified_handles' parameters are mutually exclusive." + :> ZUser + :> "list-users" + :> ReqBody '[JSON] ListUsersQuery + :> Post '[JSON] [UserProfile] + ) + :<|> Named + "send-verification-code" + ( Summary "Send a verification code to a given email address." + :> "verification-code" + :> "send" + :> ReqBody '[JSON] SendVerificationCode + :> MultiVerb 'POST '[JSON] '[RespondEmpty 200 "Verification code sent."] () + ) + +type SelfAPI = + Named + "get-self" + ( Summary "Get your own profile" :> ZUser :> "self" - :> Get '[JSON] SelfProfile, + :> Get '[JSON] SelfProfile + ) + :<|> -- This endpoint can lead to the following events being sent: -- - UserDeleted event to contacts of self -- - MemberLeave event to members for all conversations the user was in (via galley) - deleteSelf :: - routes - :- Summary "Initiate account deletion." - :> Description - "if the account has a verified identity, a verification \ - \code is sent and needs to be confirmed to authorise the \ - \deletion. if the account has no verified identity but a \ - \password, it must be provided. if password is correct, or if neither \ - \a verified identity nor a password exists, account deletion \ - \is scheduled immediately." - :> CanThrow InvalidUser - :> CanThrow InvalidCode - :> CanThrow BadCredentials - :> CanThrow MissingAuth - :> CanThrow DeleteCodePending - :> CanThrow OwnerDeletingSelf - :> ZUser - :> "self" - :> ReqBody '[JSON] DeleteUser - :> MultiVerb 'DELETE '[JSON] DeleteSelfResponses (Maybe Timeout), - updateUserEmail :: - routes - :- Summary "Resend email address validation email." - :> Description "If the user has a pending email validation, the validation email will be resent." - :> ZUser - :> "users" - :> CaptureUserId "uid" - :> "email" - :> ReqBody '[JSON] EmailUpdate - :> Put '[JSON] (), - getHandleInfoUnqualified :: - routes - :- Summary "(deprecated, use /search/contacts) Get information on a user handle" - :> ZUser - :> "users" - :> "handles" - :> Capture' '[Description "The user handle"] "handle" Handle - :> MultiVerb - 'GET - '[JSON] - '[ HandleNotFound, - Respond 200 "User found" UserHandleInfo - ] - (Maybe UserHandleInfo), - getUserByHandleQualified :: - routes - :- Summary "(deprecated, use /search/contacts) Get information on a user handle" - :> ZUser - :> "users" - :> "by-handle" - :> QualifiedCapture' '[Description "The user handle"] "handle" Handle - :> MultiVerb - 'GET - '[JSON] - '[ HandleNotFound, - Respond 200 "User found" UserProfile - ] - (Maybe UserProfile), - -- See Note [ephemeral user sideeffect] - listUsersByUnqualifiedIdsOrHandles :: - routes - :- Summary "List users (deprecated)" - :> Description "The 'ids' and 'handles' parameters are mutually exclusive." - :> ZUser - :> "users" - :> QueryParam' [Optional, Strict, Description "User IDs of users to fetch"] "ids" (CommaSeparatedList UserId) - :> 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], - -- See Note [ephemeral user sideeffect] - listUsersByIdsOrHandles :: - routes - :- Summary "List users" - :> Description "The 'qualified_ids' and 'qualified_handles' parameters are mutually exclusive." - :> ZUser - :> "list-users" - :> ReqBody '[JSON] ListUsersQuery - :> Post '[JSON] [UserProfile], - getUserClientsUnqualified :: - routes - :- Summary "Get all of a user's clients (deprecated)." - :> "users" - :> CaptureUserId "uid" - :> "clients" - :> Get '[JSON] [PubClient], - getUserClientsQualified :: - routes - :- Summary "Get all of a user's clients." - :> "users" - :> QualifiedCaptureUserId "uid" - :> "clients" - :> Get '[JSON] [PubClient], - getUserClientUnqualified :: - routes - :- Summary "Get a specific client of a user (deprecated)." - :> "users" - :> CaptureUserId "uid" - :> "clients" - :> CaptureClientId "client" - :> Get '[JSON] PubClient, - getUserClientQualified :: - routes - :- Summary "Get a specific client of a user." - :> "users" - :> QualifiedCaptureUserId "uid" - :> "clients" - :> CaptureClientId "client" - :> Get '[JSON] PubClient, - listClientsBulk :: - routes - :- Summary "List all clients for a set of user ids (deprecated, use /users/list-clients/v2)" - :> ZUser - :> "users" - :> "list-clients" - :> ReqBody '[JSON] (Range 1 MaxUsersForListClientsBulk [Qualified UserId]) - :> Post '[JSON] (QualifiedUserMap (Set PubClient)), - listClientsBulkV2 :: - routes - :- Summary "List all clients for a set of user ids" - :> ZUser - :> "users" - :> "list-clients" - :> "v2" - :> ReqBody '[JSON] (LimitedQualifiedUserIdList MaxUsersForListClientsBulk) - :> Post '[JSON] (WrappedQualifiedUserMap (Set PubClient)), - getUsersPrekeysClientUnqualified :: - routes - :- Summary "(deprecated) Get a prekey for a specific client of a user." + Named + "delete-self" + ( Summary "Initiate account deletion." + :> Description + "if the account has a verified identity, a verification \ + \code is sent and needs to be confirmed to authorise the \ + \deletion. if the account has no verified identity but a \ + \password, it must be provided. if password is correct, or if neither \ + \a verified identity nor a password exists, account deletion \ + \is scheduled immediately." + :> CanThrow InvalidUser + :> CanThrow InvalidCode + :> CanThrow BadCredentials + :> CanThrow MissingAuth + :> CanThrow DeleteCodePending + :> CanThrow OwnerDeletingSelf + :> ZUser + :> "self" + :> ReqBody '[JSON] DeleteUser + :> MultiVerb 'DELETE '[JSON] DeleteSelfResponses (Maybe Timeout) + ) + :<|> + -- This endpoint can lead to the following events being sent: + -- - UserUpdated event to contacts of self + Named + "put-self" + ( Summary "Update your profile." + :> ZUser + :> ZConn + :> "self" + :> ReqBody '[JSON] UserUpdate + :> MultiVerb 'PUT '[JSON] PutSelfResponses (Maybe UpdateProfileError) + ) + :<|> Named + "change-phone" + ( Summary "Change your phone number." + :> ZUser + :> ZConn + :> "self" + :> "phone" + :> ReqBody '[JSON] PhoneUpdate + :> MultiVerb 'PUT '[JSON] ChangePhoneResponses (Maybe ChangePhoneError) + ) + :<|> + -- This endpoint can lead to the following events being sent: + -- - UserIdentityRemoved event to self + Named + "remove-phone" + ( Summary "Remove your phone number." + :> Description + "Your phone number can only be removed if you also have an \ + \email address and a password." + :> ZUser + :> ZConn + :> "self" + :> "phone" + :> MultiVerb 'DELETE '[JSON] RemoveIdentityResponses (Maybe RemoveIdentityError) + ) + :<|> + -- This endpoint can lead to the following events being sent: + -- - UserIdentityRemoved event to self + Named + "remove-email" + ( Summary "Remove your email address." + :> Description + "Your email address can only be removed if you also have a \ + \phone number." + :> ZUser + :> ZConn + :> "self" + :> "email" + :> MultiVerb 'DELETE '[JSON] RemoveIdentityResponses (Maybe RemoveIdentityError) + ) + :<|> Named + "check-password-exists" + ( Summary "Check that your password is set." + :> ZUser + :> "self" + :> "password" + :> MultiVerb + 'HEAD + '() + '[ RespondEmpty 404 "Password is not set", + RespondEmpty 200 "Password is set" + ] + Bool + ) + :<|> Named + "change-password" + ( Summary "Change your password." + :> ZUser + :> "self" + :> "password" + :> ReqBody '[JSON] PasswordChange + :> MultiVerb 'PUT '[JSON] ChangePasswordResponses (Maybe ChangePasswordError) + ) + :<|> Named + "change-locale" + ( Summary "Change your locale." + :> ZUser + :> ZConn + :> "self" + :> "locale" + :> ReqBody '[JSON] LocaleUpdate + :> MultiVerb 'PUT '[JSON] '[RespondEmpty 200 "Local Changed"] () + ) + :<|> Named + "change-handle" + ( Summary "Change your handle." + :> ZUser + :> ZConn + :> "self" + :> "handle" + :> ReqBody '[JSON] HandleUpdate + :> MultiVerb 'PUT '[JSON] ChangeHandleResponses (Maybe ChangeHandleError) + ) + +type PrekeyAPI = + Named + "get-users-prekeys-client-unqualified" + ( Summary "(deprecated) Get a prekey for a specific client of a user." :> ZUser :> "users" :> CaptureUserId "uid" :> "prekeys" :> CaptureClientId "client" - :> Get '[JSON] ClientPrekey, - getUsersPrekeysClientQualified :: - routes - :- Summary "Get a prekey for a specific client of a user." - :> ZUser - :> "users" - :> QualifiedCaptureUserId "uid" - :> "prekeys" - :> CaptureClientId "client" - :> Get '[JSON] ClientPrekey, - getUsersPrekeyBundleUnqualified :: - routes - :- Summary "(deprecated) Get a prekey for each client of a user." - :> ZUser - :> "users" - :> CaptureUserId "uid" - :> "prekeys" - :> Get '[JSON] PrekeyBundle, - getUsersPrekeyBundleQualified :: - routes - :- Summary "Get a prekey for each client of a user." - :> ZUser - :> "users" - :> QualifiedCaptureUserId "uid" - :> "prekeys" - :> Get '[JSON] PrekeyBundle, - getMultiUserPrekeyBundleUnqualified :: - routes - :- Summary - "(deprecated) Given a 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." - :> ZUser - :> "users" - :> "prekeys" - :> ReqBody '[JSON] UserClients - :> Post '[JSON] UserClientPrekeyMap, - getMultiUserPrekeyBundleQualified :: - routes - :- 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." - :> ZUser - :> "users" - :> "list-prekeys" - :> ReqBody '[JSON] QualifiedUserClients - :> Post '[JSON] QualifiedUserClientPrekeyMap, - -- User Client API ---------------------------------------------------- + :> Get '[JSON] ClientPrekey + ) + :<|> Named + "get-users-prekeys-client-qualified" + ( Summary "Get a prekey for a specific client of a user." + :> ZUser + :> "users" + :> QualifiedCaptureUserId "uid" + :> "prekeys" + :> CaptureClientId "client" + :> Get '[JSON] ClientPrekey + ) + :<|> Named + "get-users-prekey-bundle-unqualified" + ( Summary "(deprecated) Get a prekey for each client of a user." + :> ZUser + :> "users" + :> CaptureUserId "uid" + :> "prekeys" + :> Get '[JSON] PrekeyBundle + ) + :<|> Named + "get-users-prekey-bundle-qualified" + ( Summary "Get a prekey for each client of a user." + :> ZUser + :> "users" + :> QualifiedCaptureUserId "uid" + :> "prekeys" + :> Get '[JSON] PrekeyBundle + ) + :<|> Named + "get-multi-user-prekey-bundle-unqualified" + ( Summary + "(deprecated) Given a 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." + :> ZUser + :> "users" + :> "prekeys" + :> ReqBody '[JSON] UserClients + :> Post '[JSON] UserClientPrekeyMap + ) + :<|> 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." + :> ZUser + :> "users" + :> "list-prekeys" + :> ReqBody '[JSON] QualifiedUserClients + :> Post '[JSON] QualifiedUserClientPrekeyMap + ) - -- This endpoint can lead to the following events being sent: - -- - ClientAdded event to self - -- - ClientRemoved event to self, if removing old clients due to max number - addClient :: - routes :- Summary "Register a new client" +type UserClientAPI = + -- User Client API ---------------------------------------------------- + + -- This endpoint can lead to the following events being sent: + -- - ClientAdded event to self + -- - ClientRemoved event to self, if removing old clients due to max number + Named + "add-client" + ( Summary "Register a new client" :> CanThrow TooManyClients :> CanThrow MissingAuth :> CanThrow MalformedPrekeys @@ -310,66 +391,138 @@ data Api routes = Api :> "clients" :> Header "X-Forwarded-For" IpAddr :> ReqBody '[JSON] NewClient - :> Verb 'POST 201 '[JSON] NewClientResponse, - updateClient :: - routes :- Summary "Update a registered client" - :> CanThrow MalformedPrekeys - :> ZUser - :> "clients" - :> CaptureClientId "client" - :> ReqBody '[JSON] UpdateClient - :> MultiVerb 'PUT '[JSON] '[RespondEmpty 200 "Client updated"] (), + :> Verb 'POST 201 '[JSON] NewClientResponse + ) + :<|> Named + "update-client" + ( Summary "Update a registered client" + :> CanThrow MalformedPrekeys + :> ZUser + :> "clients" + :> CaptureClientId "client" + :> ReqBody '[JSON] UpdateClient + :> MultiVerb 'PUT '[JSON] '[RespondEmpty 200 "Client updated"] () + ) + :<|> -- This endpoint can lead to the following events being sent: -- - ClientRemoved event to self - deleteClient :: - routes :- Summary "Delete an existing client" - :> ZUser - :> ZConn - :> "clients" - :> CaptureClientId "client" - :> ReqBody '[JSON] RmClient - :> MultiVerb 'DELETE '[JSON] '[RespondEmpty 200 "Client deleted"] (), - listClients :: - routes :- Summary "List the registered clients" - :> ZUser - :> "clients" - :> Get '[JSON] [Client], - getClient :: - routes :- Summary "Get a registered client by ID" - :> ZUser - :> "clients" - :> CaptureClientId "client" - :> MultiVerb - 'GET - '[JSON] - '[ EmptyErrorForLegacyReasons 404 "Client not found", - Respond 200 "Client found" Client - ] - (Maybe Client), - getClientCapabilities :: - routes :- Summary "Read back what the client has been posting about itself" - :> ZUser - :> "clients" - :> CaptureClientId "client" - :> "capabilities" - :> Get '[JSON] ClientCapabilityList, - getClientPrekeys :: - routes :- Summary "List the remaining prekey IDs of a client" - :> ZUser + Named + "delete-client" + ( Summary "Delete an existing client" + :> ZUser + :> ZConn + :> "clients" + :> CaptureClientId "client" + :> ReqBody '[JSON] RmClient + :> MultiVerb 'DELETE '[JSON] '[RespondEmpty 200 "Client deleted"] () + ) + :<|> Named + "list-clients" + ( Summary "List the registered clients" + :> ZUser + :> "clients" + :> Get '[JSON] [Client] + ) + :<|> Named + "get-client" + ( Summary "Get a registered client by ID" + :> ZUser + :> "clients" + :> CaptureClientId "client" + :> MultiVerb + 'GET + '[JSON] + '[ EmptyErrorForLegacyReasons 404 "Client not found", + Respond 200 "Client found" Client + ] + (Maybe Client) + ) + :<|> Named + "get-client-capabilities" + ( Summary "Read back what the client has been posting about itself" + :> ZUser + :> "clients" + :> CaptureClientId "client" + :> "capabilities" + :> Get '[JSON] ClientCapabilityList + ) + :<|> Named + "get-client-prekeys" + ( Summary "List the remaining prekey IDs of a client" + :> ZUser + :> "clients" + :> CaptureClientId "client" + :> "prekeys" + :> Get '[JSON] [PrekeyId] + ) + +type ClientAPI = + Named + "get-user-clients-unqualified" + ( Summary "Get all of a user's clients (deprecated)." + :> "users" + :> CaptureUserId "uid" :> "clients" - :> CaptureClientId "client" - :> "prekeys" - :> Get '[JSON] [PrekeyId], - -- Connection API ----------------------------------------------------- - -- - -- This endpoint can lead to the following events being sent: - -- - ConnectionUpdated event to self and other, if any side's connection state changes - -- - MemberJoin event to self and other, if joining an existing connect conversation (via galley) - -- - ConvCreate event to self, if creating a connect conversation (via galley) - -- - ConvConnect event to self, in some cases (via galley), - -- for details see 'Galley.API.Create.createConnectConversation' - createConnectionUnqualified :: - routes :- Summary "Create a connection to another user. (deprecated)" + :> Get '[JSON] [PubClient] + ) + :<|> Named + "get-user-clients-qualified" + ( Summary "Get all of a user's clients." + :> "users" + :> QualifiedCaptureUserId "uid" + :> "clients" + :> Get '[JSON] [PubClient] + ) + :<|> Named + "get-user-client-unqualified" + ( Summary "Get a specific client of a user (deprecated)." + :> "users" + :> CaptureUserId "uid" + :> "clients" + :> CaptureClientId "client" + :> Get '[JSON] PubClient + ) + :<|> Named + "get-user-client-qualified" + ( Summary "Get a specific client of a user." + :> "users" + :> QualifiedCaptureUserId "uid" + :> "clients" + :> CaptureClientId "client" + :> Get '[JSON] PubClient + ) + :<|> Named + "list-clients-bulk" + ( Summary "List all clients for a set of user ids (deprecated, use /users/list-clients/v2)" + :> ZUser + :> "users" + :> "list-clients" + :> ReqBody '[JSON] (Range 1 MaxUsersForListClientsBulk [Qualified UserId]) + :> Post '[JSON] (QualifiedUserMap (Set PubClient)) + ) + :<|> Named + "list-clients-bulk-v2" + ( Summary "List all clients for a set of user ids" + :> ZUser + :> "users" + :> "list-clients" + :> "v2" + :> ReqBody '[JSON] (LimitedQualifiedUserIdList MaxUsersForListClientsBulk) + :> Post '[JSON] (WrappedQualifiedUserMap (Set PubClient)) + ) + +-- Connection API ----------------------------------------------------- +-- +-- This endpoint can lead to the following events being sent: +-- - ConnectionUpdated event to self and other, if any side's connection state changes +-- - MemberJoin event to self and other, if joining an existing connect conversation (via galley) +-- - ConvCreate event to self, if creating a connect conversation (via galley) +-- - ConvConnect event to self, in some cases (via galley), +-- for details see 'Galley.API.Create.createConnectConversation' +type ConnectionAPI = + Named + "create-connection-unqualified" + ( Summary "Create a connection to another user (deprecated)" :> CanThrow MissingLegalholdConsent :> CanThrow InvalidUser :> CanThrow ConnectionLimitReached @@ -387,125 +540,142 @@ data Api routes = Api 'POST '[JSON] (ResponsesForExistedCreated "Connection existed" "Connection was created" UserConnection) - (ResponseForExistedCreated UserConnection), - createConnection :: - routes :- Summary "Create a connection to another user" - :> CanThrow MissingLegalholdConsent - :> CanThrow InvalidUser - :> CanThrow ConnectionLimitReached - :> CanThrow NoIdentity - -- Config value 'setUserMaxConnections' value in production/by default - -- is currently 1000 and has not changed in the last few years. - -- While it would be more correct to use the config value here, that - -- might not be time well spent. - :> Description "You can have no more than 1000 connections in accepted or sent state" - :> ZUser - :> ZConn - :> "connections" - :> QualifiedCaptureUserId "uid" - :> MultiVerb - 'POST - '[JSON] - (ResponsesForExistedCreated "Connection existed" "Connection was created" UserConnection) - (ResponseForExistedCreated UserConnection), - listLocalConnections :: - routes :- Summary "List the local connections to other users. (deprecated)" - :> ZUser - :> "connections" - :> QueryParam' '[Optional, Strict, Description "User ID to start from when paginating"] "start" UserId - :> QueryParam' '[Optional, Strict, Description "Number of results to return (default 100, max 500)"] "size" (Range 1 500 Int32) - :> Get '[JSON] UserConnectionList, - listConnections :: - routes :- Summary "List the connections to other users, including remote users." - :> ZUser - :> "list-connections" - :> ReqBody '[JSON] ListConnectionsRequestPaginated - :> Post '[JSON] ConnectionsPage, - getConnectionUnqualified :: - routes :- Summary "Get an existing connection to another user. (deprecated)" - :> ZUser - :> "connections" - :> CaptureUserId "uid" - :> MultiVerb - 'GET - '[JSON] - '[ EmptyErrorForLegacyReasons 404 "Connection not found", - Respond 200 "Connection found" UserConnection - ] - (Maybe UserConnection), - getConnection :: - routes :- Summary "Get an existing connection to another user (local or remote)." - :> ZUser - :> "connections" - :> QualifiedCaptureUserId "uid" - :> MultiVerb - 'GET - '[JSON] - '[ EmptyErrorForLegacyReasons 404 "Connection not found", - Respond 200 "Connection found" UserConnection - ] - (Maybe UserConnection), + (ResponseForExistedCreated UserConnection) + ) + :<|> Named + "create-connection" + ( Summary "Create a connection to another user" + :> CanThrow MissingLegalholdConsent + :> CanThrow InvalidUser + :> CanThrow ConnectionLimitReached + :> CanThrow NoIdentity + -- Config value 'setUserMaxConnections' value in production/by default + -- is currently 1000 and has not changed in the last few years. + -- While it would be more correct to use the config value here, that + -- might not be time well spent. + :> Description "You can have no more than 1000 connections in accepted or sent state" + :> ZUser + :> ZConn + :> "connections" + :> QualifiedCaptureUserId "uid" + :> MultiVerb + 'POST + '[JSON] + (ResponsesForExistedCreated "Connection existed" "Connection was created" UserConnection) + (ResponseForExistedCreated UserConnection) + ) + :<|> Named + "list-local-connections" + ( Summary "List the local connections to other users (deprecated)" + :> ZUser + :> "connections" + :> QueryParam' '[Optional, Strict, Description "User ID to start from when paginating"] "start" UserId + :> QueryParam' '[Optional, Strict, Description "Number of results to return (default 100, max 500)"] "size" (Range 1 500 Int32) + :> Get '[JSON] UserConnectionList + ) + :<|> Named + "list-connections" + ( Summary "List the connections to other users, including remote users" + :> ZUser + :> "list-connections" + :> ReqBody '[JSON] ListConnectionsRequestPaginated + :> Post '[JSON] ConnectionsPage + ) + :<|> Named + "get-connection-unqualified" + ( Summary "Get an existing connection to another user (deprecated)" + :> ZUser + :> "connections" + :> CaptureUserId "uid" + :> MultiVerb + 'GET + '[JSON] + '[ EmptyErrorForLegacyReasons 404 "Connection not found", + Respond 200 "Connection found" UserConnection + ] + (Maybe UserConnection) + ) + :<|> Named + "get-connection" + ( Summary "Get an existing connection to another user (local or remote)" + :> ZUser + :> "connections" + :> QualifiedCaptureUserId "uid" + :> MultiVerb + 'GET + '[JSON] + '[ EmptyErrorForLegacyReasons 404 "Connection not found", + Respond 200 "Connection found" UserConnection + ] + (Maybe UserConnection) + ) + :<|> -- This endpoint can lead to the following events being sent: -- - ConnectionUpdated event to self and other, if their connection states change -- -- When changing the connection state to Sent or Accepted, this can cause events to be sent -- when joining the connect conversation: -- - MemberJoin event to self and other (via galley) - updateConnectionUnqualified :: - routes :- Summary "Update a connection to another user. (deprecated)" - :> CanThrow MissingLegalholdConsent - :> CanThrow InvalidUser - :> CanThrow ConnectionLimitReached - :> CanThrow NotConnected - :> CanThrow InvalidTransition - :> CanThrow NoIdentity - :> ZUser - :> ZConn - :> "connections" - :> CaptureUserId "uid" - :> ReqBody '[JSON] ConnectionUpdate - :> MultiVerb - 'PUT - '[JSON] - ConnectionUpdateResponses - (UpdateResult UserConnection), + Named + "update-connection-unqualified" + ( Summary "Update a connection to another user (deprecated)" + :> CanThrow MissingLegalholdConsent + :> CanThrow InvalidUser + :> CanThrow ConnectionLimitReached + :> CanThrow NotConnected + :> CanThrow InvalidTransition + :> CanThrow NoIdentity + :> ZUser + :> ZConn + :> "connections" + :> CaptureUserId "uid" + :> ReqBody '[JSON] ConnectionUpdate + :> MultiVerb + 'PUT + '[JSON] + ConnectionUpdateResponses + (UpdateResult UserConnection) + ) + :<|> -- This endpoint can lead to the following events being sent: -- - ConnectionUpdated event to self and other, if their connection states change -- -- When changing the connection state to Sent or Accepted, this can cause events to be sent -- when joining the connect conversation: -- - MemberJoin event to self and other (via galley) - updateConnection :: - routes :- Summary "Update a connection to another user. (deprecated)" - :> CanThrow MissingLegalholdConsent - :> CanThrow InvalidUser - :> CanThrow ConnectionLimitReached - :> CanThrow NotConnected - :> CanThrow InvalidTransition - :> CanThrow NoIdentity - :> ZUser - :> ZConn - :> "connections" - :> QualifiedCaptureUserId "uid" - :> ReqBody '[JSON] ConnectionUpdate - :> MultiVerb - 'PUT - '[JSON] - ConnectionUpdateResponses - (UpdateResult UserConnection), - searchContacts :: - routes :- Summary "Search for users" - :> ZUser - :> "search" - :> "contacts" - :> QueryParam' '[Required, Strict, Description "Search query"] "q" Text - :> QueryParam' '[Optional, Strict, Description "Searched domain. Note: This is optional only for backwards compatibility, future versions will mandate this."] "domain" Domain - :> QueryParam' '[Optional, Strict, Description "Number of results to return (min: 1, max: 500, default 15)"] "size" (Range 1 500 Int32) - :> Get '[Servant.JSON] (SearchResult Contact) - } - deriving (Generic) + Named + "update-connection" + ( Summary "Update a connection to another user (deprecatd)" + :> CanThrow MissingLegalholdConsent + :> CanThrow InvalidUser + :> CanThrow ConnectionLimitReached + :> CanThrow NotConnected + :> CanThrow InvalidTransition + :> CanThrow NoIdentity + :> ZUser + :> ZConn + :> "connections" + :> QualifiedCaptureUserId "uid" + :> ReqBody '[JSON] ConnectionUpdate + :> MultiVerb + 'PUT + '[JSON] + ConnectionUpdateResponses + (UpdateResult UserConnection) + ) + :<|> Named + "search-contacts" + ( Summary "Search for users" + :> ZUser + :> "search" + :> "contacts" + :> QueryParam' '[Required, Strict, Description "Search query"] "q" Text + :> QueryParam' '[Optional, Strict, Description "Searched domain. Note: This is optional only for backwards compatibility, future versions will mandate this."] "domain" Domain + :> QueryParam' '[Optional, Strict, Description "Number of results to return (min: 1, max: 500, default 15)"] "size" (Range 1 500 Int32) + :> Get '[Servant.JSON] (SearchResult Contact) + ) -type ServantAPI = ToServantApi Api +type BrigAPI = UserAPI :<|> SelfAPI :<|> ClientAPI :<|> PrekeyAPI :<|> UserClientAPI :<|> ConnectionAPI -swagger :: Swagger -swagger = toSwagger (Proxy @ServantAPI) +brigSwagger :: Swagger +brigSwagger = toSwagger (Proxy @BrigAPI) diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Cannon.hs b/libs/wire-api/src/Wire/API/Routes/Public/Cannon.hs index 8f9745b5035..ceacf45518a 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Cannon.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Cannon.hs @@ -21,23 +21,27 @@ import Data.Id import Data.Swagger import Servant import Servant.Swagger +import Wire.API.Routes.Named import Wire.API.Routes.Public (ZConn, ZUser) import Wire.API.Routes.WebSocket -type ServantAPI = - Summary "Establish websocket connection" - :> "await" - :> ZUser - :> ZConn - :> QueryParam' - [ Optional, - Strict, - Description "Client ID" - ] - "client" - ClientId - -- FUTUREWORK: Consider higher-level web socket combinator - :> WebSocketPending +type PublicAPI = + Named + "await-notifications" + ( Summary "Establish websocket connection" + :> "await" + :> ZUser + :> ZConn + :> QueryParam' + [ Optional, + Strict, + Description "Client ID" + ] + "client" + ClientId + -- FUTUREWORK: Consider higher-level web socket combinator + :> WebSocketPending + ) swaggerDoc :: Swagger -swaggerDoc = toSwagger (Proxy @ServantAPI) +swaggerDoc = toSwagger (Proxy @PublicAPI) diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Cargohold.hs b/libs/wire-api/src/Wire/API/Routes/Public/Cargohold.hs index 4cadbd0661b..3a27e6cbeea 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Cargohold.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Cargohold.hs @@ -113,7 +113,6 @@ type ServantAPI = :<|> BaseAPIv3 'ProviderPrincipalTag :<|> QualifiedAPI :<|> LegacyAPI - :<|> InternalAPI type BaseAPIv3 (tag :: PrincipalTag) = ( Summary "Upload an asset" @@ -208,8 +207,5 @@ type LegacyAPI = :> GetAsset ) -type InternalAPI = - "i" :> "status" :> MultiVerb 'GET '() '[RespondEmpty 200 "OK"] () - swaggerDoc :: Swagger.Swagger swaggerDoc = toSwagger (Proxy @ServantAPI) diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Galley.hs b/libs/wire-api/src/Wire/API/Routes/Public/Galley.hs index 853f58e588b..bd4c46d60f1 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Galley.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Galley.hs @@ -48,8 +48,6 @@ import Wire.API.Team.Conversation import Wire.API.Team.Feature import Wire.API.Team.Permission (Perm (..)) --- import Wire.API.Team.Permission (Perm (..)) - instance AsHeaders '[ConvId] Conversation Conversation where toHeaders c = (I (qUnqualified (cnvQualifiedId c)) :* Nil, c) fromHeaders = snd @@ -73,8 +71,30 @@ type ConversationVerb = ] ConversationResponse +type CreateConversationCodeVerb = + MultiVerb + 'POST + '[JSON] + '[ Respond 200 "Conversation code already exists." ConversationCode, + Respond 201 "Conversation code created." Event + ] + AddCodeResult + +instance + (ResponseType r1 ~ ConversationCode, ResponseType r2 ~ Event) => + AsUnion '[r1, r2] AddCodeResult + where + toUnion (CodeAlreadyExisted c) = Z (I c) + toUnion (CodeAdded e) = S (Z (I e)) + + fromUnion (Z (I c)) = CodeAlreadyExisted c + fromUnion (S (Z (I e))) = CodeAdded e + fromUnion (S (S x)) = case x of + type ConvUpdateResponses = UpdateResponses "Conversation unchanged" "Conversation updated" Event +type ConvJoinResponses = UpdateResponses "Conversation unchanged" "Conversation joined" Event + type RemoveFromConversationVerb = MultiVerb 'DELETE @@ -88,6 +108,7 @@ type ServantAPI = ConversationAPI :<|> TeamConversationAPI :<|> MessagingAPI + :<|> BotAPI :<|> TeamAPI :<|> FeatureAPI @@ -229,7 +250,7 @@ type ConversationAPI = :> ZLocalUser :> ZConn :> "conversations" - :> ReqBody '[Servant.JSON] NewConvUnmanaged + :> ReqBody '[Servant.JSON] NewConv :> ConversationVerb ) :<|> Named @@ -250,7 +271,7 @@ type ConversationAPI = :> ZConn :> "conversations" :> "one2one" - :> ReqBody '[Servant.JSON] NewConvUnmanaged + :> ReqBody '[Servant.JSON] NewConv :> ConversationVerb ) -- This endpoint can lead to the following events being sent: @@ -273,6 +294,15 @@ type ConversationAPI = :<|> Named "add-members-to-conversation" ( Summary "Add qualified members to an existing conversation." + :> CanThrow ConvNotFound + :> CanThrow ActionDenied + :> CanThrow (InvalidOp "Invalid operation") + :> CanThrow InvalidAction + :> CanThrow TooManyMembers + :> CanThrow ConvAccessDenied + :> CanThrow NotATeamMember + :> CanThrow NotConnected + :> CanThrow MissingLegalholdConsent :> ZLocalUser :> ZConn :> "conversations" @@ -283,6 +313,116 @@ type ConversationAPI = :> MultiVerb 'POST '[Servant.JSON] ConvUpdateResponses (UpdateResult Event) ) -- This endpoint can lead to the following events being sent: + -- - MemberJoin event to members + :<|> Named + "join-conversation-by-id-unqualified" + ( Summary "Join a conversation by its ID (if link access enabled)" + :> CanThrow ConvNotFound + :> ZLocalUser + :> ZConn + :> "conversations" + :> Capture' '[Description "Conversation ID"] "cnv" ConvId + :> "join" + :> MultiVerb 'POST '[Servant.JSON] ConvJoinResponses (UpdateResult Event) + ) + -- This endpoint can lead to the following events being sent: + -- - MemberJoin event to members + :<|> Named + "join-conversation-by-code-unqualified" + ( Summary + "Join a conversation using a reusable code.\ + \If the guest links team feature is disabled, this will fail with 409 GuestLinksDisabled.\ + \Note that this is currently inconsistent (for backwards compatibility reasons) with `POST /conversations/code-check` which responds with 404 CodeNotFound if guest links are disabled." + :> CanThrow CodeNotFound + :> CanThrow ConvNotFound + :> CanThrow TooManyMembers + :> CanThrow GuestLinksDisabled + :> ZLocalUser + :> ZConn + :> "conversations" + :> "join" + :> ReqBody '[Servant.JSON] ConversationCode + :> MultiVerb 'POST '[Servant.JSON] ConvJoinResponses (UpdateResult Event) + ) + :<|> Named + "code-check" + ( Summary + "Check validity of a conversation code.\ + \If the guest links team feature is disabled, this will fail with 404 CodeNotFound.\ + \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 + :> "conversations" + :> "code-check" + :> ReqBody '[Servant.JSON] ConversationCode + :> MultiVerb + 'POST + '[JSON] + '[RespondEmpty 200 "Valid"] + () + ) + -- 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" + :> CanThrow ConvNotFound + :> CanThrow InvalidAccessOp + :> ZUser + :> ZConn + :> "conversations" + :> Capture' '[Description "Conversation ID"] "cnv" ConvId + :> "code" + :> CreateConversationCodeVerb + ) + -- This endpoint can lead to the following events being sent: + -- - ConvCodeDelete event to members + :<|> Named + "remove-code-unqualified" + ( Summary "Delete conversation code" + :> CanThrow ConvNotFound + :> CanThrow InvalidAccessOp + :> ZLocalUser + :> ZConn + :> "conversations" + :> Capture' '[Description "Conversation ID"] "cnv" ConvId + :> "code" + :> MultiVerb + 'DELETE + '[JSON] + '[Respond 200 "Conversation code deleted." Event] + Event + ) + :<|> Named + "get-code" + ( Summary "Get existing conversation code" + :> CanThrow ConvNotFound + :> CanThrow InvalidAccessOp + :> ZLocalUser + :> "conversations" + :> Capture' '[Description "Conversation ID"] "cnv" ConvId + :> "code" + :> MultiVerb + 'GET + '[JSON] + '[Respond 200 "Conversation Code" ConversationCode] + ConversationCode + ) + -- This endpoint can lead to the following events being sent: + -- - Typing event to members + :<|> Named + "member-typing-unqualified" + ( Summary "Sending typing notifications" + :> CanThrow ConvNotFound + :> ZLocalUser + :> ZConn + :> "conversations" + :> Capture' '[Description "Conversation ID"] "cnv" ConvId + :> "typing" + :> ReqBody '[JSON] TypingData + :> MultiVerb 'POST '[JSON] '[RespondEmpty 200 "Notification sent"] () + ) + -- This endpoint can lead to the following events being sent: -- - MemberLeave event to members :<|> Named "remove-member-unqualified" @@ -691,17 +831,38 @@ type MessagingAPI = :> ZConn :> "conversations" :> Capture "cnv" ConvId - :> QueryParam "ignore_missing" IgnoreMissing - :> QueryParam "report_missing" ReportMissing :> "otr" :> "messages" - :> ReqBody '[Servant.JSON, Proto] NewOtrMessage + :> QueryParam "ignore_missing" IgnoreMissing + :> QueryParam "report_missing" ReportMissing + :> ReqBody '[JSON, Proto] NewOtrMessage :> MultiVerb 'POST '[Servant.JSON] (PostOtrResponses ClientMismatch) - (Either (MessageNotSent ClientMismatch) ClientMismatch) + (PostOtrResponse ClientMismatch) ) + :<|> Named + "post-otr-broadcast-unqualified" + ( Summary "Broadcast an encrypted message to all team members and all contacts (accepts JSON or Protobuf)" + :> Description PostOtrDescriptionUnqualified + :> ZLocalUser + :> ZConn + :> CanThrow TeamNotFound + :> CanThrow BroadcastLimitExceeded + :> CanThrow NonBindingTeam + :> "broadcast" + :> "otr" + :> "messages" + :> QueryParam "ignore_missing" IgnoreMissing + :> QueryParam "report_missing" ReportMissing + :> ReqBody '[JSON, Proto] NewOtrMessage + :> MultiVerb + 'POST + '[JSON] + (PostOtrResponses ClientMismatch) + (PostOtrResponse ClientMismatch) + ) :<|> Named "post-proteus-message" ( Summary "Post an encrypted message to a conversation (accepts only Protobuf)" @@ -720,6 +881,23 @@ type MessagingAPI = (Either (MessageNotSent MessageSendingStatus) MessageSendingStatus) ) +type BotAPI = + Named + "post-bot-message-unqualified" + ( ZBot + :> ZConversation + :> "bot" + :> "messages" + :> QueryParam "ignore_missing" IgnoreMissing + :> QueryParam "report_missing" ReportMissing + :> ReqBody '[JSON] NewOtrMessage + :> MultiVerb + 'POST + '[Servant.JSON] + (PostOtrResponses ClientMismatch) + (PostOtrResponse ClientMismatch) + ) + type FeatureAPI = FeatureStatusGet 'TeamFeatureSSO :<|> FeatureStatusGet 'TeamFeatureLegalHold @@ -742,6 +920,8 @@ type FeatureAPI = :<|> FeatureStatusPut 'TeamFeatureSelfDeletingMessages :<|> FeatureStatusGet 'TeamFeatureGuestLinks :<|> FeatureStatusPut 'TeamFeatureGuestLinks + :<|> FeatureStatusGet 'TeamFeatureSndFactorPasswordChallenge + :<|> FeatureStatusPut 'TeamFeatureSndFactorPasswordChallenge :<|> AllFeatureConfigsGet :<|> FeatureConfigGet 'WithoutLockStatus 'TeamFeatureLegalHold :<|> FeatureConfigGet 'WithoutLockStatus 'TeamFeatureSSO @@ -754,60 +934,66 @@ type FeatureAPI = :<|> FeatureConfigGet 'WithLockStatus 'TeamFeatureConferenceCalling :<|> FeatureConfigGet 'WithLockStatus 'TeamFeatureSelfDeletingMessages :<|> FeatureConfigGet 'WithLockStatus 'TeamFeatureGuestLinks + :<|> FeatureConfigGet 'WithLockStatus 'TeamFeatureSndFactorPasswordChallenge -type FeatureStatusGet featureName = +type FeatureStatusGet f = Named - '("get", featureName) - ( Summary (AppendSymbol "Get config for " (KnownTeamFeatureNameSymbol featureName)) - :> ZUser - :> "teams" - :> Capture "tid" TeamId - :> "features" - :> KnownTeamFeatureNameSymbol featureName - :> Get '[Servant.JSON] (TeamFeatureStatus 'WithLockStatus featureName) - ) + '("get", f) + (ZUser :> FeatureStatusBaseGet 'WithLockStatus f) -type FeatureStatusPut featureName = +type FeatureStatusPut f = Named - '("put", featureName) - ( Summary (AppendSymbol "Put config for " (KnownTeamFeatureNameSymbol featureName)) - :> ZUser - :> "teams" - :> Capture "tid" TeamId - :> "features" - :> KnownTeamFeatureNameSymbol featureName - :> ReqBody '[Servant.JSON] (TeamFeatureStatus 'WithoutLockStatus featureName) - :> Put '[Servant.JSON] (TeamFeatureStatus 'WithoutLockStatus featureName) - ) + '("put", f) + (ZUser :> FeatureStatusBasePut f) --- | A type for a GET endpoint for a feature with a deprecated path -type FeatureStatusDeprecatedGet ps featureName = +type FeatureStatusDeprecatedGet l f = Named - '("get-deprecated", featureName) - ( Summary - (AppendSymbol "[deprecated] Get config for " (KnownTeamFeatureNameSymbol featureName)) - :> ZUser - :> "teams" - :> Capture "tid" TeamId - :> "features" - :> DeprecatedFeatureName featureName - :> Get '[Servant.JSON] (TeamFeatureStatus ps featureName) - ) + '("get-deprecated", f) + (ZUser :> FeatureStatusBaseDeprecatedGet l f) --- | A type for a PUT endpoint for a feature with a deprecated path -type FeatureStatusDeprecatedPut featureName = +type FeatureStatusDeprecatedPut f = Named - '("put-deprecated", featureName) - ( Summary - (AppendSymbol "[deprecated] Get config for " (KnownTeamFeatureNameSymbol featureName)) - :> ZUser - :> "teams" - :> Capture "tid" TeamId - :> "features" - :> DeprecatedFeatureName featureName - :> ReqBody '[Servant.JSON] (TeamFeatureStatus 'WithoutLockStatus featureName) - :> Put '[Servant.JSON] (TeamFeatureStatus 'WithoutLockStatus featureName) - ) + '("put-deprecated", f) + (ZUser :> FeatureStatusBaseDeprecatedPut f) + +type FeatureStatusBaseGet lockStatus featureName = + Summary (AppendSymbol "Get config for " (KnownTeamFeatureNameSymbol featureName)) + :> "teams" + :> Capture "tid" TeamId + :> "features" + :> KnownTeamFeatureNameSymbol featureName + :> Get '[Servant.JSON] (TeamFeatureStatus lockStatus featureName) + +type FeatureStatusBasePut featureName = + Summary (AppendSymbol "Put config for " (KnownTeamFeatureNameSymbol featureName)) + :> "teams" + :> Capture "tid" TeamId + :> "features" + :> KnownTeamFeatureNameSymbol featureName + :> ReqBody '[Servant.JSON] (TeamFeatureStatus 'WithoutLockStatus featureName) + :> Put '[Servant.JSON] (TeamFeatureStatus 'WithoutLockStatus featureName) + +-- | A type for a GET endpoint for a feature with a deprecated path +type FeatureStatusBaseDeprecatedGet lockStatus featureName = + ( Summary + (AppendSymbol "[deprecated] Get config for " (KnownTeamFeatureNameSymbol featureName)) + :> "teams" + :> Capture "tid" TeamId + :> "features" + :> DeprecatedFeatureName featureName + :> Get '[Servant.JSON] (TeamFeatureStatus lockStatus featureName) + ) + +-- | A type for a PUT endpoint for a feature with a deprecated path +type FeatureStatusBaseDeprecatedPut featureName = + Summary + (AppendSymbol "[deprecated] Get config for " (KnownTeamFeatureNameSymbol featureName)) + :> "teams" + :> Capture "tid" TeamId + :> "features" + :> DeprecatedFeatureName featureName + :> ReqBody '[Servant.JSON] (TeamFeatureStatus 'WithoutLockStatus featureName) + :> Put '[Servant.JSON] (TeamFeatureStatus 'WithoutLockStatus featureName) type FeatureConfigGet ps featureName = Named @@ -828,6 +1014,9 @@ type AllFeatureConfigsGet = :> Get '[Servant.JSON] AllFeatureConfigs ) +-- This is a work-around for the fact that we sometimes want to send larger lists of user ids +-- in the filter query than fits the url length limit. For details, see +-- https://github.com/zinfra/backend-issues/issues/1248 type PostOtrDescriptionUnqualified = "This endpoint ensures that the list of clients is correct and only sends the message if the list is correct.\n\ \To override this, the endpoint accepts two query params:\n\ diff --git a/libs/wire-api/src/Wire/API/Routes/Public/LegalHold.hs b/libs/wire-api/src/Wire/API/Routes/Public/LegalHold.hs index a89b2db516b..a4eb42ae5de 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/LegalHold.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/LegalHold.hs @@ -22,13 +22,10 @@ import Data.Proxy import Data.Swagger hiding (Header (..)) import Servant.API hiding (Header) import Servant.Swagger -import Wire.API.Team.Feature import Wire.API.Team.LegalHold -type ServantAPI = PublicAPI :<|> InternalAPI - -- FUTUREWORK: restructure this for readability and add missing bodies -type PublicAPI = +type ServantAPI = "teams" :> Capture "tid" TeamId :> "legalhold" :> "settings" :> ReqBody '[JSON] NewLegalHoldService :> Post '[JSON] ViewLegalHoldService @@ -50,12 +47,5 @@ type PublicAPI = -- :> ReqBody '[JSON] DisableLegalHoldForUserRequest :> Verb 'DELETE 204 '[] NoContent -type InternalAPI = - "i" :> "teams" :> Capture "tid" TeamId :> "legalhold" - :> Get '[JSON] (TeamFeatureStatus 'WithLockStatus 'TeamFeatureLegalHold) - :<|> "i" :> "teams" :> Capture "tid" TeamId :> "legalhold" - :> ReqBody '[JSON] (TeamFeatureStatus 'WithoutLockStatus 'TeamFeatureLegalHold) - :> Put '[] NoContent - swaggerDoc :: Swagger swaggerDoc = toSwagger (Proxy @ServantAPI) diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Spar.hs b/libs/wire-api/src/Wire/API/Routes/Public/Spar.hs index f2a2d5eb4cd..ecf42e21d24 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Spar.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Spar.hs @@ -33,6 +33,7 @@ import Web.Scim.Capabilities.MetaSchema as Scim.Meta import Web.Scim.Class.Auth as Scim.Auth import Web.Scim.Class.User as Scim.User import Wire.API.Cookie +import Wire.API.ErrorDescription (CanThrow, CodeAuthenticationFailed, PasswordAuthenticationFailed) import Wire.API.Routes.Public import Wire.API.User.IdentityProvider import Wire.API.User.Saml @@ -203,7 +204,10 @@ sparResponseURI (Just tid) = type APIScim = OmitDocs :> "v2" :> ScimSiteAPI SparTag - :<|> "auth-tokens" :> APIScimToken + :<|> "auth-tokens" + :> CanThrow PasswordAuthenticationFailed + :> CanThrow CodeAuthenticationFailed + :> APIScimToken type ScimSiteAPI tag = ToServantApi (ScimSite tag) diff --git a/libs/wire-api/src/Wire/API/Routes/Version.hs b/libs/wire-api/src/Wire/API/Routes/Version.hs new file mode 100644 index 00000000000..acd525dc336 --- /dev/null +++ b/libs/wire-api/src/Wire/API/Routes/Version.hs @@ -0,0 +1,80 @@ +-- 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.Version where + +import Control.Lens ((?~)) +import Data.Aeson (FromJSON, ToJSON (..)) +import qualified Data.Aeson as Aeson +import Data.Schema +import qualified Data.Swagger as S +import qualified Data.Text as Text +import qualified Data.Text.Read as Text +import Imports +import Servant +import Servant.Swagger +import Wire.API.Routes.Named +import Wire.API.VersionInfo + +data Version = V0 | V1 + deriving stock (Eq, Ord, Bounded, Enum, Show) + deriving (FromJSON, ToJSON) via (Schema Version) + +instance ToSchema Version where + schema = + enum @Integer "Version" . mconcat $ + [ element 0 V0, + element 1 V1 + ] + +readVersionNumber :: Text -> Maybe Integer +readVersionNumber v = do + ('v', rest) <- Text.uncons v + case Text.decimal rest of + Right (n, "") -> pure n + _ -> Nothing + +mkVersion :: Integer -> Maybe Version +mkVersion n = case Aeson.fromJSON (Aeson.Number (fromIntegral n)) of + Aeson.Error _ -> Nothing + Aeson.Success v -> pure v + +supportedVersions :: [Version] +supportedVersions = [minBound .. maxBound] + +newtype VersionInfo = VersionInfo {vinfoSupported :: [Version]} + deriving (FromJSON, ToJSON, S.ToSchema) via (Schema VersionInfo) + +instance ToSchema VersionInfo where + schema = + (S.schema . S.example ?~ toJSON (VersionInfo supportedVersions)) + (VersionInfo <$> vinfoSupported .= vinfoSchema schema) + +type VersionAPI = + Named + "get-version" + ( "api-version" + :> Get '[JSON] VersionInfo + ) + +versionAPI :: Applicative m => ServerT VersionAPI m +versionAPI = + Named @"get-version" $ + pure . VersionInfo $ supportedVersions + +versionSwagger :: S.Swagger +versionSwagger = toSwagger (Proxy @VersionAPI) diff --git a/libs/wire-api/src/Wire/API/Routes/Version/Wai.hs b/libs/wire-api/src/Wire/API/Routes/Version/Wai.hs new file mode 100644 index 00000000000..b0ba0c2ef8b --- /dev/null +++ b/libs/wire-api/src/Wire/API/Routes/Version/Wai.hs @@ -0,0 +1,47 @@ +-- 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.Version.Wai where + +import qualified Data.Text.Lazy as LText +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 Wire.API.Routes.Version + +-- | Strip off version prefix. Return 404 if the version is not supported. +versionMiddleware :: Middleware +versionMiddleware app req k = case parseVersion req of + Nothing -> app req k + Just (req', n) -> case mkVersion n of + Just _ -> app req' k + Nothing -> + k $ + errorRs' $ + mkError HTTP.status404 "unsupported-version" $ + "Version " <> LText.pack (show n) <> " is not supported" + +parseVersion :: Request -> Maybe (Request, Integer) +parseVersion req = do + (version, pinfo) <- case pathInfo req of + [] -> Nothing + (x : xs) -> pure (x, xs) + n <- readVersionNumber version + pure (rewriteRequestPure (\(_, q) _ -> (pinfo, q)) req, n) diff --git a/libs/wire-api/src/Wire/API/Swagger.hs b/libs/wire-api/src/Wire/API/Swagger.hs index 19cdb227c6a..4fbc4ed77ca 100644 --- a/libs/wire-api/src/Wire/API/Swagger.hs +++ b/libs/wire-api/src/Wire/API/Swagger.hs @@ -142,12 +142,7 @@ models = User.modelUserIdList, User.modelUser, User.modelNewUser, - User.modelUserUpdate, - User.modelChangePassword, - User.modelChangeLocale, User.modelEmailUpdate, - User.modelPhoneUpdate, - User.modelChangeHandle, User.modelDelete, User.modelVerifyDelete, User.Activation.modelActivate, @@ -173,7 +168,6 @@ models = User.Handle.modelCheckHandles, User.Password.modelNewPasswordReset, User.Password.modelCompletePasswordReset, - User.Profile.modelUserDisplayName, User.Profile.modelAsset, User.RichInfo.modelRichInfo, User.RichInfo.modelRichField, diff --git a/libs/wire-api/src/Wire/API/Team.hs b/libs/wire-api/src/Wire/API/Team.hs index 0a9b5744f61..5b3563e1819 100644 --- a/libs/wire-api/src/Wire/API/Team.hs +++ b/libs/wire-api/src/Wire/API/Team.hs @@ -72,15 +72,20 @@ where import Control.Lens (makeLenses) import Data.Aeson (FromJSON, ToJSON, Value (..)) import Data.Aeson.Types (Parser) +import qualified Data.Attoparsec.ByteString as Atto (Parser, string) +import Data.Attoparsec.Combinator (choice) +import Data.ByteString.Conversion import Data.Id (TeamId, UserId) import Data.Misc (PlainTextPassword (..)) import Data.Range import Data.Schema import qualified Data.Swagger as S import qualified Data.Swagger.Build.Api as Doc +import qualified Data.Text.Encoding as T import Imports import Test.QuickCheck.Gen (suchThat) import Wire.API.Arbitrary (Arbitrary (arbitrary), GenericUniform (..)) +import Wire.API.Asset (AssetKey) import Wire.API.Team.Member (TeamMember, modelTeamMember) -------------------------------------------------------------------------------- @@ -254,9 +259,30 @@ newTeamSchema name sch = -------------------------------------------------------------------------------- -- TeamUpdateData +data IconUpdate = IconUpdate AssetKey | DefaultIcon + deriving stock (Eq, Show, Generic) + deriving (Arbitrary) via (GenericUniform IconUpdate) + deriving (ToJSON, FromJSON, S.ToSchema) via Schema IconUpdate + +instance FromByteString IconUpdate where + parser = + choice + [ IconUpdate <$> (parser :: Atto.Parser AssetKey), + DefaultIcon <$ Atto.string "default" + ] + +instance ToByteString IconUpdate where + builder (IconUpdate key) = builder key + builder DefaultIcon = "default" + +instance ToSchema IconUpdate where + schema = + (T.decodeUtf8 . toByteString') + .= parsedText "IconUpdate" (runParser parser . T.encodeUtf8) + data TeamUpdateData = TeamUpdateData { _nameUpdate :: Maybe (Range 1 256 Text), - _iconUpdate :: Maybe (Range 1 256 Text), + _iconUpdate :: Maybe IconUpdate, _iconKeyUpdate :: Maybe (Range 1 256 Text) } deriving stock (Eq, Show, Generic) diff --git a/libs/wire-api/src/Wire/API/Team/Conversation.hs b/libs/wire-api/src/Wire/API/Team/Conversation.hs index c3e9c3e49ec..f6fb56f92a7 100644 --- a/libs/wire-api/src/Wire/API/Team/Conversation.hs +++ b/libs/wire-api/src/Wire/API/Team/Conversation.hs @@ -24,7 +24,6 @@ module Wire.API.Team.Conversation TeamConversation, newTeamConversation, conversationId, - managedConversation, -- * TeamConversationList TeamConversationList, @@ -49,9 +48,8 @@ import Wire.API.Arbitrary (Arbitrary, GenericUniform (..)) -------------------------------------------------------------------------------- -- TeamConversation -data TeamConversation = TeamConversation - { _conversationId :: ConvId, - _managedConversation :: Bool +newtype TeamConversation = TeamConversation + { _conversationId :: ConvId } deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform TeamConversation) @@ -59,20 +57,15 @@ data TeamConversation = TeamConversation instance ToSchema TeamConversation where declareNamedSchema _ = do idSchema <- declareSchemaRef (Proxy @ConvId) - let managed = - toSchema (Proxy @Bool) - & description ?~ "Indicates if this is a managed team conversation." pure $ NamedSchema (Just "TeamConversation") $ mempty & description ?~ "team conversation data" & over properties - ( (at "managed" ?~ Inline managed) - . (at "conversation" ?~ idSchema) - ) + (at "conversation" ?~ idSchema) -newTeamConversation :: ConvId -> Bool -> TeamConversation +newTeamConversation :: ConvId -> TeamConversation newTeamConversation = TeamConversation modelTeamConversation :: Doc.Model @@ -80,19 +73,18 @@ modelTeamConversation = Doc.defineModel "TeamConversation" $ do Doc.description "team conversation data" Doc.property "conversation" Doc.bytes' $ Doc.description "conversation ID" - Doc.property "managed" Doc.bool' $ - Doc.description "Indicates if this is a managed team conversation." instance ToJSON TeamConversation where toJSON t = object [ "conversation" .= _conversationId t, - "managed" .= _managedConversation t + -- FUTUREWORK: get rid of the "managed" field in the next version of the API + "managed" .= False ] instance FromJSON TeamConversation where parseJSON = withObject "team conversation" $ \o -> - TeamConversation <$> o .: "conversation" <*> o .: "managed" + TeamConversation <$> o .: "conversation" -------------------------------------------------------------------------------- -- TeamConversationList diff --git a/libs/wire-api/src/Wire/API/Team/Export.hs b/libs/wire-api/src/Wire/API/Team/Export.hs index b0fee5a4bee..1391f86b277 100644 --- a/libs/wire-api/src/Wire/API/Team/Export.hs +++ b/libs/wire-api/src/Wire/API/Team/Export.hs @@ -15,11 +15,12 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Wire.API.Team.Export (TeamExportUser (..)) where +module Wire.API.Team.Export (TeamExportUser (..), quoted, unquoted) where import qualified Data.Aeson as Aeson import Data.Attoparsec.ByteString.Lazy (parseOnly) -import Data.ByteString.Conversion (FromByteString (..), toByteString') +import qualified Data.ByteString.Char8 as C +import Data.ByteString.Conversion (FromByteString (..), ToByteString, toByteString') import Data.Csv (DefaultOrdered (..), FromNamedRecord (..), Parser, ToNamedRecord (..), namedRecord, (.:)) import Data.Handle (Handle) import Data.Id (UserId) @@ -56,20 +57,23 @@ data TeamExportUser = TeamExportUser instance ToNamedRecord TeamExportUser where toNamedRecord row = namedRecord - [ ("display_name", toByteString' (tExportDisplayName row)), - ("handle", maybe "" toByteString' (tExportHandle row)), - ("email", maybe "" toByteString' (tExportEmail row)), - ("role", maybe "" toByteString' (tExportRole row)), - ("created_on", maybe "" toByteString' (tExportCreatedOn row)), - ("invited_by", maybe "" toByteString' (tExportInvitedBy row)), - ("idp_issuer", maybe "" toByteString' (tExportIdpIssuer row)), - ("managed_by", toByteString' (tExportManagedBy row)), - ("saml_name_id", toByteString' (tExportSAMLNamedId row)), - ("scim_external_id", toByteString' (tExportSCIMExternalId row)), + [ ("display_name", secureCsvFieldToByteString (tExportDisplayName row)), + ("handle", maybe "" secureCsvFieldToByteString (tExportHandle row)), + ("email", maybe "" secureCsvFieldToByteString (tExportEmail row)), + ("role", maybe "" secureCsvFieldToByteString (tExportRole row)), + ("created_on", maybe "" secureCsvFieldToByteString (tExportCreatedOn row)), + ("invited_by", maybe "" secureCsvFieldToByteString (tExportInvitedBy row)), + ("idp_issuer", maybe "" secureCsvFieldToByteString (tExportIdpIssuer row)), + ("managed_by", secureCsvFieldToByteString (tExportManagedBy row)), + ("saml_name_id", secureCsvFieldToByteString (tExportSAMLNamedId row)), + ("scim_external_id", secureCsvFieldToByteString (tExportSCIMExternalId row)), ("scim_rich_info", maybe "" (cs . Aeson.encode) (tExportSCIMRichInfo row)), - ("user_id", toByteString' (tExportUserId row)) + ("user_id", secureCsvFieldToByteString (tExportUserId row)) ] +secureCsvFieldToByteString :: forall a. ToByteString a => a -> ByteString +secureCsvFieldToByteString = quoted . toByteString' + instance DefaultOrdered TeamExportUser where headerOrder = const $ @@ -94,7 +98,7 @@ allowEmpty p str = Just <$> p str parseByteString :: forall a. FromByteString a => ByteString -> Parser a parseByteString bstr = - case parseOnly (parser @a) bstr of + case parseOnly (parser @a) (unquoted bstr) of Left err -> fail err Right thing -> pure thing @@ -113,3 +117,24 @@ instance FromNamedRecord TeamExportUser where <*> (nrec .: "scim_external_id" >>= parseByteString) <*> (nrec .: "scim_rich_info" >>= allowEmpty (maybe (fail "failed to decode RichInfo") pure . Aeson.decode . cs)) <*> (nrec .: "user_id" >>= parseByteString) + +quoted :: ByteString -> ByteString +quoted bs = case C.uncons bs of + -- fields that begin with a disallowed character are prepended with a single quote + Just ('=', _) -> '\'' `C.cons` bs + Just ('+', _) -> '\'' `C.cons` bs + Just ('-', _) -> '\'' `C.cons` bs + Just ('@', _) -> '\'' `C.cons` bs + -- tab + Just ('\x0009', _) -> '\'' `C.cons` bs + -- carriage return + Just ('\x000D', _) -> '\'' `C.cons` bs + -- if a field begins with a single quote we have to prepend another single quote to be able to decode back correctly + Just ('\'', _) -> '\'' `C.cons` bs + -- everything else is fine + _ -> bs + +unquoted :: ByteString -> ByteString +unquoted bstr = case C.uncons bstr of + Just ('\'', t) -> t + _ -> bstr diff --git a/libs/wire-api/src/Wire/API/Team/Feature.hs b/libs/wire-api/src/Wire/API/Team/Feature.hs index e3127733d82..92aa3b062c8 100644 --- a/libs/wire-api/src/Wire/API/Team/Feature.hs +++ b/libs/wire-api/src/Wire/API/Team/Feature.hs @@ -42,6 +42,8 @@ module Wire.API.Team.Feature defaultSelfDeletingMessagesStatus, defaultGuestLinksStatus, defaultTeamFeatureFileSharing, + defaultTeamFeatureValidateSAMLEmailsStatus, + defaultTeamFeatureSndFactorPasswordChallengeStatus, -- * Swagger typeTeamFeatureName, @@ -139,6 +141,7 @@ data TeamFeatureName | TeamFeatureConferenceCalling | TeamFeatureSelfDeletingMessages | TeamFeatureGuestLinks + | TeamFeatureSndFactorPasswordChallenge deriving stock (Eq, Show, Ord, Generic, Enum, Bounded, Typeable) deriving (Arbitrary) via (GenericUniform TeamFeatureName) @@ -190,6 +193,10 @@ instance KnownTeamFeatureName 'TeamFeatureGuestLinks where type KnownTeamFeatureNameSymbol 'TeamFeatureGuestLinks = "conversationGuestLinks" knownTeamFeatureName = TeamFeatureGuestLinks +instance KnownTeamFeatureName 'TeamFeatureSndFactorPasswordChallenge where + type KnownTeamFeatureNameSymbol 'TeamFeatureSndFactorPasswordChallenge = "sndFactorPasswordChallenge" + knownTeamFeatureName = TeamFeatureSndFactorPasswordChallenge + instance FromByteString TeamFeatureName where parser = Parser.takeByteString >>= \b -> @@ -209,6 +216,7 @@ instance FromByteString TeamFeatureName where Right "conferenceCalling" -> pure TeamFeatureConferenceCalling Right "selfDeletingMessages" -> pure TeamFeatureSelfDeletingMessages Right "conversationGuestLinks" -> pure TeamFeatureGuestLinks + Right "sndFactorPasswordChallenge" -> pure TeamFeatureSndFactorPasswordChallenge Right t -> fail $ "Invalid TeamFeatureName: " <> T.unpack t -- TODO: how do we make this consistent with 'KnownTeamFeatureNameSymbol'? add a test for @@ -225,6 +233,7 @@ instance ToByteString TeamFeatureName where builder TeamFeatureConferenceCalling = "conferenceCalling" builder TeamFeatureSelfDeletingMessages = "selfDeletingMessages" builder TeamFeatureGuestLinks = "conversationGuestLinks" + builder TeamFeatureSndFactorPasswordChallenge = "sndFactorPasswordChallenge" instance ToSchema TeamFeatureName where schema = @@ -319,6 +328,8 @@ type family TeamFeatureStatus (ps :: IncludeLockStatus) (a :: TeamFeatureName) : TeamFeatureStatus 'WithLockStatus 'TeamFeatureSelfDeletingMessages = TeamFeatureStatusWithConfigAndLockStatus TeamFeatureSelfDeletingMessagesConfig TeamFeatureStatus 'WithoutLockStatus 'TeamFeatureGuestLinks = TeamFeatureStatusNoConfig TeamFeatureStatus 'WithLockStatus 'TeamFeatureGuestLinks = TeamFeatureStatusNoConfigAndLockStatus + TeamFeatureStatus 'WithoutLockStatus 'TeamFeatureSndFactorPasswordChallenge = TeamFeatureStatusNoConfig + TeamFeatureStatus 'WithLockStatus 'TeamFeatureSndFactorPasswordChallenge = TeamFeatureStatusNoConfigAndLockStatus type family FeatureHasNoConfig (ps :: IncludeLockStatus) (a :: TeamFeatureName) :: Constraint where FeatureHasNoConfig 'WithLockStatus a = (TeamFeatureStatus 'WithLockStatus a ~ TeamFeatureStatusNoConfigAndLockStatus) @@ -337,6 +348,7 @@ modelForTeamFeature name@TeamFeatureClassifiedDomains = modelTeamFeatureStatusWi modelForTeamFeature TeamFeatureConferenceCalling = modelTeamFeatureStatusNoConfig modelForTeamFeature name@TeamFeatureSelfDeletingMessages = modelTeamFeatureStatusWithConfig name modelTeamFeatureSelfDeletingMessagesConfig modelForTeamFeature TeamFeatureGuestLinks = modelTeamFeatureStatusNoConfig +modelForTeamFeature TeamFeatureSndFactorPasswordChallenge = modelTeamFeatureStatusNoConfig ---------------------------------------------------------------------- -- TeamFeatureStatusNoConfig @@ -615,6 +627,18 @@ instance Cass.Cql LockStatusValue where defaultGuestLinksStatus :: TeamFeatureStatusNoConfigAndLockStatus defaultGuestLinksStatus = TeamFeatureStatusNoConfigAndLockStatus TeamFeatureEnabled Unlocked +---------------------------------------------------------------------- +-- TeamFeatureValidateSAMLEmails + +defaultTeamFeatureValidateSAMLEmailsStatus :: TeamFeatureStatusNoConfig +defaultTeamFeatureValidateSAMLEmailsStatus = TeamFeatureStatusNoConfig TeamFeatureEnabled + +---------------------------------------------------------------------- +-- TeamFeatureSndFactorPasswordChallenge + +defaultTeamFeatureSndFactorPasswordChallengeStatus :: TeamFeatureStatusNoConfigAndLockStatus +defaultTeamFeatureSndFactorPasswordChallengeStatus = TeamFeatureStatusNoConfigAndLockStatus TeamFeatureDisabled Unlocked + ---------------------------------------------------------------------- -- internal diff --git a/libs/wire-api/src/Wire/API/User.hs b/libs/wire-api/src/Wire/API/User.hs index 2ef7f9f7158..be3dd749a2d 100644 --- a/libs/wire-api/src/Wire/API/User.hs +++ b/libs/wire-api/src/Wire/API/User.hs @@ -56,11 +56,21 @@ module Wire.API.User -- * Profile Updates UserUpdate (..), + UpdateProfileError (..), + PutSelfResponses, PasswordChange (..), + ChangePasswordError (..), + ChangePasswordResponses, LocaleUpdate (..), EmailUpdate (..), PhoneUpdate (..), + ChangePhoneError (..), + ChangePhoneResponses, + RemoveIdentityError (..), + RemoveIdentityResponses, HandleUpdate (..), + ChangeHandleError (..), + ChangeHandleResponses, NameUpdate (..), -- * Account Deletion @@ -81,17 +91,17 @@ module Wire.API.User module Wire.API.User.Profile, -- * Swagger - modelChangeHandle, - modelChangeLocale, - modelChangePassword, modelDelete, modelEmailUpdate, modelNewUser, - modelPhoneUpdate, modelUser, modelUserIdList, - modelUserUpdate, modelVerifyDelete, + + -- * 2nd factor auth + SndFactorPasswordChallengeAction (..), + SendVerificationCode (..), + TeamFeatureSndFPasswordChallengeNotImplemented (..), ) where @@ -113,9 +123,9 @@ import Data.Json.Util (UTCTimeMillis, (#)) import Data.LegalHold (UserLegalHoldStatus) import qualified Data.List as List import Data.Misc (PlainTextPassword (..)) -import Data.Proxy (Proxy (..)) import Data.Qualified import Data.Range +import Data.SOP import Data.Schema import qualified Data.Swagger as S import qualified Data.Swagger.Build.Api as Doc @@ -124,11 +134,15 @@ import Data.UUID (UUID, nil) import qualified Data.UUID as UUID import Deriving.Swagger import GHC.TypeLits (KnownNat, Nat) +import qualified Generics.SOP as GSOP import Imports import qualified SAML2.WebSSO as SAML +import Servant (type (.++)) import qualified Test.QuickCheck as QC import Wire.API.Arbitrary (Arbitrary (arbitrary), GenericUniform (..)) +import Wire.API.ErrorDescription import Wire.API.Provider.Service (ServiceRef, modelServiceRef) +import Wire.API.Routes.MultiVerb import Wire.API.Team (BindingNewTeam (BindingNewTeam), NewTeam (..), modelNewBindingTeam) import Wire.API.User.Activation (ActivationCode) import Wire.API.User.Auth (CookieLabel) @@ -830,37 +844,33 @@ data UserUpdate = UserUpdate uupAccentId :: Maybe ColourId } deriving stock (Eq, Show, Generic) + deriving (ToJSON, FromJSON, S.ToSchema) via (Schema UserUpdate) deriving (Arbitrary) via (GenericUniform UserUpdate) -modelUserUpdate :: Doc.Model -modelUserUpdate = Doc.defineModel "UserUpdate" $ do - Doc.description "User Update Data" - Doc.property "name" Doc.string' $ do - Doc.description "Name (1 - 128 characters)" - Doc.optional - Doc.property "assets" (Doc.array (Doc.ref modelAsset)) $ do - Doc.description "Profile assets" - Doc.optional - Doc.property "accent_id" Doc.int32' $ do - Doc.description "Accent colour ID" - Doc.optional +instance ToSchema UserUpdate where + schema = + object "UserUpdate" $ + UserUpdate + <$> uupName .= maybe_ (optField "name" schema) + <*> uupPict .= maybe_ (optField "picture" schema) + <*> uupAssets .= maybe_ (optField "assets" (array schema)) + <*> uupAccentId .= maybe_ (optField "accent_id" schema) -instance ToJSON UserUpdate where - toJSON u = - A.object $ - "name" A..= uupName u - # "picture" A..= uupPict u - # "assets" A..= uupAssets u - # "accent_id" A..= uupAccentId u - # [] +data UpdateProfileError + = DisplayNameManagedByScim + | ProfileNotFound + deriving (Generic) + deriving (AsUnion PutSelfErrorResponses) via GenericAsUnion PutSelfErrorResponses UpdateProfileError -instance FromJSON UserUpdate where - parseJSON = A.withObject "UserUpdate" $ \o -> - UserUpdate - <$> o A..:? "name" - <*> o A..:? "picture" - <*> o A..:? "assets" - <*> o A..:? "accent_id" +instance GSOP.Generic UpdateProfileError + +type PutSelfErrorResponses = '[NameManagedByScim, UserNotFound] + +type PutSelfResponses = PutSelfErrorResponses .++ '[RespondEmpty 200 "User updated"] + +instance (res ~ PutSelfResponses) => AsUnion res (Maybe UpdateProfileError) where + toUnion = maybeToUnion (toUnion @PutSelfErrorResponses) + fromUnion = maybeFromUnion (fromUnion @PutSelfErrorResponses) -- | The payload for setting or changing a password. data PasswordChange = PasswordChange @@ -869,47 +879,49 @@ data PasswordChange = PasswordChange } deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform PasswordChange) + deriving (ToJSON, FromJSON, S.ToSchema) via (Schema PasswordChange) -modelChangePassword :: Doc.Model -modelChangePassword = Doc.defineModel "ChangePassword" $ do - Doc.description - "Data to change a password. The old password is required if \ - \a password already exists." - Doc.property "old_password" Doc.string' $ do - Doc.description "Old password" - Doc.optional - Doc.property "new_password" Doc.string' $ - Doc.description "New password (6 - 1024 characters)" - -instance ToJSON PasswordChange where - toJSON (PasswordChange old new) = - A.object - [ "old_password" A..= old, - "new_password" A..= new - ] - -instance FromJSON PasswordChange where - parseJSON = A.withObject "PasswordChange" $ \o -> - PasswordChange - <$> o A..:? "old_password" - <*> o A..: "new_password" +instance ToSchema PasswordChange where + schema = + over + doc + ( description + ?~ "Data to change a password. The old password is required if \ + \a password already exists." + ) + . object "PasswordChange" + $ PasswordChange + <$> cpOldPassword .= maybe_ (optField "old_password" schema) + <*> cpNewPassword .= field "new_password" schema + +data ChangePasswordError + = InvalidCurrentPassword + | ChangePasswordNoIdentity + | ChangePasswordMustDiffer + deriving (Generic) + deriving (AsUnion ChangePasswordErrorResponses) via GenericAsUnion ChangePasswordErrorResponses ChangePasswordError + +instance GSOP.Generic ChangePasswordError + +type ChangePasswordErrorResponses = [BadCredentials, NoIdentity, ChangePasswordMustDiffer] + +type ChangePasswordResponses = + ChangePasswordErrorResponses .++ '[RespondEmpty 200 "Password Changed"] + +instance (res ~ ChangePasswordResponses) => AsUnion res (Maybe ChangePasswordError) where + toUnion = maybeToUnion (toUnion @ChangePasswordErrorResponses) + fromUnion = maybeFromUnion (fromUnion @ChangePasswordErrorResponses) newtype LocaleUpdate = LocaleUpdate {luLocale :: Locale} deriving stock (Eq, Show, Generic) deriving newtype (Arbitrary) + deriving (ToJSON, FromJSON, S.ToSchema) via (Schema LocaleUpdate) -modelChangeLocale :: Doc.Model -modelChangeLocale = Doc.defineModel "ChangeLocale" $ do - Doc.description "Data to change a locale." - Doc.property "locale" Doc.string' $ - Doc.description "Locale to be set" - -instance ToJSON LocaleUpdate where - toJSON l = A.object ["locale" A..= luLocale l] - -instance FromJSON LocaleUpdate where - parseJSON = A.withObject "locale-update" $ \o -> - LocaleUpdate <$> o A..: "locale" +instance ToSchema LocaleUpdate where + schema = + object "LocaleUpdate" $ + LocaleUpdate + <$> luLocale .= field "locale" schema newtype EmailUpdate = EmailUpdate {euEmail :: Email} deriving stock (Eq, Show, Generic) @@ -938,36 +950,78 @@ instance FromJSON EmailUpdate where newtype PhoneUpdate = PhoneUpdate {puPhone :: Phone} deriving stock (Eq, Show, Generic) deriving newtype (Arbitrary) + deriving (ToJSON, FromJSON, S.ToSchema) via Schema PhoneUpdate -modelPhoneUpdate :: Doc.Model -modelPhoneUpdate = Doc.defineModel "PhoneUpdate" $ do - Doc.description "Phone Update Data" - Doc.property "phone" Doc.string' $ - Doc.description "E.164 phone number" +instance ToSchema PhoneUpdate where + schema = + object "PhoneUpdate" $ + PhoneUpdate + <$> puPhone .= field "phone" schema + +data ChangePhoneError + = PhoneExists + | InvalidNewPhone + | BlacklistedNewPhone + deriving (Generic) + deriving (AsUnion ChangePhoneErrorResponses) via GenericAsUnion ChangePhoneErrorResponses ChangePhoneError + +instance GSOP.Generic ChangePhoneError + +type ChangePhoneErrorResponses = [UserKeyExists, InvalidPhone, BlacklistedPhone] + +type ChangePhoneResponses = + ChangePhoneErrorResponses .++ '[RespondEmpty 202 "Phone updated"] -instance ToJSON PhoneUpdate where - toJSON p = A.object ["phone" A..= puPhone p] +instance (res ~ ChangePhoneResponses) => AsUnion res (Maybe ChangePhoneError) where + toUnion = maybeToUnion (toUnion @ChangePhoneErrorResponses) + fromUnion = maybeFromUnion (fromUnion @ChangePhoneErrorResponses) -instance FromJSON PhoneUpdate where - parseJSON = A.withObject "phone-update" $ \o -> - PhoneUpdate <$> o A..: "phone" +data RemoveIdentityError + = LastIdentity + | NoPassword + | NoIdentity + deriving (Generic) + deriving (AsUnion RemoveIdentityErrorResponses) via GenericAsUnion RemoveIdentityErrorResponses RemoveIdentityError + +instance GSOP.Generic RemoveIdentityError + +type RemoveIdentityErrorResponses = [LastIdentity, NoPassword, NoIdentity] + +type RemoveIdentityResponses = + RemoveIdentityErrorResponses .++ '[RespondEmpty 200 "Identity Removed"] + +instance (res ~ RemoveIdentityResponses) => AsUnion res (Maybe RemoveIdentityError) where + toUnion = maybeToUnion (toUnion @RemoveIdentityErrorResponses) + fromUnion = maybeFromUnion (fromUnion @RemoveIdentityErrorResponses) newtype HandleUpdate = HandleUpdate {huHandle :: Text} deriving stock (Eq, Show, Generic) deriving newtype (Arbitrary) + deriving (ToJSON, FromJSON, S.ToSchema) via (Schema HandleUpdate) + +instance ToSchema HandleUpdate where + schema = + object "HandleUpdate" $ + HandleUpdate <$> huHandle .= field "handle" schema -modelChangeHandle :: Doc.Model -modelChangeHandle = Doc.defineModel "ChangeHandle" $ do - Doc.description "Change the handle." - Doc.property "handle" Doc.string' $ - Doc.description "Handle to set" +data ChangeHandleError + = ChangeHandleNoIdentity + | ChangeHandleExists + | ChangeHandleInvalid + | ChangeHandleManagedByScim + deriving (Generic) + deriving (AsUnion ChangeHandleErrorResponses) via GenericAsUnion ChangeHandleErrorResponses ChangeHandleError -instance ToJSON HandleUpdate where - toJSON h = A.object ["handle" A..= huHandle h] +instance GSOP.Generic ChangeHandleError -instance FromJSON HandleUpdate where - parseJSON = A.withObject "handle-update" $ \o -> - HandleUpdate <$> o A..: "handle" +type ChangeHandleErrorResponses = [NoIdentity, HandleExists, InvalidHandle, HandleManagedByScim] + +type ChangeHandleResponses = + ChangeHandleErrorResponses .++ '[RespondEmpty 200 "Handle Changed"] + +instance (res ~ ChangeHandleResponses) => AsUnion res (Maybe ChangeHandleError) where + toUnion = maybeToUnion (toUnion @ChangeHandleErrorResponses) + fromUnion = maybeFromUnion (fromUnion @ChangeHandleErrorResponses) newtype NameUpdate = NameUpdate {nuHandle :: Text} deriving stock (Eq, Show, Generic) @@ -1102,3 +1156,38 @@ instance S.ToSchema ListUsersQuery where & S.description ?~ "exactly one of qualified_ids or qualified_handles must be provided." & S.properties .~ InsOrdHashMap.fromList [("qualified_ids", uids), ("qualified_handles", handles)] & S.example ?~ toJSON (ListUsersByIds [Qualified (Id UUID.nil) (Domain "example.com")]) + +----------------------------------------------------------------------------- +-- SndFactorPasswordChallenge + +-- | remove this type once we have an implementation in order to find all the places where we need to touch code. +data TeamFeatureSndFPasswordChallengeNotImplemented + = TeamFeatureSndFPasswordChallengeNotImplemented + +data SndFactorPasswordChallengeAction = GenerateScimToken | Login + deriving stock (Eq, Show, Enum, Bounded, Generic) + deriving (Arbitrary) via (GenericUniform SndFactorPasswordChallengeAction) + deriving (FromJSON, ToJSON, S.ToSchema) via (Schema SndFactorPasswordChallengeAction) + +instance ToSchema SndFactorPasswordChallengeAction where + schema = + enum @Text "SndFactorPasswordChallengeAction" $ + mconcat + [ element "generate_scim_token" GenerateScimToken, + element "login" Login + ] + +data SendVerificationCode = SendVerificationCode + { svcAction :: SndFactorPasswordChallengeAction, + svcEmail :: Email + } + deriving stock (Eq, Show, Generic) + deriving (Arbitrary) via (GenericUniform SendVerificationCode) + deriving (FromJSON, ToJSON, S.ToSchema) via Schema SendVerificationCode + +instance ToSchema SendVerificationCode where + schema = + object "SendVerificationCode" $ + SendVerificationCode + <$> svcAction .= field "action" schema + <*> svcEmail .= field "email" schema diff --git a/libs/wire-api/src/Wire/API/User/Activation.hs b/libs/wire-api/src/Wire/API/User/Activation.hs index cd85f248857..a4a2f2a0565 100644 --- a/libs/wire-api/src/Wire/API/User/Activation.hs +++ b/libs/wire-api/src/Wire/API/User/Activation.hs @@ -80,6 +80,7 @@ newtype ActivationKey = ActivationKey -- | A random code for use with an 'ActivationKey' that is usually transmitted -- out-of-band, e.g. via email or sms. +-- FUTUREWORK(leif): rename to VerificationCode newtype ActivationCode = ActivationCode {fromActivationCode :: AsciiBase64Url} deriving stock (Eq, Show, Generic) diff --git a/libs/wire-api/src/Wire/API/User/Auth.hs b/libs/wire-api/src/Wire/API/User/Auth.hs index a718f24cb7a..e8fac171d23 100644 --- a/libs/wire-api/src/Wire/API/User/Auth.hs +++ b/libs/wire-api/src/Wire/API/User/Auth.hs @@ -66,6 +66,7 @@ import Data.Text.Lazy.Encoding (decodeUtf8, encodeUtf8) import Data.Time.Clock (UTCTime) import Imports import Wire.API.Arbitrary (Arbitrary (arbitrary), GenericUniform (..)) +import Wire.API.User.Activation import Wire.API.User.Identity (Email, Phone) -------------------------------------------------------------------------------- @@ -73,7 +74,7 @@ import Wire.API.User.Identity (Email, Phone) -- | Different kinds of logins. data Login - = PasswordLogin LoginId PlainTextPassword (Maybe CookieLabel) + = PasswordLogin LoginId PlainTextPassword (Maybe CookieLabel) (Maybe ActivationCode) | SmsLogin Phone LoginCode (Maybe CookieLabel) deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform Login) @@ -103,11 +104,19 @@ modelLogin = Doc.defineModel "Login" $ do \to allow targeted revocation of all cookies granted to that \ \specific client." Doc.optional + Doc.property "verification_code" Doc.string' $ do + Doc.description "The login verification code for 2nd factor authentication. Required only if SndFactorPasswordChallenge is enabled for the team/server." + Doc.optional instance ToJSON Login where toJSON (SmsLogin p c l) = object ["phone" .= p, "code" .= c, "label" .= l] - toJSON (PasswordLogin login password label) = - object ["password" .= password, "label" .= label, loginIdPair login] + toJSON (PasswordLogin login password label mbCode) = + object + [ "password" .= password, + "label" .= label, + loginIdPair login, + "verification_code" .= mbCode + ] instance FromJSON Login where parseJSON = withObject "Login" $ \o -> do @@ -117,10 +126,10 @@ instance FromJSON Login where SmsLogin <$> o .: "phone" <*> o .: "code" <*> o .:? "label" Just pw -> do loginId <- parseJSON (Object o) - PasswordLogin loginId pw <$> o .:? "label" + PasswordLogin loginId pw <$> (o .:? "label") <*> (o .:? "verification_code") loginLabel :: Login -> Maybe CookieLabel -loginLabel (PasswordLogin _ _ l) = l +loginLabel (PasswordLogin _ _ l _) = l loginLabel (SmsLogin _ _ l) = l -------------------------------------------------------------------------------- diff --git a/libs/wire-api/src/Wire/API/User/Identity.hs b/libs/wire-api/src/Wire/API/User/Identity.hs index d9b819d9a38..07ca0f4bc93 100644 --- a/libs/wire-api/src/Wire/API/User/Identity.hs +++ b/libs/wire-api/src/Wire/API/User/Identity.hs @@ -50,7 +50,7 @@ module Wire.API.User.Identity where import Control.Applicative (optional) -import Control.Lens ((.~), (?~), (^.)) +import Control.Lens (over, (.~), (?~), (^.)) import Data.Aeson (FromJSON (..), ToJSON (..)) import qualified Data.Aeson as A import qualified Data.Aeson.Types as A @@ -242,13 +242,12 @@ validateEmail = newtype Phone = Phone {fromPhone :: Text} deriving stock (Eq, Ord, Show, Generic) - deriving newtype (ToJSON, S.ToSchema) + deriving (ToJSON, FromJSON, S.ToSchema) via (Schema Phone) -instance FromJSON Phone where - parseJSON (A.String s) = case parsePhone s of - Just p -> return p - Nothing -> fail "Invalid phone number. Expected E.164 format." - parseJSON _ = mempty +instance ToSchema Phone where + schema = + over doc (S.description ?~ "E.164 phone number") $ + fromPhone .= parsedText "PhoneNumber" (maybe (Left "Invalid phone number. Expected E.164 format.") Right . parsePhone) instance ToByteString Phone where builder = builder . fromPhone diff --git a/libs/wire-api/src/Wire/API/User/Profile.hs b/libs/wire-api/src/Wire/API/User/Profile.hs index bdca14b31e6..b5950500fec 100644 --- a/libs/wire-api/src/Wire/API/User/Profile.hs +++ b/libs/wire-api/src/Wire/API/User/Profile.hs @@ -48,7 +48,6 @@ module Wire.API.User.Profile noPict, -- * Swagger - modelUserDisplayName, modelAsset, typeManagedBy, ) @@ -89,12 +88,6 @@ newtype Name = Name mkName :: Text -> Either String Name mkName txt = Name . fromRange <$> checkedEitherMsg @_ @1 @128 "Name" txt -modelUserDisplayName :: Doc.Model -modelUserDisplayName = Doc.defineModel "UserDisplayName" $ do - Doc.description "User name" - Doc.property "name" Doc.string' $ - Doc.description "User name" - instance ToSchema Name where schema = Name <$> fromName .= untypedRangedSchema 1 128 schema diff --git a/libs/wire-api/src/Wire/API/User/Scim.hs b/libs/wire-api/src/Wire/API/User/Scim.hs index e1255157aa9..45b3597f31d 100644 --- a/libs/wire-api/src/Wire/API/User/Scim.hs +++ b/libs/wire-api/src/Wire/API/User/Scim.hs @@ -83,6 +83,8 @@ import Web.Scim.Schema.Schema (Schema (CustomSchema)) import qualified Web.Scim.Schema.Schema as Scim import qualified Web.Scim.Schema.User as Scim import qualified Web.Scim.Schema.User as Scim.User +import Wire.API.Arbitrary (Arbitrary, GenericUniform (..)) +import Wire.API.User.Activation import Wire.API.User.Identity (Email) import Wire.API.User.Profile as BT import qualified Wire.API.User.RichInfo as RI @@ -365,14 +367,18 @@ 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 PlainTextPassword), + -- | User code (sent by email), for 2nd factor to 'createScimTokenPassword' + createScimTokenCode :: !(Maybe ActivationCode) } - deriving (Eq, Show) + deriving (Eq, Show, Generic) + deriving (Arbitrary) via (GenericUniform CreateScimToken) instance A.FromJSON CreateScimToken where parseJSON = A.withObject "CreateScimToken" $ \o -> do createScimTokenDescr <- o A..: "description" createScimTokenPassword <- o A..:? "password" + createScimTokenCode <- o A..:? "verification_code" pure CreateScimToken {..} -- Used for integration tests @@ -380,7 +386,8 @@ instance A.ToJSON CreateScimToken where toJSON CreateScimToken {..} = A.object [ "description" A..= createScimTokenDescr, - "password" A..= createScimTokenPassword + "password" A..= createScimTokenPassword, + "verification_code" A..= createScimTokenCode ] -- | Type used for the response of 'APIScimTokenCreate'. @@ -463,7 +470,8 @@ instance ToSchema CreateScimToken where & type_ .~ Just SwaggerObject & properties .~ [ ("description", textSchema), - ("password", textSchema) + ("password", textSchema), + ("verification_code", textSchema) ] & required .~ ["description"] diff --git a/libs/wire-api/src/Wire/API/VersionInfo.hs b/libs/wire-api/src/Wire/API/VersionInfo.hs new file mode 100644 index 00000000000..7c888e684df --- /dev/null +++ b/libs/wire-api/src/Wire/API/VersionInfo.hs @@ -0,0 +1,27 @@ +-- 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.VersionInfo + ( vinfoSchema, + ) +where + +import Data.Schema +import Imports + +vinfoSchema :: ValueSchema NamedSwaggerDoc v -> ValueSchema NamedSwaggerDoc [v] +vinfoSchema sch = object "VersionInfo" $ field "supported" (array sch) diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/FromJSON.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/FromJSON.hs index f30027e4044..1294ca9bd06 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/FromJSON.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/FromJSON.hs @@ -22,12 +22,11 @@ import Test.Tasty import Test.Tasty.HUnit import Test.Wire.API.Golden.Generated.Invite_user (testObject_Invite_user_2) import Test.Wire.API.Golden.Generated.MemberUpdateData_user -import Test.Wire.API.Golden.Generated.NewConvUnmanaged_user import Test.Wire.API.Golden.Generated.NewOtrMessage_user import Test.Wire.API.Golden.Generated.RmClient_user import Test.Wire.API.Golden.Generated.SimpleMember_user import Test.Wire.API.Golden.Runner -import Wire.API.Conversation (Conversation, MemberUpdate, NewConvManaged, NewConvUnmanaged, OtherMemberUpdate) +import Wire.API.Conversation (Conversation, MemberUpdate, OtherMemberUpdate) import Wire.API.User (NewUser, NewUserPublic) import Wire.API.User.Client (RmClient) @@ -43,25 +42,6 @@ tests = [ (testObject_SimpleMember_user_2, "testObject_SimpleMember_user_2.json"), (testObject_SimpleMember_user_2, "testObject_SimpleMember_user_2-2.json") ], - testGroup - "NewConvUnmanaged" - [ testCase "success" $ - testFromJSONObject - testObject_NewConvUnmanaged_user_1 - "testObject_NewConvUnmanaged_user_1.json", - testCase - "failure" - $ testFromJSONFailureWithMsg - @NewConvUnmanaged - (Just "managed conversations have been deprecated") - "testObject_NewConvUnmanaged_user_2.json" - ], - testCase - "NewConvManaged failure" - $ testFromJSONFailureWithMsg - @NewConvManaged - (Just "only managed conversations are allowed here") - "testObject_NewConvManaged_user_2.json", testCase "RmClient" $ testFromJSONObjects 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 2b0f1dc2e8b..f0f20bc7e3a 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 @@ -113,8 +113,7 @@ import qualified Test.Wire.API.Golden.Generated.NewAssetToken_user import qualified Test.Wire.API.Golden.Generated.NewBotRequest_provider import qualified Test.Wire.API.Golden.Generated.NewBotResponse_provider import qualified Test.Wire.API.Golden.Generated.NewClient_user -import qualified Test.Wire.API.Golden.Generated.NewConvManaged_user -import qualified Test.Wire.API.Golden.Generated.NewConvUnmanaged_user +import qualified Test.Wire.API.Golden.Generated.NewConv_user import qualified Test.Wire.API.Golden.Generated.NewLegalHoldClient_team import qualified Test.Wire.API.Golden.Generated.NewLegalHoldService_team import qualified Test.Wire.API.Golden.Generated.NewOtrMessage_user @@ -190,6 +189,7 @@ import qualified Test.Wire.API.Golden.Generated.ServiceToken_provider import qualified Test.Wire.API.Golden.Generated.Service_provider import qualified Test.Wire.API.Golden.Generated.SimpleMember_user import qualified Test.Wire.API.Golden.Generated.SimpleMembers_user +import qualified Test.Wire.API.Golden.Generated.SndFactorPasswordChallengeAction_user import qualified Test.Wire.API.Golden.Generated.TeamBinding_team import qualified Test.Wire.API.Golden.Generated.TeamContact_user import qualified Test.Wire.API.Golden.Generated.TeamConversationList_team @@ -389,14 +389,10 @@ tests = [ (Test.Wire.API.Golden.Generated.Conversation_user.testObject_Conversation_user_1, "testObject_Conversation_user_1.json"), (Test.Wire.API.Golden.Generated.Conversation_user.testObject_Conversation_user_2, "testObject_Conversation_user_2.json") ], - testGroup "Golden: NewConvUnmanaged_user" $ + testGroup "Golden: NewConv_user" $ testObjects - [ (Test.Wire.API.Golden.Generated.NewConvUnmanaged_user.testObject_NewConvUnmanaged_user_1, "testObject_NewConvUnmanaged_user_1.json"), - (Test.Wire.API.Golden.Generated.NewConvUnmanaged_user.testObject_NewConvUnmanaged_user_3, "testObject_NewConvUnmanaged_user_3.json") - ], - testGroup "Golden: NewConvManaged_user" $ - testObjects - [ (Test.Wire.API.Golden.Generated.NewConvManaged_user.testObject_NewConvManaged_user_1, "testObject_NewConvManaged_user_1.json") + [ (Test.Wire.API.Golden.Generated.NewConv_user.testObject_NewConv_user_1, "testObject_NewConv_user_1.json"), + (Test.Wire.API.Golden.Generated.NewConv_user.testObject_NewConv_user_3, "testObject_NewConv_user_3.json") ], testGroup "Golden: ConversationList_20_28Id_20_2a_20C_29_user" $ testObjects @@ -1211,5 +1207,10 @@ tests = testGroup "Golden: TeamSearchVisibility_team" $ testObjects [(Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_1, "testObject_TeamSearchVisibility_team_1.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_2, "testObject_TeamSearchVisibility_team_2.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_3, "testObject_TeamSearchVisibility_team_3.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_4, "testObject_TeamSearchVisibility_team_4.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_5, "testObject_TeamSearchVisibility_team_5.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_6, "testObject_TeamSearchVisibility_team_6.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_7, "testObject_TeamSearchVisibility_team_7.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_8, "testObject_TeamSearchVisibility_team_8.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_9, "testObject_TeamSearchVisibility_team_9.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_10, "testObject_TeamSearchVisibility_team_10.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_11, "testObject_TeamSearchVisibility_team_11.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_12, "testObject_TeamSearchVisibility_team_12.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_13, "testObject_TeamSearchVisibility_team_13.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_14, "testObject_TeamSearchVisibility_team_14.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_15, "testObject_TeamSearchVisibility_team_15.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_16, "testObject_TeamSearchVisibility_team_16.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_17, "testObject_TeamSearchVisibility_team_17.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_18, "testObject_TeamSearchVisibility_team_18.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_19, "testObject_TeamSearchVisibility_team_19.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_20, "testObject_TeamSearchVisibility_team_20.json")], testGroup "Golden: TeamSearchVisibilityView_team" $ - testObjects [(Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_1, "testObject_TeamSearchVisibilityView_team_1.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_2, "testObject_TeamSearchVisibilityView_team_2.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_3, "testObject_TeamSearchVisibilityView_team_3.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_4, "testObject_TeamSearchVisibilityView_team_4.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_5, "testObject_TeamSearchVisibilityView_team_5.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_6, "testObject_TeamSearchVisibilityView_team_6.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_7, "testObject_TeamSearchVisibilityView_team_7.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_8, "testObject_TeamSearchVisibilityView_team_8.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_9, "testObject_TeamSearchVisibilityView_team_9.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_10, "testObject_TeamSearchVisibilityView_team_10.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_11, "testObject_TeamSearchVisibilityView_team_11.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_12, "testObject_TeamSearchVisibilityView_team_12.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_13, "testObject_TeamSearchVisibilityView_team_13.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_14, "testObject_TeamSearchVisibilityView_team_14.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_15, "testObject_TeamSearchVisibilityView_team_15.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_16, "testObject_TeamSearchVisibilityView_team_16.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_17, "testObject_TeamSearchVisibilityView_team_17.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_18, "testObject_TeamSearchVisibilityView_team_18.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_19, "testObject_TeamSearchVisibilityView_team_19.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_20, "testObject_TeamSearchVisibilityView_team_20.json")] + testObjects [(Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_1, "testObject_TeamSearchVisibilityView_team_1.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_2, "testObject_TeamSearchVisibilityView_team_2.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_3, "testObject_TeamSearchVisibilityView_team_3.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_4, "testObject_TeamSearchVisibilityView_team_4.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_5, "testObject_TeamSearchVisibilityView_team_5.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_6, "testObject_TeamSearchVisibilityView_team_6.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_7, "testObject_TeamSearchVisibilityView_team_7.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_8, "testObject_TeamSearchVisibilityView_team_8.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_9, "testObject_TeamSearchVisibilityView_team_9.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_10, "testObject_TeamSearchVisibilityView_team_10.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_11, "testObject_TeamSearchVisibilityView_team_11.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_12, "testObject_TeamSearchVisibilityView_team_12.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_13, "testObject_TeamSearchVisibilityView_team_13.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_14, "testObject_TeamSearchVisibilityView_team_14.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_15, "testObject_TeamSearchVisibilityView_team_15.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_16, "testObject_TeamSearchVisibilityView_team_16.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_17, "testObject_TeamSearchVisibilityView_team_17.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_18, "testObject_TeamSearchVisibilityView_team_18.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_19, "testObject_TeamSearchVisibilityView_team_19.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_20, "testObject_TeamSearchVisibilityView_team_20.json")], + testGroup "Golden: SndFactorPasswordChallengeAction_user" $ + testObjects + [ (Test.Wire.API.Golden.Generated.SndFactorPasswordChallengeAction_user.testObject_SndFactorPasswordChallengeAction_user_1, "testObject_SndFactorPasswordChallengeAction_user_1"), + (Test.Wire.API.Golden.Generated.SndFactorPasswordChallengeAction_user.testObject_SndFactorPasswordChallengeAction_user_2, "testObject_SndFactorPasswordChallengeAction_user_2") + ] ] diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/ConvTeamInfo_user.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/ConvTeamInfo_user.hs index a73ec7f8435..bbd2b16e299 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/ConvTeamInfo_user.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/ConvTeamInfo_user.hs @@ -19,12 +19,11 @@ module Test.Wire.API.Golden.Generated.ConvTeamInfo_user where import Data.Id (Id (Id)) import qualified Data.UUID as UUID (fromString) -import Imports (Bool (False), fromJust) +import Imports (fromJust) import Wire.API.Conversation (ConvTeamInfo (..)) testObject_ConvTeamInfo_user_1 :: ConvTeamInfo testObject_ConvTeamInfo_user_1 = ConvTeamInfo - { cnvTeamId = Id (fromJust (UUID.fromString "0000003f-0000-0059-0000-002200000028")), - cnvManaged = False + { cnvTeamId = Id (fromJust (UUID.fromString "0000003f-0000-0059-0000-002200000028")) } diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Event_team.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Event_team.hs index 74a885e4d00..e9a1ca0ec3a 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Event_team.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Event_team.hs @@ -20,6 +20,7 @@ module Test.Wire.API.Golden.Generated.Event_team where import Control.Lens ((.~)) +import Data.ByteString.Conversion (fromByteString') import Data.Id (Id (Id)) import Data.Range (unsafeRange) import qualified Data.UUID as UUID (fromString) @@ -115,11 +116,7 @@ testObject_Event_team_2 = ( unsafeRange ("i5\EOT\1002575\1097973\1066101\&1u\1105430\&1\41840U*/*\999102\1001662\DC3\994167d\1096830\&4uG\173887\fUh09\\\1028574\vPy\t\171003\SI\GS0bV\CAN]\17049\96404\15202\RS\SYNX\ESC3[\CANf\NAK") ), - _iconUpdate = - Just - ( unsafeRange - ("G*~\1098568\62228\EOT\FS\36117%s\DC3\57890|\1092250ZS\989493jf\119998-w\1113299{]R\aNwI\a\1007357?Z\1019937x\65703*\t\SI33\1091562\&3-j\DC2\170440\STXp,n.)*\1073149e=\100962n\1063403\159370aK\ffeF\ETBx\149218\GSX_\1023100R\1102760K\70812gK\1050395\&2J\SYNM\99409-+\1055216uW^Xwjlt\fGy;&\984905\ESC\1033170\DC2^\ETB8\9010\62641wtq\1083210\12238\983428n1~k\bk61R!\1018162\1084522\1075186\1074814w\183828x\DC4\1097642\34650\1078763M05\ENQZY#\92897\RS(\1816\1070299{'W\DC4\SUB\1064958?n\EOTAhT-\CANa;\1013791CV\"") - ), + _iconUpdate = fromByteString' "3-1-f595b8ed-6dcf-41f2-8a2f-f662a9c0fce4", _iconKeyUpdate = Just (unsafeRange ("\131355Pp\1067299\987603\ENQS\22773S\ACK\NAKmM\19084\&0\19257\31361$rL,XvJ")) } @@ -152,11 +149,7 @@ testObject_Event_team_4 = ( unsafeRange ("d\SI\172132@o\988798s&na\136232\1090952\149487|\83503\1016948/\989099v\NAKu\DC2f\1093640\1011936KC\47338\1066997\1059386\&9_\v_^\1045398K\155463\SO Y*T\CAN\1086598<\1056774>\171907\4929\rt\1038163\1072126w2E\127366hS>\ACK_PQN,Vk\SYN\1083970=90\EM2e\984550\USVA!\EM\FS\EOTe;\189780\&1\EM\1004319=\DC3\1095917@o\1016975\NAKkR\1022510l^W)W=\1026382\40628\SYNrrN\144727\1026366S\SI^,\ETB5Q&z8D[\15759\ETBbas\SUBY\RSR2\140794\1012833G+'Q+\996998") - ), + _iconUpdate = fromByteString' "3-1-f595b8ed-6dcf-41f2-8a2f-f662a9c0fce4", _iconKeyUpdate = Just ( unsafeRange 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 7e35d440095..45c0896958d 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 @@ -23,6 +23,7 @@ import Data.Handle (Handle (Handle, fromHandle)) import Data.Misc (PlainTextPassword (PlainTextPassword)) import Imports (Maybe (Just, Nothing)) import Wire.API.User (Email (Email, emailDomain, emailLocal), Phone (Phone, fromPhone)) +import Wire.API.User.Activation (ActivationCode (..)) import Wire.API.User.Auth ( CookieLabel (CookieLabel, cookieLabelText), Login (..), @@ -38,6 +39,7 @@ testObject_Login_user_1 = "\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"})) + Nothing testObject_Login_user_2 :: Login testObject_Login_user_2 = @@ -54,6 +56,7 @@ testObject_Login_user_3 = "&\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"})) + (Just (ActivationCode "123456")) testObject_Login_user_4 :: Login testObject_Login_user_4 = @@ -76,6 +79,7 @@ testObject_Login_user_5 = "\120347\184756DU\1035832hp\1006715t~\DC2\SOH\STX*\1053210y1\1078382H\173223{e\\S\SO?c_7\t\DC4X\135187\&6\172722E\100168j\SUB\t\SYN\1088511>HO]60\990035\ETX\"+w,t\1066040\ak(b%u\151197`>b\1028272e\ACKc\151393\1107996)\12375\&7\1082464`\186313yO+v%\1033664\rc<\65764\&2>8u\1094258\1080669\1113623\75033a\179193\NAK=\EOT\1077021\&8R&j\1042630\ESC\t4sj-\991835\40404n\136765\1064089N\GS\\\1026123\72288\&5\r\97004(P!\DEL\29235\26855\b\1067772Mr~\65123\EMjt>Z\GS~\140732A\1031358\SO\\>\DC16\">%\45860\1084751I@u5\187891\vrY\r;7\1071052#\1078407\1016286\CAN'\63315\1041397\EM_I_zY\987300\149441\EMd\1039844cd\DEL\1061999\136326Cp3\26325\GSXj\n\46305jy\44050\58825\t-\19065\43336d\1046547L\SUBYF\ACKPOL\54766\DC2\DC1\DC1\DC2*\rH\DLE(?\DC3F\25820\DLE\r]\1069451j\170177 @\ENQT\1100685s\FSF2\NAK]8\a\DC3!\NAKW\176469\1110834K\1025058\1112222_%\1001818\1113069'\1098149\70360(#\SOHky\t\ETB!\17570\NAK\DC4\ESC{\119317U2LS'" ) (Just (CookieLabel {cookieLabelText = "LGz%\119949j\f\RS/\SOH"})) + (Just (ActivationCode "123456")) testObject_Login_user_6 :: Login testObject_Login_user_6 = @@ -85,6 +89,7 @@ testObject_Login_user_6 = "K?)V\148106}_\185335\1060952\fJ3!\986581\1062221\51615\166583\1071064\a\1015675\SOH7\\#z9\133503\1081163\985690\1041362\EM\DC3\156174'\r)~Ke9+\175606\175778\994126M\1099049\"h\SOHTh\EOT`;\ACK\1093024\ENQ\1026474'e{\FSv\40757\US\143355*\16236\1076902\52767:E]:R\1093823K}l\1111648Y\51665\1049318S~\EOT#T\1029316\&1hIWn\v`\45455Kb~\ESC\DLEdT\FS\SI\1092141f\ETBY7\DEL\RS\131804\t\998971\13414\48242\GSG\DC3BH#\DEL\\RAd\166099g\1072356\1054332\SIk&\STXE\22217\FS\FS\FS$t\1001957:O\1098769q}_\1039296.\SOH\DC4\STX\157262c`L>\1050744l\1086722m'BtB5\1003280,t\"\1066340\&9(#\ENQ4\SIIy>\1031158\1100542\GSbf\"i\ETB\14367a\1086113C@\1078844\1092137\32415\NAK\999161\23344*N\SYN\ESC:iXibA\136851\169508q\1048663]:9r\63027\73801\NUL\1050763\USCN\US\147710\1048697\1016861eR\RSZbD5!8N\ESCV\7344\ACK\173064\SUBuz\1053950\188308~\ESC\SI%{3I/F\25232/DMS\US>o\187199\63000Z\1108766\GS[K\184801\94661\1088369\995346\ESCO-4\CAN\US\FSZp" ) (Just (CookieLabel {cookieLabelText = "\1014596'\998013KW\\\NUL\DC4"})) + (Just (ActivationCode "123456")) testObject_Login_user_7 :: Login testObject_Login_user_7 = @@ -94,6 +99,7 @@ testObject_Login_user_7 = "&\991818\1023244\83352\STXJ<-~\STX>\v\74228\151871\&5QN\53968\166184ql\NAK\74290\&3}{\DC3\173242S\22739;\t7\183958_F~D*f\1049940)\1067330-9\20699\&7GK= %\RS@kOF#\179945\1094401\124994\&8_\42309\GSL\37698\ETX\1047946\&0Wl1A`LYz\USy\20728\SUBo\ESC[\DC4\bt\66640a\ETXs~\USF\175140G`$\vG\DC1\1044421\128611/\1014458C>\SI" ) (Just (CookieLabel {cookieLabelText = "\SO\NAKeC/"})) + (Just (ActivationCode "123456")) testObject_Login_user_8 :: Login testObject_Login_user_8 = @@ -103,6 +109,7 @@ testObject_Login_user_8 = "z>\1088515\1024903/\137135\1092812\b%$\1037736\143620:}\t\CAN\1058585\1044157)\12957\1005180s\1006270\CAN}\40034\EM[\41342\vX#VG,df4\141493\&8m5\46365OTK\144460\37582\DEL\44719\9670Z\"ZS\ESCms|[Q%\1088673\ENQW\\\1000857C\185096+\1070458\4114\17825v\180321\41886){\1028513\DEL\143570f\187156}:X-\b2N\EM\USl\127906\49608Y\1071393\1012763r2.1\49912\EOT+\137561\DC3\145480]'\1028275s\997684\42805.}\185059o\992118X\132901\11013\r\SUBNq6\1019605'\fd\RS\14503\1097628,:%\t\151916\73955QD\1086880\ESC(q4KDQ2zcI\DLE>\EM5\993596\&1\fBkd\DC3\ACK:F:\EOT\100901\11650O N\FS,N\1054390\1000247[h\DEL9\5932:xZ=\f\1085312\DC3u\RS\fe#\SUB^$lkx\32804 \rr\SUBJ\1013606\1017057\FSR][_5\NAK\58351\11748\35779\&5\24821\1055669\996852\37445K!\1052768eRR%\32108+h~1\993198\35871lTzS$\DLE\1060275\"*\1086839pmRE\DC3(\US^\8047Jc\10129\1071815i\n+G$|\993993\156283g\FS\fgU3Y\119068\ACKf)\1093562\SYN\78340\1100638/\NULPi\43622{\1048095j\1083269\FS9\132797\1024684\32713w$\45599\126246)Si\167172\29311FX\1057490j{`\44452`\999383\159809\&4u%\1070378P*\1057403\25422\DELC\RSR\SYN-\51098\1011541g\68666:S>c\15266\132940\DLEY\1066831~a)YW_J\1063076P\a+ U\1084883j\EMk\SOH\1096984\DC1\18679e\172760\175328,\5135g@\DC2\GSHXl.\ETB\153793\&2\DC3mY\1054891\tv?L8L\1074044N\133565\nb1j\1044024\148213xfQ=\\\ENQe\995818\1023862U\DC2p{\SO\1099404jd^@U\994269tP.\DC2Y%R`a\r\160622\&7}HnUf\132856m^7:\NAK=\52348>l\95313hwp27\149950jE\fx=!.\DC3]Ar\tw\DC4&\SUBk\194572s\1042820\4498I\146071\61461\1060645dsY\DLE\181922dX.\146295i]\151113\1028288\rWS\USU\1098732\SUB\49884\1083906\DLE\STXN~-\SO6\190031\1110322\\O\185165Jc\1052359\1071278\NULHSo\DLE-W\DC36\170321I\1068712)\99800={\99796h\27961\61707M\1022570FwJQ\1111976ck\SUB\CAN|UV-\NAK\SOH|\DC4;\f\156907\145795\ENQS\NAK.B\"D\163007#o*\126577\32988m\RS\1049834B3Gg;\DC1\\\180659\1098926\ENQ B^\SI\152630$e\39220\170037>fMgC\187276,o\128488\\?\1033955~/s\SOH?MMc;D18Ne\EOT\CAN)*\STX\GS\16268\f\RSUc\EOTV9&c\3517\a\986228a'PPG\100445\179638>[\3453\&2\64964Xc\131306[0\1002646\b\99652B\DC1[\1029237\GS\19515\US\EMs-u\ETBs\1067133\1005008\161663n\1072320?\1045643ck\DC48XC\174289\RSI2\2862\STX\DLEM\ESC\n?<\\\DC3E\72219\GS\n$cyS\136198!,\v9\ETB/\DC1\62324?P\ETB\41758\DC2\999537~\1058761W-W4K8.\DC27\EML\1078049h\SI}t+H\SUB\ESCX\120523s\EOTt\177703taa\GS\f\152365(v\1024552M\ESCvg3P1\1032835\57603]g\3933\&4T\NAK$\38212);\\8\1109165\nK\NAK}D'^fJ'\143205e\174052\39597!\EM.\DC2{\\CEp\1045384\ETBk_\1083904\18397\164138\1063468]MG$\187650[E\1112126\b\1073487{b\50650\ESC^b@W\NAK$\FS<\1023895&\155992R\ACKJ\SI\1093108\1101041\41438n\1007134\&8]\148288\ENQ}|k\STX\CANQ\USI\a\CANDZ\1062877\NUL\50197rb\18947\&3G%\FS\162081\EOT\NAK4YB0-i\1018065IM\1073908[\1111554:Cr$\99636)L\136837W\40897.x;\41461\1030711\995525\USkb\CANY9)\SYN4\SI\1103461Av.\r\f\1061861\&9{\SO\ETBP\f\33538u\r-9cB4\1016091G\RS\22817\1014740r\128247HcsPm\59419s\120987!|J<\DLE8\FS[\NAKWYAK\75011^\987050c3\1042176\aC\ETX\ETB\1053739Y\DC4f\ACK\1060945!\1032209:RlQ!BX\f=\1070694f\151362\DEL\113727O\ETX\\\"\53275B<\RSLV4g%3\1098063\ACK`\NAK>\n\44626kp\986102\171479\DEL\60526H\20888lyJ\DC2)\1055149(\1027099A\FSh\EOTj\35251\DC4M\ESCP-q\bn\CAN\143310~\GS\EM\"o\21512%*e2\165597L\1023807sy\152913\&2m\GS\1049046{EG]\DC16B+{\983622IYa\1008153\&5,<\ESCX\f\SI\186613\153744E\134407\1011088L<\EMdUO\ETB\SUBZYm\ACK\1086320R\SUB\991954\DC3^\60967s\fu_g\EM?i~}\DELV2\148681R\FS\EOT3j\45841m\1542\1100884\n7S\SIT5j\170914\SI\1015133\141587h\182480Q\146618\59914\DEL\NAKZM\1110574\&02f\129340l!*\SOH\1027033\SOH\1070384\1094775\t\72805\ESCa:q UKEN\RS-\n\ETXH\22365a\1074707\b\37494\"\1035508\149695\1033139R4\ETX\DLE\FS\STX\1004750%\"@\1009369\&6=/x\NULP\EOT\174871/\190041\f\f\1005146?*\fIcKW\DELQ\"\1001726P*\1095849\&6=d\n\157680\RS\1087962\EOT\DC2I\47501U\b=Pc\DLE" ) (Just (CookieLabel {cookieLabelText = "\SI\128787-\125004:\136001\39864\ACK\SO"})) + (Just (ActivationCode "123456")) testObject_Login_user_13 :: Login testObject_Login_user_13 = @@ -187,3 +196,4 @@ testObject_Login_user_20 = "ryzP\DC39\11027-1A)\b,u\8457j~0\1090580\1033743\fI\170254er\DC4V|}'kzG%A;3H\amD\STXU1\NUL^\1043764\DLEO&5u\EOT\SUB\167046\&0A\996223X\DC2\FS7fEt\97366rPvytT\136915!\100713$Q|BI+EM5\NAK\t\DELRKrE\DLE\US\r?.\STX|@1v^\vycpu\n$\DC2\186675\131718-Q\151081\n\r\1033981\68381O\ENQ*\68660Z\USo\EOTn\188565%&\DC3Me*\STX;\DLE034\nv\NAK\140398(\1075494\990138n@\1108345|\48421d\n*\SI\NUL}\NAKA!\1045882\1036527Hx\ETB3\STX{#T|5|GC\1089070z.\USN\1080851\22324\vu\SYN~LP\147583CV\SO q\151952\DC2e8h\USg\1019358;\f\996107\1108688At\1022346)\USG\DC3\166541\39337|\1042043\SI\134073\EOTc~6\DLE:u\165393##^\nn{d\CAN\ng\16237\ESC\US\US~A8};T\RS\NAK)&\b\ACK\1106044\GS(\DC3u;\1094683;=e\1051162\"\40669vCt)o\987006m\43912\78088l1+\1036284[\STXFLx\1080932:\1031973\992752\&71/kE\93787p\DC4Ij\ETB\194985&\SUB^\FSl1\ACK\1019548\ETXW,+3\128058\95671\DLE7\59727\&7rG'\1078914JC9M\1053804\SYN\DC2\44350>~\1016308Y\1062059=i-\fS\172440\156520K2-@\ENQ\f\1108851_1D-&\128386lR\187248/\993988$:\31415:\52267Dg\1015243O\1010173\170117\SO\179807\&2z\NAKq\141547c\FSliJ{\1055925\1060070'BL\168670;\STX\1046844\18443B\NUL\7839b\1072569:w\1108016Ad\SUB6\NAKo\55279\nsPWM{\ETXfW\1018373JT\1021361$\989069\54608\190318\173259u4\1103286\t\34021\1039458\"\153264UM\1084148\1095406\34105\1105325\t\nIn'\1070532\21097\16091\EM\DC1<\v\bW\SI}\141807\b\1072339\1035283\GS`\1094467x\NUL\986937K\FSj\1079287\DC1\SI\168992d\991620k4\SUB\1009876\49943^\58464\1052547\1016875i2=$:[f\1064579\DC2n\NAKJ<=\2028\SI!z\1105364\SON\NAK\EM\180748V\1024876CQ_G\nY#ky\132779k\DC3\ENQ}OC\96566}~M\EMp\ETX\RSx\b\183962\1073008\b8/\DC4?\1081654B\1025870\EOT\SO\DELU\1020905\ESC=%\51062J\168855\ETB\992593\990312\985186\to\1101036X_@@\45111\43952$" ) (Just (CookieLabel {cookieLabelText = "\1055424\r9\998420`\NAKx"})) + (Just (ActivationCode "123456")) diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewConvManaged_user.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewConvManaged_user.hs deleted file mode 100644 index 2d760ecb05c..00000000000 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewConvManaged_user.hs +++ /dev/null @@ -1,71 +0,0 @@ -{-# 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.Generated.NewConvManaged_user where - -import Data.Domain -import Data.Id (Id (Id)) -import Data.Misc (Milliseconds (Ms, ms)) -import qualified Data.Set as Set (fromList) -import qualified Data.UUID as UUID (fromString) -import Imports (Bool (True), Maybe (Just, Nothing), fromJust) -import Wire.API.Conversation - ( AccessRoleV2 (..), - ConvTeamInfo (ConvTeamInfo, cnvManaged, cnvTeamId), - NewConv - ( NewConv, - newConvAccess, - newConvAccessRoles, - newConvMessageTimer, - newConvName, - newConvQualifiedUsers, - newConvReceiptMode, - newConvTeam, - newConvUsers, - newConvUsersRole - ), - NewConvManaged (..), - ReceiptMode (ReceiptMode, unReceiptMode), - ) -import Wire.API.Conversation.Role (parseRoleName) - -testDomain :: Domain -testDomain = Domain "test.example.com" - -testObject_NewConvManaged_user_1 :: NewConvManaged -testObject_NewConvManaged_user_1 = - NewConvManaged - ( NewConv - { newConvUsers = [], - newConvQualifiedUsers = [], - newConvName = Nothing, - newConvAccess = Set.fromList [], - newConvAccessRoles = Just (Set.fromList [TeamMemberAccessRole, GuestAccessRole]), - newConvTeam = - Just - ( ConvTeamInfo - { cnvTeamId = Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000200000000")), - cnvManaged = True - } - ), - newConvMessageTimer = Just (Ms {ms = 193643728192048}), - newConvReceiptMode = Just (ReceiptMode {unReceiptMode = 4}), - newConvUsersRole = fromJust (parseRoleName "37q9eeybycp5972td4oo9_r7y16eh6n67z5spda8sffy8qv") - } - ) diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewConvUnmanaged_user.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewConvUnmanaged_user.hs deleted file mode 100644 index 69046ee0d45..00000000000 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewConvUnmanaged_user.hs +++ /dev/null @@ -1,122 +0,0 @@ -{-# 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.Generated.NewConvUnmanaged_user where - -import Data.Domain (Domain (Domain)) -import Data.Id (Id (Id)) -import Data.Misc (Milliseconds (Ms, ms)) -import Data.Qualified (Qualified (Qualified)) -import qualified Data.Set as Set (fromList) -import qualified Data.UUID as UUID (fromString) -import Imports -import Wire.API.Conversation - ( Access (CodeAccess, InviteAccess, LinkAccess, PrivateAccess), - AccessRoleV2 (..), - ConvTeamInfo (ConvTeamInfo, cnvManaged, cnvTeamId), - NewConv - ( NewConv, - newConvAccess, - newConvAccessRoles, - newConvMessageTimer, - newConvName, - newConvQualifiedUsers, - newConvReceiptMode, - newConvTeam, - newConvUsers, - newConvUsersRole - ), - NewConvUnmanaged (..), - ReceiptMode (ReceiptMode, unReceiptMode), - ) -import Wire.API.Conversation.Role (parseRoleName) - -testDomain :: Domain -testDomain = Domain "testdomain.example.com" - -testObject_NewConvUnmanaged_user_1 :: NewConvUnmanaged -testObject_NewConvUnmanaged_user_1 = - NewConvUnmanaged - ( NewConv - { newConvUsers = - [ Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000001")), - Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000000")) - ], - newConvQualifiedUsers = [], - newConvName = Nothing, - newConvAccess = Set.fromList [PrivateAccess, InviteAccess], - newConvAccessRoles = Just $ Set.fromList [TeamMemberAccessRole, GuestAccessRole], - newConvTeam = - Just - ( ConvTeamInfo - { cnvTeamId = Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000000")), - cnvManaged = False - } - ), - newConvMessageTimer = Just (Ms {ms = 3320987366258987}), - newConvReceiptMode = Just (ReceiptMode {unReceiptMode = 1}), - newConvUsersRole = fromJust (parseRoleName "8tp2gs7b6") - } - ) - -testObject_NewConvUnmanaged_user_2 :: NewConvUnmanaged -testObject_NewConvUnmanaged_user_2 = - NewConvUnmanaged - ( NewConv - { newConvUsers = [], - newConvQualifiedUsers = [Qualified (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000001"))) testDomain], - newConvName = Just "\128527\1061495", - newConvAccess = Set.fromList [], - newConvAccessRoles = Nothing, - newConvTeam = - Just - ( ConvTeamInfo - { cnvTeamId = Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000001")), - cnvManaged = True - } - ), - newConvMessageTimer = Just (Ms {ms = 2406292360203739}), - newConvReceiptMode = Just (ReceiptMode {unReceiptMode = -1}), - newConvUsersRole = - fromJust - ( parseRoleName - "vmao7psxph3fenvbpsu1u57fns5pfo53d67k98om378rnxr0crcpak_mpspn8q_3m1b02n2n133s1d7q5w3qgmt_5e_dgtvzon8an7dtauiecd32" - ) - } - ) - -testObject_NewConvUnmanaged_user_3 :: NewConvUnmanaged -testObject_NewConvUnmanaged_user_3 = - NewConvUnmanaged - ( NewConv - { newConvUsers = [], - newConvQualifiedUsers = [], - newConvName = Nothing, - newConvAccess = Set.fromList [InviteAccess, LinkAccess, CodeAccess], - newConvAccessRoles = Just (Set.fromList [TeamMemberAccessRole, GuestAccessRole]), - newConvTeam = Nothing, - newConvMessageTimer = Nothing, - newConvReceiptMode = Nothing, - newConvUsersRole = - fromJust - ( parseRoleName - "y3otpiwu615lvvccxsq0315jj75jquw01flhtuf49t6mzfurvwe3_sh51f4s257e2x47zo85rif_xyiyfldpan3g4r6zr35rbwnzm0k" - ) - } - ) diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewConv_user.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewConv_user.hs new file mode 100644 index 00000000000..e756106180a --- /dev/null +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewConv_user.hs @@ -0,0 +1,72 @@ +{-# 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.Generated.NewConv_user where + +import Data.Domain (Domain (Domain)) +import Data.Id (Id (Id)) +import Data.Misc (Milliseconds (Ms, ms)) +import qualified Data.Set as Set (fromList) +import qualified Data.UUID as UUID (fromString) +import Imports +import Wire.API.Conversation +import Wire.API.Conversation.Role + +testDomain :: Domain +testDomain = Domain "testdomain.example.com" + +testObject_NewConv_user_1 :: NewConv +testObject_NewConv_user_1 = + NewConv + { newConvUsers = + [ Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000001")), + Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000000")) + ], + newConvQualifiedUsers = [], + newConvName = Nothing, + newConvAccess = Set.fromList [PrivateAccess, InviteAccess], + newConvAccessRoles = Just $ Set.fromList [TeamMemberAccessRole, GuestAccessRole], + newConvTeam = + Just + ( ConvTeamInfo + { cnvTeamId = Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000000")) + } + ), + newConvMessageTimer = Just (Ms {ms = 3320987366258987}), + newConvReceiptMode = Just (ReceiptMode {unReceiptMode = 1}), + newConvUsersRole = fromJust (parseRoleName "8tp2gs7b6") + } + +testObject_NewConv_user_3 :: NewConv +testObject_NewConv_user_3 = + NewConv + { newConvUsers = [], + newConvQualifiedUsers = [], + newConvName = Nothing, + newConvAccess = Set.fromList [InviteAccess, LinkAccess, CodeAccess], + newConvAccessRoles = Just (Set.fromList [TeamMemberAccessRole, GuestAccessRole]), + newConvTeam = Nothing, + newConvMessageTimer = Nothing, + newConvReceiptMode = Nothing, + newConvUsersRole = + fromJust + ( parseRoleName + "y3otpiwu615lvvccxsq0315jj75jquw01flhtuf49t6mzfurvwe3_sh51f4s257e2x47zo85rif_xyiyfldpan3g4r6zr35rbwnzm0k" + ) + } diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/SndFactorPasswordChallengeAction_user.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/SndFactorPasswordChallengeAction_user.hs new file mode 100644 index 00000000000..4daba80e3ae --- /dev/null +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/SndFactorPasswordChallengeAction_user.hs @@ -0,0 +1,28 @@ +{-# 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.Generated.SndFactorPasswordChallengeAction_user where + +import Wire.API.User (SndFactorPasswordChallengeAction (..)) + +testObject_SndFactorPasswordChallengeAction_user_1 :: SndFactorPasswordChallengeAction +testObject_SndFactorPasswordChallengeAction_user_1 = GenerateScimToken + +testObject_SndFactorPasswordChallengeAction_user_2 :: SndFactorPasswordChallengeAction +testObject_SndFactorPasswordChallengeAction_user_2 = Login diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/TeamConversationList_team.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/TeamConversationList_team.hs index cd03d982624..3bdf75b2bea 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/TeamConversationList_team.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/TeamConversationList_team.hs @@ -21,464 +21,464 @@ module Test.Wire.API.Golden.Generated.TeamConversationList_team where import Data.Id (Id (Id)) import qualified Data.UUID as UUID (fromString) -import Imports (Bool (False, True), fromJust) +import Imports import Wire.API.Team.Conversation (TeamConversationList, newTeamConversation, newTeamConversationList) testObject_TeamConversationList_team_1 :: TeamConversationList testObject_TeamConversationList_team_1 = ( newTeamConversationList - [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000012-0000-0018-0000-00260000002b")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000002d-0000-0063-0000-006900000013")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000002e-0000-003c-0000-00440000000e")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000067-0000-003a-0000-006100000049")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000005f-0000-0003-0000-005a00000075")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000007f-0000-0018-0000-00250000007c")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000006a-0000-0020-0000-001a00000073")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000002e-0000-006a-0000-005f00000003")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000034-0000-0021-0000-00330000005b")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000048-0000-0011-0000-002a00000004")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000031-0000-0018-0000-00060000001a")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000056-0000-000e-0000-004300000028")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000067-0000-007f-0000-003600000031")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000066-0000-0053-0000-006a00000034")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000000f-0000-0071-0000-001b00000057")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000032-0000-0035-0000-00210000003b")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000004-0000-000d-0000-002100000067")))) (False)) + [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000012-0000-0018-0000-00260000002b"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000002d-0000-0063-0000-006900000013"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000002e-0000-003c-0000-00440000000e"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000067-0000-003a-0000-006100000049"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000005f-0000-0003-0000-005a00000075"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000007f-0000-0018-0000-00250000007c"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000006a-0000-0020-0000-001a00000073"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000002e-0000-006a-0000-005f00000003"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000034-0000-0021-0000-00330000005b"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000048-0000-0011-0000-002a00000004"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000031-0000-0018-0000-00060000001a"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000056-0000-000e-0000-004300000028"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000067-0000-007f-0000-003600000031"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000066-0000-0053-0000-006a00000034"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000000f-0000-0071-0000-001b00000057"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000032-0000-0035-0000-00210000003b"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000004-0000-000d-0000-002100000067"))))) ] ) testObject_TeamConversationList_team_2 :: TeamConversationList testObject_TeamConversationList_team_2 = ( newTeamConversationList - [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000064-0000-0045-0000-007d00000023")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000000d-0000-0080-0000-00550000001b")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000004e-0000-0053-0000-004600000056")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000006e-0000-003c-0000-003200000071")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000067-0000-002f-0000-007a0000007f")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000000a-0000-0027-0000-004e0000005f")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000006-0000-0026-0000-000000000054")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000006e-0000-007e-0000-001600000035")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000002c-0000-0057-0000-007e00000070")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000074-0000-0053-0000-005f00000006")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000028-0000-005c-0000-00050000006b")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000018-0000-0061-0000-004a00000024")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000005a-0000-007b-0000-000800000033")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000027-0000-0043-0000-006800000068")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000056-0000-0018-0000-003f00000001")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000014-0000-0066-0000-00440000001b")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000007f-0000-0071-0000-007f0000001b")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000018-0000-004d-0000-005000000080")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000018-0000-003e-0000-00140000006e")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000033-0000-005c-0000-001e0000000d")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000004d-0000-0021-0000-00360000000e")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000057-0000-003f-0000-003700000065")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000006f-0000-003e-0000-000300000051")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000038-0000-0025-0000-00030000003b")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000003c-0000-0069-0000-005000000035")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000005f-0000-006b-0000-00260000004e")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000001c-0000-001c-0000-00530000000c")))) (False)) + [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000064-0000-0045-0000-007d00000023"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000000d-0000-0080-0000-00550000001b"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000004e-0000-0053-0000-004600000056"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000006e-0000-003c-0000-003200000071"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000067-0000-002f-0000-007a0000007f"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000000a-0000-0027-0000-004e0000005f"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000006-0000-0026-0000-000000000054"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000006e-0000-007e-0000-001600000035"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000002c-0000-0057-0000-007e00000070"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000074-0000-0053-0000-005f00000006"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000028-0000-005c-0000-00050000006b"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000018-0000-0061-0000-004a00000024"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000005a-0000-007b-0000-000800000033"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000027-0000-0043-0000-006800000068"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000056-0000-0018-0000-003f00000001"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000014-0000-0066-0000-00440000001b"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000007f-0000-0071-0000-007f0000001b"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000018-0000-004d-0000-005000000080"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000018-0000-003e-0000-00140000006e"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000033-0000-005c-0000-001e0000000d"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000004d-0000-0021-0000-00360000000e"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000057-0000-003f-0000-003700000065"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000006f-0000-003e-0000-000300000051"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000038-0000-0025-0000-00030000003b"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000003c-0000-0069-0000-005000000035"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000005f-0000-006b-0000-00260000004e"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000001c-0000-001c-0000-00530000000c"))))) ] ) testObject_TeamConversationList_team_3 :: TeamConversationList testObject_TeamConversationList_team_3 = ( newTeamConversationList - [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000046-0000-0026-0000-005600000014")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000006b-0000-0042-0000-002c00000074")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000006d-0000-006d-0000-006100000027")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000079-0000-0024-0000-004600000011")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000044-0000-0005-0000-003800000008")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000052-0000-005e-0000-00200000001a")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000009-0000-0038-0000-001b00000065")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000029-0000-0045-0000-004500000078")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000001e-0000-0036-0000-006400000045")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000050-0000-0066-0000-000500000075")))) (False)) + [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000046-0000-0026-0000-005600000014"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000006b-0000-0042-0000-002c00000074"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000006d-0000-006d-0000-006100000027"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000079-0000-0024-0000-004600000011"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000044-0000-0005-0000-003800000008"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000052-0000-005e-0000-00200000001a"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000009-0000-0038-0000-001b00000065"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000029-0000-0045-0000-004500000078"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000001e-0000-0036-0000-006400000045"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000050-0000-0066-0000-000500000075"))))) ] ) testObject_TeamConversationList_team_4 :: TeamConversationList testObject_TeamConversationList_team_4 = ( newTeamConversationList - [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000076-0000-0038-0000-003c00000043")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000046-0000-001f-0000-005800000080")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000023-0000-0070-0000-006f00000077")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000006-0000-0031-0000-004700000053")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000057-0000-0041-0000-001600000013")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000007b-0000-003c-0000-004800000063")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000028-0000-0009-0000-004c00000009")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000001a-0000-007b-0000-00460000007f")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000052-0000-002e-0000-001000000064")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000003d-0000-002a-0000-00290000007b")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000004-0000-0033-0000-00780000005e")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000006b-0000-007f-0000-001d0000002c")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000017-0000-0079-0000-001c00000066")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000002f-0000-0024-0000-001000000074")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000010-0000-000c-0000-001700000046")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000003-0000-0049-0000-003100000022")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000011-0000-0051-0000-003300000061")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000003e-0000-0077-0000-004c00000022")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000007e-0000-0048-0000-007200000056")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000006f-0000-0007-0000-00190000004f")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000002d-0000-0048-0000-001c0000007e")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000004c-0000-0071-0000-007a00000071")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000006-0000-0002-0000-002000000068")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000002e-0000-0037-0000-005e00000027")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000056-0000-006d-0000-004d00000024")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000004a-0000-0038-0000-001e0000003b")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000033-0000-001a-0000-004a0000001a")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000001a-0000-0070-0000-007000000019")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000006b-0000-0013-0000-004a00000018")))) (True)) + [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000076-0000-0038-0000-003c00000043"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000046-0000-001f-0000-005800000080"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000023-0000-0070-0000-006f00000077"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000006-0000-0031-0000-004700000053"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000057-0000-0041-0000-001600000013"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000007b-0000-003c-0000-004800000063"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000028-0000-0009-0000-004c00000009"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000001a-0000-007b-0000-00460000007f"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000052-0000-002e-0000-001000000064"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000003d-0000-002a-0000-00290000007b"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000004-0000-0033-0000-00780000005e"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000006b-0000-007f-0000-001d0000002c"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000017-0000-0079-0000-001c00000066"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000002f-0000-0024-0000-001000000074"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000010-0000-000c-0000-001700000046"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000003-0000-0049-0000-003100000022"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000011-0000-0051-0000-003300000061"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000003e-0000-0077-0000-004c00000022"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000007e-0000-0048-0000-007200000056"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000006f-0000-0007-0000-00190000004f"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000002d-0000-0048-0000-001c0000007e"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000004c-0000-0071-0000-007a00000071"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000006-0000-0002-0000-002000000068"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000002e-0000-0037-0000-005e00000027"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000056-0000-006d-0000-004d00000024"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000004a-0000-0038-0000-001e0000003b"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000033-0000-001a-0000-004a0000001a"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000001a-0000-0070-0000-007000000019"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000006b-0000-0013-0000-004a00000018"))))) ] ) testObject_TeamConversationList_team_5 :: TeamConversationList testObject_TeamConversationList_team_5 = ( newTeamConversationList - [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000073-0000-005a-0000-00250000000d")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000033-0000-005c-0000-006e00000014")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000017-0000-005d-0000-003b00000023")))) (False)) + [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000073-0000-005a-0000-00250000000d"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000033-0000-005c-0000-006e00000014"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000017-0000-005d-0000-003b00000023"))))) ] ) testObject_TeamConversationList_team_6 :: TeamConversationList testObject_TeamConversationList_team_6 = ( newTeamConversationList - [ (newTeamConversation ((Id (fromJust (UUID.fromString "0000007c-0000-007f-0000-00730000000d")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000028-0000-0037-0000-000b00000016")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000021-0000-0064-0000-003900000002")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000064-0000-001f-0000-00350000001b")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000002d-0000-007b-0000-00770000003e")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000064-0000-0068-0000-007700000068")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000061-0000-000b-0000-00170000005c")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000005c-0000-0001-0000-004e00000003")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000008-0000-002b-0000-002d00000022")))) (False)) + [ (newTeamConversation ((Id (fromJust (UUID.fromString "0000007c-0000-007f-0000-00730000000d"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000028-0000-0037-0000-000b00000016"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000021-0000-0064-0000-003900000002"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000064-0000-001f-0000-00350000001b"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000002d-0000-007b-0000-00770000003e"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000064-0000-0068-0000-007700000068"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000061-0000-000b-0000-00170000005c"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000005c-0000-0001-0000-004e00000003"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000008-0000-002b-0000-002d00000022"))))) ] ) testObject_TeamConversationList_team_7 :: TeamConversationList testObject_TeamConversationList_team_7 = ( newTeamConversationList - [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000068-0000-0010-0000-002700000004")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000006d-0000-0036-0000-000e00000080")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000003-0000-0068-0000-000000000006")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000024-0000-0018-0000-005d00000050")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000040-0000-0001-0000-00670000002e")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000002-0000-0016-0000-004300000052")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000007b-0000-0073-0000-002700000048")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000003b-0000-0048-0000-002500000015")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000055-0000-007c-0000-001500000051")))) (True)) + [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000068-0000-0010-0000-002700000004"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000006d-0000-0036-0000-000e00000080"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000003-0000-0068-0000-000000000006"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000024-0000-0018-0000-005d00000050"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000040-0000-0001-0000-00670000002e"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000002-0000-0016-0000-004300000052"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000007b-0000-0073-0000-002700000048"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000003b-0000-0048-0000-002500000015"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000055-0000-007c-0000-001500000051"))))) ] ) testObject_TeamConversationList_team_8 :: TeamConversationList testObject_TeamConversationList_team_8 = ( newTeamConversationList - [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000026-0000-0066-0000-00170000007b")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000064-0000-0015-0000-001f00000071")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000063-0000-0049-0000-004100000018")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000050-0000-002b-0000-000300000001")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000035-0000-006e-0000-002f00000057")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000006f-0000-0064-0000-003b0000002d")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000003e-0000-0009-0000-00630000001d")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000002a-0000-004d-0000-001b00000036")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000002d-0000-0073-0000-007d00000010")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000016-0000-0007-0000-00690000002d")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000043-0000-001f-0000-007500000002")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000002-0000-0012-0000-006200000028")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000019-0000-003a-0000-002300000023")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000050-0000-006d-0000-00610000000c")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000068-0000-0048-0000-003200000004")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000003-0000-0024-0000-002000000015")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000027-0000-0003-0000-007600000028")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000074-0000-005d-0000-00100000005d")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000071-0000-0075-0000-000a0000002c")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000012-0000-0071-0000-004d00000010")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000006f-0000-003f-0000-005a00000026")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000016-0000-0069-0000-00500000000a")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000033-0000-000b-0000-003000000046")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000002d-0000-005f-0000-007f0000001b")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000057-0000-0050-0000-002100000074")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000077-0000-0063-0000-00360000000e")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000058-0000-0011-0000-001200000005")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000004a-0000-0037-0000-003000000034")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000029-0000-0043-0000-006700000030")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000039-0000-003e-0000-008000000051")))) (True)) + [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000026-0000-0066-0000-00170000007b"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000064-0000-0015-0000-001f00000071"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000063-0000-0049-0000-004100000018"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000050-0000-002b-0000-000300000001"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000035-0000-006e-0000-002f00000057"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000006f-0000-0064-0000-003b0000002d"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000003e-0000-0009-0000-00630000001d"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000002a-0000-004d-0000-001b00000036"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000002d-0000-0073-0000-007d00000010"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000016-0000-0007-0000-00690000002d"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000043-0000-001f-0000-007500000002"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000002-0000-0012-0000-006200000028"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000019-0000-003a-0000-002300000023"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000050-0000-006d-0000-00610000000c"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000068-0000-0048-0000-003200000004"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000003-0000-0024-0000-002000000015"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000027-0000-0003-0000-007600000028"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000074-0000-005d-0000-00100000005d"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000071-0000-0075-0000-000a0000002c"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000012-0000-0071-0000-004d00000010"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000006f-0000-003f-0000-005a00000026"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000016-0000-0069-0000-00500000000a"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000033-0000-000b-0000-003000000046"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000002d-0000-005f-0000-007f0000001b"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000057-0000-0050-0000-002100000074"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000077-0000-0063-0000-00360000000e"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000058-0000-0011-0000-001200000005"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000004a-0000-0037-0000-003000000034"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000029-0000-0043-0000-006700000030"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000039-0000-003e-0000-008000000051"))))) ] ) testObject_TeamConversationList_team_9 :: TeamConversationList testObject_TeamConversationList_team_9 = ( newTeamConversationList - [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000058-0000-007c-0000-002a0000005f")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000080-0000-0009-0000-006500000038")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000004e-0000-000a-0000-004e00000039")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000062-0000-001e-0000-004c00000058")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000004-0000-0021-0000-00670000000a")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000004f-0000-0063-0000-004a0000004b")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000044-0000-0017-0000-006300000067")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000006a-0000-0070-0000-002e0000000a")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000049-0000-0080-0000-006000000025")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000007d-0000-0040-0000-001700000066")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000057-0000-0045-0000-00610000006c")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000001-0000-0042-0000-005b00000057")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000048-0000-0032-0000-000000000069")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000003-0000-0022-0000-00370000005b")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000007-0000-0068-0000-00150000001f")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000003a-0000-0067-0000-00060000003e")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000001e-0000-0043-0000-002800000065")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000053-0000-001f-0000-001700000006")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000068-0000-0024-0000-004900000037")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000005-0000-0019-0000-00670000005c")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000029-0000-0003-0000-00520000004c")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000080-0000-002f-0000-002b0000006f")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000021-0000-002e-0000-004f0000005e")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000006a-0000-0023-0000-00560000001b")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000066-0000-007b-0000-00160000005c")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000004e-0000-0008-0000-006b00000049")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000005b-0000-0020-0000-005000000006")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000052-0000-0038-0000-003400000074")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000067-0000-006f-0000-00370000002e")))) (True)) + [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000058-0000-007c-0000-002a0000005f"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000080-0000-0009-0000-006500000038"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000004e-0000-000a-0000-004e00000039"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000062-0000-001e-0000-004c00000058"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000004-0000-0021-0000-00670000000a"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000004f-0000-0063-0000-004a0000004b"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000044-0000-0017-0000-006300000067"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000006a-0000-0070-0000-002e0000000a"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000049-0000-0080-0000-006000000025"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000007d-0000-0040-0000-001700000066"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000057-0000-0045-0000-00610000006c"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000001-0000-0042-0000-005b00000057"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000048-0000-0032-0000-000000000069"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000003-0000-0022-0000-00370000005b"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000007-0000-0068-0000-00150000001f"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000003a-0000-0067-0000-00060000003e"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000001e-0000-0043-0000-002800000065"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000053-0000-001f-0000-001700000006"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000068-0000-0024-0000-004900000037"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000005-0000-0019-0000-00670000005c"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000029-0000-0003-0000-00520000004c"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000080-0000-002f-0000-002b0000006f"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000021-0000-002e-0000-004f0000005e"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000006a-0000-0023-0000-00560000001b"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000066-0000-007b-0000-00160000005c"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000004e-0000-0008-0000-006b00000049"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000005b-0000-0020-0000-005000000006"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000052-0000-0038-0000-003400000074"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000067-0000-006f-0000-00370000002e"))))) ] ) testObject_TeamConversationList_team_10 :: TeamConversationList testObject_TeamConversationList_team_10 = ( newTeamConversationList - [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000070-0000-007d-0000-001400000009")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000065-0000-0057-0000-00190000004a")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000049-0000-0030-0000-006b00000005")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000007c-0000-0065-0000-001100000066")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000057-0000-0039-0000-000400000071")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000003e-0000-0053-0000-007f0000003c")))) (False)) + [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000070-0000-007d-0000-001400000009"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000065-0000-0057-0000-00190000004a"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000049-0000-0030-0000-006b00000005"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000007c-0000-0065-0000-001100000066"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000057-0000-0039-0000-000400000071"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000003e-0000-0053-0000-007f0000003c"))))) ] ) testObject_TeamConversationList_team_11 :: TeamConversationList testObject_TeamConversationList_team_11 = ( newTeamConversationList - [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000038-0000-0030-0000-006700000067")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000000-0000-006a-0000-00220000007c")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000055-0000-004f-0000-005500000047")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000064-0000-003d-0000-006500000060")))) (False)) + [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000038-0000-0030-0000-006700000067"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000000-0000-006a-0000-00220000007c"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000055-0000-004f-0000-005500000047"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000064-0000-003d-0000-006500000060"))))) ] ) testObject_TeamConversationList_team_12 :: TeamConversationList testObject_TeamConversationList_team_12 = ( newTeamConversationList - [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000056-0000-0042-0000-00120000004e")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000010-0000-002b-0000-002600000066")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000006b-0000-0054-0000-005300000004")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000000e-0000-006f-0000-000c00000038")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000038-0000-0021-0000-005500000008")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000005b-0000-007a-0000-00230000002d")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000078-0000-000e-0000-004300000065")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000036-0000-0003-0000-000500000011")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000043-0000-0032-0000-005200000069")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000000c-0000-0003-0000-001400000018")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000002a-0000-0020-0000-005200000053")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000047-0000-007b-0000-00670000000b")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000001a-0000-005b-0000-00250000000c")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000004b-0000-005b-0000-004200000001")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000057-0000-0073-0000-003d00000006")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000053-0000-0038-0000-006600000048")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000000d-0000-0022-0000-00800000006f")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000005e-0000-0023-0000-000700000012")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000046-0000-0071-0000-005f00000070")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000056-0000-0024-0000-003400000018")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000054-0000-0056-0000-007000000058")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000046-0000-0011-0000-001500000007")))) (False)) + [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000056-0000-0042-0000-00120000004e"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000010-0000-002b-0000-002600000066"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000006b-0000-0054-0000-005300000004"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000000e-0000-006f-0000-000c00000038"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000038-0000-0021-0000-005500000008"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000005b-0000-007a-0000-00230000002d"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000078-0000-000e-0000-004300000065"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000036-0000-0003-0000-000500000011"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000043-0000-0032-0000-005200000069"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000000c-0000-0003-0000-001400000018"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000002a-0000-0020-0000-005200000053"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000047-0000-007b-0000-00670000000b"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000001a-0000-005b-0000-00250000000c"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000004b-0000-005b-0000-004200000001"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000057-0000-0073-0000-003d00000006"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000053-0000-0038-0000-006600000048"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000000d-0000-0022-0000-00800000006f"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000005e-0000-0023-0000-000700000012"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000046-0000-0071-0000-005f00000070"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000056-0000-0024-0000-003400000018"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000054-0000-0056-0000-007000000058"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000046-0000-0011-0000-001500000007"))))) ] ) testObject_TeamConversationList_team_13 :: TeamConversationList testObject_TeamConversationList_team_13 = ( newTeamConversationList - [ (newTeamConversation ((Id (fromJust (UUID.fromString "0000006a-0000-0043-0000-007f00000048")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000007d-0000-005f-0000-000a00000024")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000007a-0000-0046-0000-003800000023")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000023-0000-006b-0000-002000000068")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000041-0000-0000-0000-007000000005")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000007a-0000-0075-0000-00200000007a")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000038-0000-0023-0000-001a00000022")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000035-0000-004f-0000-000400000072")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000065-0000-001a-0000-00680000004d")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000002f-0000-0037-0000-00020000000f")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000023-0000-0040-0000-005b0000001c")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000000-0000-0074-0000-007b00000019")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000004e-0000-0025-0000-006900000014")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000063-0000-0000-0000-002100000043")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000004e-0000-0018-0000-004d0000003a")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000052-0000-004e-0000-002700000075")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000046-0000-0014-0000-000100000040")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000049-0000-0004-0000-00280000000a")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000004-0000-0012-0000-00150000006e")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000009-0000-003c-0000-006400000055")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000008-0000-003d-0000-003c00000003")))) (False)) + [ (newTeamConversation ((Id (fromJust (UUID.fromString "0000006a-0000-0043-0000-007f00000048"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000007d-0000-005f-0000-000a00000024"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000007a-0000-0046-0000-003800000023"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000023-0000-006b-0000-002000000068"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000041-0000-0000-0000-007000000005"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000007a-0000-0075-0000-00200000007a"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000038-0000-0023-0000-001a00000022"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000035-0000-004f-0000-000400000072"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000065-0000-001a-0000-00680000004d"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000002f-0000-0037-0000-00020000000f"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000023-0000-0040-0000-005b0000001c"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000000-0000-0074-0000-007b00000019"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000004e-0000-0025-0000-006900000014"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000063-0000-0000-0000-002100000043"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000004e-0000-0018-0000-004d0000003a"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000052-0000-004e-0000-002700000075"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000046-0000-0014-0000-000100000040"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000049-0000-0004-0000-00280000000a"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000004-0000-0012-0000-00150000006e"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000009-0000-003c-0000-006400000055"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000008-0000-003d-0000-003c00000003"))))) ] ) testObject_TeamConversationList_team_14 :: TeamConversationList testObject_TeamConversationList_team_14 = ( newTeamConversationList - [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000039-0000-005c-0000-000e00000044")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000080-0000-0061-0000-005d00000066")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000011-0000-0009-0000-006c00000065")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000002a-0000-0026-0000-001e00000007")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000023-0000-005e-0000-007300000058")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000056-0000-006a-0000-004100000045")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000006d-0000-0027-0000-00080000000d")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000024-0000-0028-0000-007700000051")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000004-0000-001c-0000-004c00000073")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000006f-0000-002f-0000-003400000023")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000005d-0000-0057-0000-00580000006a")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000034-0000-0016-0000-002500000036")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000033-0000-006c-0000-00420000003d")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000008-0000-005d-0000-004600000002")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000006a-0000-002b-0000-005800000035")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000006e-0000-0007-0000-005800000075")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000047-0000-002b-0000-000100000080")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000013-0000-001b-0000-003200000000")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000006-0000-0013-0000-004d0000006e")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000074-0000-0041-0000-007200000079")))) (True)) + [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000039-0000-005c-0000-000e00000044"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000080-0000-0061-0000-005d00000066"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000011-0000-0009-0000-006c00000065"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000002a-0000-0026-0000-001e00000007"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000023-0000-005e-0000-007300000058"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000056-0000-006a-0000-004100000045"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000006d-0000-0027-0000-00080000000d"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000024-0000-0028-0000-007700000051"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000004-0000-001c-0000-004c00000073"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000006f-0000-002f-0000-003400000023"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000005d-0000-0057-0000-00580000006a"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000034-0000-0016-0000-002500000036"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000033-0000-006c-0000-00420000003d"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000008-0000-005d-0000-004600000002"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000006a-0000-002b-0000-005800000035"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000006e-0000-0007-0000-005800000075"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000047-0000-002b-0000-000100000080"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000013-0000-001b-0000-003200000000"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000006-0000-0013-0000-004d0000006e"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000074-0000-0041-0000-007200000079"))))) ] ) testObject_TeamConversationList_team_15 :: TeamConversationList testObject_TeamConversationList_team_15 = ( newTeamConversationList - [ (newTeamConversation ((Id (fromJust (UUID.fromString "0000001a-0000-0013-0000-006400000036")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000070-0000-007e-0000-002f00000057")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000002-0000-006e-0000-006800000040")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000080-0000-005a-0000-000e00000024")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000069-0000-007c-0000-00550000002f")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000068-0000-0041-0000-000e0000003e")))) (False)) + [ (newTeamConversation ((Id (fromJust (UUID.fromString "0000001a-0000-0013-0000-006400000036"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000070-0000-007e-0000-002f00000057"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000002-0000-006e-0000-006800000040"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000080-0000-005a-0000-000e00000024"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000069-0000-007c-0000-00550000002f"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000068-0000-0041-0000-000e0000003e"))))) ] ) testObject_TeamConversationList_team_16 :: TeamConversationList testObject_TeamConversationList_team_16 = ( newTeamConversationList - [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000012-0000-0066-0000-003800000061")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000049-0000-0007-0000-003f0000001d")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000045-0000-0038-0000-005f00000072")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000032-0000-0069-0000-005b00000011")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000053-0000-0073-0000-00280000005d")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000046-0000-0068-0000-004f00000042")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000003-0000-0056-0000-00780000000f")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000006b-0000-0064-0000-001b00000024")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000004e-0000-0052-0000-004000000072")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000052-0000-0080-0000-005100000029")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000079-0000-0018-0000-000600000047")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000009-0000-0029-0000-003100000043")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000048-0000-002e-0000-00220000005b")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000003b-0000-004d-0000-001700000055")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000006c-0000-0028-0000-002100000076")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000033-0000-0052-0000-003300000080")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000004c-0000-005f-0000-00390000004d")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000007a-0000-004b-0000-00440000003e")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000052-0000-007a-0000-003d00000036")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000018-0000-0058-0000-003700000019")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000034-0000-0011-0000-007c00000011")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000056-0000-0057-0000-00630000002b")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000051-0000-0018-0000-00590000007a")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000004b-0000-0011-0000-002100000014")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000060-0000-0003-0000-00490000001b")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000042-0000-006e-0000-001e0000001a")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000005d-0000-0065-0000-004b00000045")))) (False)) + [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000012-0000-0066-0000-003800000061"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000049-0000-0007-0000-003f0000001d"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000045-0000-0038-0000-005f00000072"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000032-0000-0069-0000-005b00000011"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000053-0000-0073-0000-00280000005d"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000046-0000-0068-0000-004f00000042"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000003-0000-0056-0000-00780000000f"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000006b-0000-0064-0000-001b00000024"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000004e-0000-0052-0000-004000000072"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000052-0000-0080-0000-005100000029"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000079-0000-0018-0000-000600000047"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000009-0000-0029-0000-003100000043"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000048-0000-002e-0000-00220000005b"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000003b-0000-004d-0000-001700000055"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000006c-0000-0028-0000-002100000076"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000033-0000-0052-0000-003300000080"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000004c-0000-005f-0000-00390000004d"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000007a-0000-004b-0000-00440000003e"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000052-0000-007a-0000-003d00000036"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000018-0000-0058-0000-003700000019"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000034-0000-0011-0000-007c00000011"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000056-0000-0057-0000-00630000002b"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000051-0000-0018-0000-00590000007a"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000004b-0000-0011-0000-002100000014"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000060-0000-0003-0000-00490000001b"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000042-0000-006e-0000-001e0000001a"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000005d-0000-0065-0000-004b00000045"))))) ] ) testObject_TeamConversationList_team_17 :: TeamConversationList testObject_TeamConversationList_team_17 = ( newTeamConversationList - [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000053-0000-0070-0000-007f0000001c")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000036-0000-0017-0000-002a00000076")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000065-0000-004f-0000-00710000002d")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000067-0000-0037-0000-004d0000007b")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000004-0000-0071-0000-000800000015")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000047-0000-0062-0000-002900000024")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000072-0000-0027-0000-001300000046")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000000f-0000-0034-0000-00720000000f")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000021-0000-005d-0000-003300000024")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000023-0000-000b-0000-00160000000d")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000022-0000-0042-0000-003400000043")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000020-0000-0033-0000-00780000006b")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000074-0000-0067-0000-005f00000042")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000000f-0000-0079-0000-00630000007e")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000001a-0000-0045-0000-003900000053")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000000-0000-003e-0000-003d00000000")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000039-0000-0052-0000-000500000034")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000004e-0000-002d-0000-00030000005c")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000036-0000-0067-0000-007400000054")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000047-0000-0075-0000-001200000054")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000002e-0000-003d-0000-000700000080")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000005d-0000-0006-0000-00010000001a")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000012-0000-0073-0000-002000000058")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000073-0000-0015-0000-005e0000006e")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000047-0000-0019-0000-00510000005a")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000004b-0000-0074-0000-007000000021")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000007a-0000-0040-0000-006f00000075")))) (True)) + [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000053-0000-0070-0000-007f0000001c"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000036-0000-0017-0000-002a00000076"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000065-0000-004f-0000-00710000002d"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000067-0000-0037-0000-004d0000007b"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000004-0000-0071-0000-000800000015"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000047-0000-0062-0000-002900000024"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000072-0000-0027-0000-001300000046"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000000f-0000-0034-0000-00720000000f"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000021-0000-005d-0000-003300000024"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000023-0000-000b-0000-00160000000d"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000022-0000-0042-0000-003400000043"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000020-0000-0033-0000-00780000006b"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000074-0000-0067-0000-005f00000042"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000000f-0000-0079-0000-00630000007e"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000001a-0000-0045-0000-003900000053"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000000-0000-003e-0000-003d00000000"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000039-0000-0052-0000-000500000034"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000004e-0000-002d-0000-00030000005c"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000036-0000-0067-0000-007400000054"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000047-0000-0075-0000-001200000054"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000002e-0000-003d-0000-000700000080"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000005d-0000-0006-0000-00010000001a"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000012-0000-0073-0000-002000000058"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000073-0000-0015-0000-005e0000006e"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000047-0000-0019-0000-00510000005a"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000004b-0000-0074-0000-007000000021"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000007a-0000-0040-0000-006f00000075"))))) ] ) testObject_TeamConversationList_team_18 :: TeamConversationList testObject_TeamConversationList_team_18 = ( newTeamConversationList - [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000049-0000-000d-0000-007600000068")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000002a-0000-0033-0000-006400000019")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000080-0000-0075-0000-00400000004e")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000062-0000-0073-0000-002a00000051")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000003b-0000-004b-0000-005c00000064")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000016-0000-001a-0000-00430000003d")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000002f-0000-0005-0000-004f00000031")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000000-0000-0043-0000-001a0000000c")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000003-0000-001c-0000-003a0000002b")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000001c-0000-007b-0000-00170000000a")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000073-0000-0073-0000-000000000074")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000005b-0000-0069-0000-00490000002d")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000003c-0000-0012-0000-000400000000")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000016-0000-004e-0000-003800000057")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000008-0000-0022-0000-002000000004")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000070-0000-0011-0000-00260000004a")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000002c-0000-007a-0000-00340000006e")))) (True)) + [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000049-0000-000d-0000-007600000068"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000002a-0000-0033-0000-006400000019"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000080-0000-0075-0000-00400000004e"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000062-0000-0073-0000-002a00000051"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000003b-0000-004b-0000-005c00000064"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000016-0000-001a-0000-00430000003d"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000002f-0000-0005-0000-004f00000031"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000000-0000-0043-0000-001a0000000c"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000003-0000-001c-0000-003a0000002b"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000001c-0000-007b-0000-00170000000a"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000073-0000-0073-0000-000000000074"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000005b-0000-0069-0000-00490000002d"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000003c-0000-0012-0000-000400000000"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000016-0000-004e-0000-003800000057"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000008-0000-0022-0000-002000000004"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000070-0000-0011-0000-00260000004a"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000002c-0000-007a-0000-00340000006e"))))) ] ) testObject_TeamConversationList_team_19 :: TeamConversationList testObject_TeamConversationList_team_19 = ( newTeamConversationList - [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000000-0000-0041-0000-007b00000060")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000003f-0000-0059-0000-000700000073")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000065-0000-0056-0000-007e00000066")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000002b-0000-000b-0000-007a00000065")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000071-0000-003a-0000-001b00000027")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000070-0000-004f-0000-008000000008")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000003c-0000-000d-0000-00510000005a")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000045-0000-006e-0000-004200000072")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000001b-0000-003b-0000-007900000004")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000002d-0000-0077-0000-006400000054")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000001a-0000-005e-0000-003e00000012")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000057-0000-000c-0000-00370000003b")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000031-0000-0010-0000-006500000077")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000028-0000-004b-0000-00460000007b")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000005-0000-0040-0000-006400000024")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000042-0000-005b-0000-002d00000031")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000065-0000-0067-0000-00610000006d")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000007f-0000-0036-0000-00770000000d")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000058-0000-0042-0000-003700000054")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000002a-0000-0001-0000-000700000015")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000002f-0000-003c-0000-003b00000000")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "00000065-0000-0049-0000-00720000006c")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000000f-0000-0021-0000-004c00000055")))) (True)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000005b-0000-002e-0000-00140000003d")))) (False)) + [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000000-0000-0041-0000-007b00000060"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000003f-0000-0059-0000-000700000073"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000065-0000-0056-0000-007e00000066"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000002b-0000-000b-0000-007a00000065"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000071-0000-003a-0000-001b00000027"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000070-0000-004f-0000-008000000008"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000003c-0000-000d-0000-00510000005a"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000045-0000-006e-0000-004200000072"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000001b-0000-003b-0000-007900000004"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000002d-0000-0077-0000-006400000054"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000001a-0000-005e-0000-003e00000012"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000057-0000-000c-0000-00370000003b"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000031-0000-0010-0000-006500000077"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000028-0000-004b-0000-00460000007b"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000005-0000-0040-0000-006400000024"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000042-0000-005b-0000-002d00000031"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000065-0000-0067-0000-00610000006d"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000007f-0000-0036-0000-00770000000d"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000058-0000-0042-0000-003700000054"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000002a-0000-0001-0000-000700000015"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000002f-0000-003c-0000-003b00000000"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "00000065-0000-0049-0000-00720000006c"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000000f-0000-0021-0000-004c00000055"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000005b-0000-002e-0000-00140000003d"))))) ] ) testObject_TeamConversationList_team_20 :: TeamConversationList testObject_TeamConversationList_team_20 = ( newTeamConversationList - [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000007-0000-0017-0000-007500000074")))) (False)), - (newTeamConversation ((Id (fromJust (UUID.fromString "0000003b-0000-0055-0000-003f00000059")))) (True)) + [ (newTeamConversation ((Id (fromJust (UUID.fromString "00000007-0000-0017-0000-007500000074"))))), + (newTeamConversation ((Id (fromJust (UUID.fromString "0000003b-0000-0055-0000-003f00000059"))))) ] ) diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/TeamConversation_team.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/TeamConversation_team.hs index 2e56b99a4d6..8f519c4f5f1 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/TeamConversation_team.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/TeamConversation_team.hs @@ -21,85 +21,85 @@ module Test.Wire.API.Golden.Generated.TeamConversation_team where import Data.Id (Id (Id)) import qualified Data.UUID as UUID (fromString) -import Imports (Bool (False, True), fromJust) +import Imports import Wire.API.Team.Conversation (TeamConversation, newTeamConversation) testObject_TeamConversation_team_1 :: TeamConversation testObject_TeamConversation_team_1 = - (newTeamConversation ((Id (fromJust (UUID.fromString "00000054-0000-0032-0000-001d0000003e")))) (False)) + (newTeamConversation ((Id (fromJust (UUID.fromString "00000054-0000-0032-0000-001d0000003e"))))) testObject_TeamConversation_team_2 :: TeamConversation testObject_TeamConversation_team_2 = - (newTeamConversation ((Id (fromJust (UUID.fromString "00000021-0000-0059-0000-00390000004c")))) (False)) + (newTeamConversation ((Id (fromJust (UUID.fromString "00000021-0000-0059-0000-00390000004c"))))) testObject_TeamConversation_team_3 :: TeamConversation testObject_TeamConversation_team_3 = - (newTeamConversation ((Id (fromJust (UUID.fromString "00000020-0000-0022-0000-00550000003b")))) (False)) + (newTeamConversation ((Id (fromJust (UUID.fromString "00000020-0000-0022-0000-00550000003b"))))) testObject_TeamConversation_team_4 :: TeamConversation testObject_TeamConversation_team_4 = - (newTeamConversation ((Id (fromJust (UUID.fromString "0000002d-0000-0034-0000-004600000023")))) (True)) + (newTeamConversation ((Id (fromJust (UUID.fromString "0000002d-0000-0034-0000-004600000023"))))) testObject_TeamConversation_team_5 :: TeamConversation testObject_TeamConversation_team_5 = - (newTeamConversation ((Id (fromJust (UUID.fromString "0000007d-0000-005d-0000-003d00000076")))) (False)) + (newTeamConversation ((Id (fromJust (UUID.fromString "0000007d-0000-005d-0000-003d00000076"))))) testObject_TeamConversation_team_6 :: TeamConversation testObject_TeamConversation_team_6 = - (newTeamConversation ((Id (fromJust (UUID.fromString "0000000a-0000-0013-0000-00420000002e")))) (False)) + (newTeamConversation ((Id (fromJust (UUID.fromString "0000000a-0000-0013-0000-00420000002e"))))) testObject_TeamConversation_team_7 :: TeamConversation testObject_TeamConversation_team_7 = - (newTeamConversation ((Id (fromJust (UUID.fromString "0000005d-0000-0080-0000-002800000080")))) (False)) + (newTeamConversation ((Id (fromJust (UUID.fromString "0000005d-0000-0080-0000-002800000080"))))) testObject_TeamConversation_team_8 :: TeamConversation testObject_TeamConversation_team_8 = - (newTeamConversation ((Id (fromJust (UUID.fromString "0000002e-0000-006d-0000-003700000042")))) (True)) + (newTeamConversation ((Id (fromJust (UUID.fromString "0000002e-0000-006d-0000-003700000042"))))) testObject_TeamConversation_team_9 :: TeamConversation testObject_TeamConversation_team_9 = - (newTeamConversation ((Id (fromJust (UUID.fromString "0000000d-0000-001b-0000-006800000047")))) (True)) + (newTeamConversation ((Id (fromJust (UUID.fromString "0000000d-0000-001b-0000-006800000047"))))) testObject_TeamConversation_team_10 :: TeamConversation testObject_TeamConversation_team_10 = - (newTeamConversation ((Id (fromJust (UUID.fromString "00000023-0000-0024-0000-003200000067")))) (False)) + (newTeamConversation ((Id (fromJust (UUID.fromString "00000023-0000-0024-0000-003200000067"))))) testObject_TeamConversation_team_11 :: TeamConversation testObject_TeamConversation_team_11 = - (newTeamConversation ((Id (fromJust (UUID.fromString "00000003-0000-0041-0000-002600000041")))) (True)) + (newTeamConversation ((Id (fromJust (UUID.fromString "00000003-0000-0041-0000-002600000041"))))) testObject_TeamConversation_team_12 :: TeamConversation testObject_TeamConversation_team_12 = - (newTeamConversation ((Id (fromJust (UUID.fromString "0000007d-0000-0049-0000-001f00000034")))) (False)) + (newTeamConversation ((Id (fromJust (UUID.fromString "0000007d-0000-0049-0000-001f00000034"))))) testObject_TeamConversation_team_13 :: TeamConversation testObject_TeamConversation_team_13 = - (newTeamConversation ((Id (fromJust (UUID.fromString "00000025-0000-003c-0000-003d00000032")))) (False)) + (newTeamConversation ((Id (fromJust (UUID.fromString "00000025-0000-003c-0000-003d00000032"))))) testObject_TeamConversation_team_14 :: TeamConversation testObject_TeamConversation_team_14 = - (newTeamConversation ((Id (fromJust (UUID.fromString "0000005b-0000-0065-0000-002a00000060")))) (True)) + (newTeamConversation ((Id (fromJust (UUID.fromString "0000005b-0000-0065-0000-002a00000060"))))) testObject_TeamConversation_team_15 :: TeamConversation testObject_TeamConversation_team_15 = - (newTeamConversation ((Id (fromJust (UUID.fromString "0000001f-0000-0037-0000-005a0000004d")))) (True)) + (newTeamConversation ((Id (fromJust (UUID.fromString "0000001f-0000-0037-0000-005a0000004d"))))) testObject_TeamConversation_team_16 :: TeamConversation testObject_TeamConversation_team_16 = - (newTeamConversation ((Id (fromJust (UUID.fromString "00000044-0000-000a-0000-007f0000001d")))) (False)) + (newTeamConversation ((Id (fromJust (UUID.fromString "00000044-0000-000a-0000-007f0000001d"))))) testObject_TeamConversation_team_17 :: TeamConversation testObject_TeamConversation_team_17 = - (newTeamConversation ((Id (fromJust (UUID.fromString "00000009-0000-0060-0000-005c00000049")))) (True)) + (newTeamConversation ((Id (fromJust (UUID.fromString "00000009-0000-0060-0000-005c00000049"))))) testObject_TeamConversation_team_18 :: TeamConversation testObject_TeamConversation_team_18 = - (newTeamConversation ((Id (fromJust (UUID.fromString "0000005f-0000-0051-0000-003d00000026")))) (False)) + (newTeamConversation ((Id (fromJust (UUID.fromString "0000005f-0000-0051-0000-003d00000026"))))) testObject_TeamConversation_team_19 :: TeamConversation testObject_TeamConversation_team_19 = - (newTeamConversation ((Id (fromJust (UUID.fromString "0000003d-0000-0025-0000-00170000002e")))) (True)) + (newTeamConversation ((Id (fromJust (UUID.fromString "0000003d-0000-0025-0000-00170000002e"))))) testObject_TeamConversation_team_20 :: TeamConversation testObject_TeamConversation_team_20 = - (newTeamConversation ((Id (fromJust (UUID.fromString "00000007-0000-0053-0000-001500000035")))) (False)) + (newTeamConversation ((Id (fromJust (UUID.fromString "00000007-0000-0053-0000-001500000035"))))) diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/TeamUpdateData_team.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/TeamUpdateData_team.hs index 223db088933..7366c6da603 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/TeamUpdateData_team.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/TeamUpdateData_team.hs @@ -19,6 +19,7 @@ module Test.Wire.API.Golden.Generated.TeamUpdateData_team where +import Data.ByteString.Conversion (fromByteString') import Data.Range (unsafeRange) import Imports (Maybe (Just, Nothing)) import Wire.API.Team (TeamUpdateData (..)) @@ -31,11 +32,7 @@ testObject_TeamUpdateData_team_1 = ( unsafeRange ("@t\1104947K\1103008\v\34277\ETXe^\984496x~U;^\1086372\b\SYNwn\\aS\1022526g\CAN\1015468\ENQ'+\DC2~yJ\190623%y\110657!#3\CANtZ\1095609[&{?\SYNX`\50850f\FS\62969=j\US\1046631+d\ESC0\111091\50408Ft`U\97666g\158703\1072122\987428F\avEBjP\153147\94534c\142165\1041426e\176319\SIL\189459\1080869GW\995547I(XBV8\ETX\EOT\DEL\1017745C\38693\1075418\NUL,\190006/P\1000635[y\NAKZ \US\51607c\DC4X`%\1066586\&8@\tP>em\917813E\SOH") ), - _iconUpdate = - Just - ( unsafeRange - ("\FSH\n0\1039325L\ESC\1113097\DC1\1080599MT\RSk\DEL\ESCC\74068\1085263S~X\995215\ACK\181632~Lm\44348\SYN\180250\&6\SI\DLE=\1080377\1057137U8h\ETX=\143784\1079703\STXb7Q\ENQt\v*|I{Ps\73695W,\ESC\DC3m5\5258\EOT\US\186343I\DC3\1044809X8\f\DC3a2(Ic\1083941t\GSS\RS\DC4M\ETB\DLE4;0)j\SO\SO2yN\SUB\190408L\DC2P$8\136887V\1033879U\71351\184267\r\b\1024342\FS\154123bx\14530\&3;[Qb!i\47397J|ca$0n-\SIAZ9\"9\ETB5=\148230\a\rZ\EMn\1036736\fZevj}$`\4356n\STX\SOH8\167784\&8\1053057)|\ESC\DC1\1005173\EMX8eLZ\DC2\10329U\ETX4aI\ETX972GQF\SOU*F\1047919\r3\1041496?d\995610u<\f\DC1t\141693\\g\4420)c4\44853eP\1024435^\r_\156907h\1035687Z\ACK\163949\1088232\ENQ\f{Y\SYNp\NUL\189656&'\NUL\GS*\n-\RS\ETX\RS[") - ), + _iconUpdate = fromByteString' "3-1-47de4580-ae51-4650-acbb-d10c028cb0ac", _iconKeyUpdate = Just ( unsafeRange @@ -51,11 +48,7 @@ testObject_TeamUpdateData_team_2 = ( unsafeRange ("\189807ZV\ESC\1108470:RV\ACK0%U\r\ENQ\5305\vg\SUB\DC3\67160\v\17005\v\164969\DC3\CAN`\t\153326\t_\1030121\19120\US\r\"\182508\95642\1011430\SO@\39970\DLE\ACKy8*\134852P;o`53L\ACK\999693\t\16157e\50198\SYN$\SOH\1101935\1093304\NAK\1031461\100218\b\FSeW,\1082547#\DELUU\DC1%\23739dF\69383Z^\993333u\182995\62551v\1026012gb\1087967\USR \49133\SOHm\ENQ+\RSdHcX\1043456\SYN1\41562\t.r^\n\DC3\25500^\EMp\23943h>\1008252%\1065685#:\20208\DC1EB.\996292\&0H6\174124\190683\19272\1012708o>L\6289_\ETX\988770\&7.9\1073238\DC1WQ\vr3\1014429g\US\178828dZ\DC4\987183\\\1033879\998865~\30943R\tl3Fz\GS\DC3\SUB\ACKD\1032087KjM\FS^\SI,\39922Rjve\NAK$\DC4!\\\SI/13xE\176873\41996X:B\DC1h\38384\&0\15928>\1084065\v5\GS'\1028874\ETX\SOHgj*\181871#P") ), - _iconUpdate = - Just - ( unsafeRange - ("\21590h\62749Ve1`kK\STX&]}%(p\22753\1083321\1003176r\1107488\DEL\27481x\r\"\1043757\ACK\14885\1025784m\1007376\1012010\US\8534j;f-\1034363=4@1O$\94870\ENQ\NAK") - ), + _iconUpdate = fromByteString' "3-1-2ed18927-755c-4197-996a-7076baa23923", _iconKeyUpdate = Just (unsafeRange ("`m\DEL$\1032324\44660`\152159b\1052163\"\FST\SYNiA%ZnO_\b\DEL\NULb")) } @@ -87,11 +76,7 @@ testObject_TeamUpdateData_team_4 = ( unsafeRange ("\128134e\DLE\SUBcs\2387\1075156|\US\SUBu\FS\52294\153736&@\EOT\998980,D,<\147898\1023755RVpe}[\148576\52157xN\tz,T-d*\159171\EM\GSa\1086147\188771\STX|>]\v\191414:7\190399\1002509b\1012517|c\131827,\44613\1016960O\120010l=\1026976QA\21240\48375E\1048133#J\SIocH&\SI\191295\DELh\SUB^\t\NUL\58166\986951\1040859n[\1099585o\118936\DC2\43837\41993,\SUB\a\ENQ\EM\20127*m\1060088&\171058\1057983\EOTE#W'\a;+I\SYN\FSg\n^i[\1044417\STX.\CAN]\21346b\\\1106355\1004766\DC4J\1010071\1109900s:D\FS\SOBwpPF\ESC\\\f\1043258\157327\32653H\1038564\1018956j\1068498\19386\119144\SOH\\\785NE\51900\72110\DC1\rU\t\149777j\SYNX\n\1042182}\1041865\1047029\1069576\vS\1022749{\1063362\72135\USi\1043163\DC2\1098488^8\78341\ACK\SObG\6333X\1107580\SUB!R\59730^\DEL\SYNqjf}|\STX$\ESC0Q\SI\ACK\1025203\a\46015F\173556~\DC4T\1110827\135066j+I\EM\RSWK\DC2f") ), - _iconUpdate = - Just - ( unsafeRange - ("\30772\&5\FS\13470\ETX\t5l\100970j\DC1iu\64772\1001606\1051131\DC3)$M82pC,Hwy\ETX>Vf\SOz\SUB\1548\ETB_4B\f\1097043\1043467\156243a\1102338\186298FZ") - ), + _iconUpdate = fromByteString' "3-1-2f002dda-74de-457b-aabe-831229662e4d", _iconKeyUpdate = Just ( unsafeRange @@ -107,11 +92,7 @@ testObject_TeamUpdateData_team_5 = ( unsafeRange ("c\DC4\21957\1083082Am\SOH,0\5634\1011802\DLE\DC1\1048597\ESC>\DC1rj5Hd\1061313\DELI'$.\98215\DC2}\ENQ\DC2\1009633\158711\100133|\FS\r\DLE^\8538t\190283\1060031vf\1047172`d' '={4\48912\b5][T\165195\&7A1\32515\NAKY\\frek5$f6b_4%\129513\DC2\1047616!\DC16\f\ETXK[SQH\n\35821\1017522\1088735\EMd\\@RQB\1113466\75066A^ l\1085060\1033719X^i\1014199\SOH\1042929\176179I\1107945\US3\1044762xIC\DEL#C-\1054562\SUB\136101r\35811\f,\SYN=\SOHJ\40558I=\987545") ), - _iconUpdate = - Just - ( unsafeRange - ("0yl+Ej0A)D\NAK\ETB}\1039598\1063472FB\50299\97823\146248\34652+\42767\&2&\SYN\1089179\ETX?\152085[Td*K!c\1018259\163237<\1043113\1053778\1035549\984028ecC\187860\5191\f\1101522{-\\\EOT\1022309\FS1qhH\STX\ACKNq\NAKf\b{&\4910\1033774\140126\SYN\DC33\DC3-.:K\175882F7I\ETX\1097348il\1109195\179701A\167906F~N\b\100458\53332\34700Z!\DC4?\157325\42325n\ESC\1087965") - ), + _iconUpdate = fromByteString' "3-1-1ab65a15-a0de-4e3a-b5cf-533b43df652e", _iconKeyUpdate = Nothing } @@ -135,11 +116,7 @@ testObject_TeamUpdateData_team_7 = ( unsafeRange ("n{\1057261oZn\DC1\ESCJt kj\ACK\r\1009375\"'{\SUBX\183635?@\1072481Ly\1034079\ENQ@$\126078W\182880\152533mW\1031829\DC1\DLE^c)\185735\987874\168851\44285\&9\1026256\1081073\1088339\ETB\DC4\DC2My\EM\998884\CAN\155753gmi\18003\SIy:r[\1028859i.\\\SOH\1013999\ETB5\184553H#\DC2\100088#l\SI@\149391@\NAK)\155671Jg\16061c\ACKV\EOT\1052115\166619\1106254\DC3\7348\1014585\1039214fQ\36540\1014874\1099704|Ik\DC1X\SYN\FS}ii\1044665M&.)\163680\SYNL\1006642\ESCk\a!\DEL \SUB\1083653\150892+\RSRW\\x\US\GSt\988142\1060379\33437\CAN\STX\51186+\DC2\1051428,\\F%,w\174606a\\\DEL]\RS\141663~X;f\134482 \1065664p\DC1d8mhY7w\RSe\ETX\DC1\1112177l\ETB{3&\49028\ACK\DC4V=D\NUL\ENQ\SI\93957\aK_di=,") ), - _iconUpdate = - Just - ( unsafeRange - ("\990737\CAN\33854\&3\1097824EE\RStQb\FS`:;\4613=\997632P2\DC3\\\NAKZ\1050942\DC1p'WrP3\ESC\152406\&9o\FS,H\DC2.&\110851\128155\SOHo\RS ^Jak&\DC2\1010395\13081W\CAN5\t?;/\983915\DLEA\139873A\v*Q\66752<\166347i80\v%c{U\59976\SO\ACKW\1077362&~{R>iP\SYN\49730\ENQI\1011701*K\996728a\21531\"gi\NAK\1094228.\1071509\1077599L\NAK\132856<\1017306\ETB\DC4\CAN\100503U\997758f\128348\162175~\EOTb\t\1002782\SO\43510\1033082\4725\b\NAK\7475\164004QaZ)\USt\ENQ\b.\1008499^\189520\DC4\994152{S\138099sRGr\1026084\1017366$\1084696\&2~b\137745") - ), + _iconUpdate = fromByteString' "3-1-b5aa5007-2939-4c53-874b-aecfbb6244fd", _iconKeyUpdate = Nothing } @@ -159,11 +136,7 @@ testObject_TeamUpdateData_team_9 = ( unsafeRange ("QJ\1097031G\"g\SI~jG%\DC3o\SI2Zb\30604\1005260\145682vb\US,N9@\1044946\ENQ\DC1\151830:\23929H\v\EOT\66778\ETB/1\3753:\ESC\188539\&3X\146473\&5g)3xq7\38571\140250o%PvWF\vF-vF|\a\1071563k }\1008775\120687!\NULZ$md\97106\119012'\1035663\131295*Tj\ACKh\\TK$~ *\1658\19623*P/=W\GS\29550\1019406~_$~\99885:\ESC=\153783\1005174r\65190\\/oRB\v\EOTK\1073165\18061$\17338\EM~-}S\996372ipLl\190933IJ\GS\SYN\bu\28200\CANkq@1m\126546\&5|\DC2O1<'\ENQ)\1004070;\1045448\SUBF\v\987260I\SYNg\fb\nXB37\DC2HHhO\DC3\aD\ETX\FSmm\65705W*\1045560d\v\SUB^\1037116ow\166819&9\185716B\1015997\nK!i\DC1\1103398\\\137045\1044022\95353\&4\1041203J8g\ACK\1076662\163809\1074446N\51814|^\1097868@\1071814\1095356,Wi\54749\&4\SI\NUL`\SI;\SI=}jY\993753:\182678f:M\991209\1103492\995417#5\172275\DLE\139206*\99381U\155843\ETX\DC2U\983347\50942PU\v\60676\STX1\b") ), - _iconUpdate = - Just - ( unsafeRange - ("\1105395fO\136152#s\1045772 e]+\40673P\RS\1069217I\SUB\7389k\b\15693\v\EOT'nY\1050737hc\SOHC\CAN=9I\DC3Z\1060298:DNj\73790~{\1014583\1103190C\1458u\\6\21036\149041gZ\1083605\a\a\1068405\175226\985156v$9\SIa\tAg)T}\1089275\988268'\GS\1102415ng}\27622<\a\134504\1061180s+\1030442\1067569`z[\CAN,\b:TX\NAK)U?\NUL |\137753xI\GS\154470S") - ), + _iconUpdate = fromByteString' "3-1-fb2b55d0-becc-449a-9755-ed72366c4e24", _iconKeyUpdate = Just (unsafeRange ("i\165439\1084715\70744\984960r\143191\FSiL\SOH)I\EM\n9l>\SOHPu]\NUL\34711Q##\ETX\185628\DC2")) @@ -249,11 +206,7 @@ testObject_TeamUpdateData_team_14 = ( unsafeRange ("W\v\128126\49287\DC4\STX\SOH\1071632\1089152\&0\177175\1020380\1097825N\1096909j\1551\&7]v\DLE\ETX\1064358\1088228do\59527]?\"\39129\&4\NUL^I5\8990{\153487{\f>\SOTx\n!dR8%\1008955N5\vI\139104R\DC4\DC4uo\993229\&2\172393[V\DLE\GS\SI\bn\121117J\177399\EOT^x<\131581W\1080876H#oF(t\tQ\38424\1075412V3\180074\155485\SI*T]r5G\1091385\158397>\30986\99439\1029421qwVi|\1018658\163652^b^/\\%\DC4\142529A\ETXgL\46741Zt^Y\NUL \ACKcv4\189064f\181439\DC35\135778u\31202\nOI\48512\1102654~\1093814\178360\&4\NUL)%X\992245Ar p\1078684\1014480y{DB|]lbI:3$\17570\&1bX+ \1032696N\1021333\SO\984213\r\51699\f~%\"{&\93818j\57610ME\USmg{\DLE\28913Q/\96067XW") ), - _iconUpdate = - Just - ( unsafeRange - ("i\ESC\DC4I\ESC>X\CAN\25839q+?\a4J\28955\1107236,\45638\1091677\SYNo,\1087282#O\CAN4\1060231\r\1111276\&5.\DC1\STXB*F\NAK\161291l\1013717\178596zJ\DC3'5.R\EM\STX\983262\STX8M\1106219\1032181\169235D\178077\"\EM\\!Z[ t\NAK02t=\US\1053232\1035064\1049822A>4\DC4\64018`\8383\DC2d\\oB\44662\1022783\ETX\ap!B\21389\1049746\na^\CANU!\1039339\98447QOjEm\65682`g\100320\r\f\US\35096\57781\DC2n1\tD:S\67369<8\r<\1003632bVi\GSoS'2o\ETB}\125053=q\1086133\EOT\STXw}\NUL\44477hK\1046502+gud:W!\1013730\f\74784}\r\EOT$L\n\136841`\a\126225M4y\1001381\1049907zYW\EM\1007372\1058563Ty\1048016\FS3\STXV7\50967j\GS\ay7i\DC3\r\1107257l\1082862\1050872\99516\32567H%,(1j\998348\v\986415H:\SYN\1007283W\ai)\181153C\161390rm{1\ESC\ESCrde\SYN$80%\133874\EM\16697O") - ), + _iconUpdate = fromByteString' "3-1-bc1b2714-64a1-4cd5-bf28-769f2726c204", _iconKeyUpdate = Just ( unsafeRange @@ -269,11 +222,7 @@ testObject_TeamUpdateData_team_15 = ( unsafeRange ("p\ETX\47602\GS\NUL_\127910n4\1075628\&6V\65148\STX\156622a\1080314\&97\55267X\36536)Y\65341\1035712t\1064872\DC1\990797\1072225\20887G`\tV\ESC0&\DC2t\4414&\984777oq\DLEM\182922)+>Dh7\1011725c\157347\21358R\175842a\991848H\992285\1098926\r_pU\r\ACKXP-\raR8P\EMT5RD\1075743j>\RS\EOT4J$e\FS\bWP&\b\1013201\1062988\1103722\&5[\3622lYC\1051016z8\DLE\1004950\US\18405\96631\DC1\1085685\DELg\131242\NAK\153801RS\1109644Q\1009155\186327t\5905\STXLAS3I~ 9\NUL\985675rj\150171C\1058830\&794&\1111226\SOvk`h?") - ), + _iconUpdate = fromByteString' "3-1-f7d6739b-9dd7-4dff-acbf-972b0864158f", _iconKeyUpdate = Just ( unsafeRange @@ -289,11 +238,7 @@ testObject_TeamUpdateData_team_16 = ( unsafeRange ("{,W\113725j\66867VW,|\DC1-\EM\92324\52301\1085991\ENQSdM\183964~\187744\166807\v\46661\1012290\1008523\11770m\1001938{R\\\21218\184105(\DLE@\1105928Eiw\181379\989957\&7\1088623\53157\vc[L[\NAK\9325_\CAN](H\aOj\993741\FSgdV\179455") ), - _iconUpdate = - Just - ( unsafeRange - ("\EOT>D0.\123153\GStW\1072092YER)M@jid\ETX\147741\SOS\57542Q-\DLEO@\bo\ENQ@iV\74411\1001808P}s\44110f3\DC3\RSAN=*^[\1025032\t\189496q\SI,s3?\175350D\1099533*\132583\&6\169444!*\ESCTk\1059479Fyc\"L\rlv!\136570NxB\ENQ\rs&>M[\DLE\SO\184134q\SO6n:\DEL\ESCb\SIV\ENQLNT\ETX?\170592[\US8\1098891jT1\139047y\CAN)") ), - _iconUpdate = - Just - ( unsafeRange - ("\187020\68302\68231\1094239(!WtCYc\DELX$\CANe-9Gr\999186=M){w\DC2\156678\\\1110651_\DC1*j\US.\1110789\1072197\1026885\DEL\1314;\1099158go\v:Dr0\928W\DELom\1099777P*\72311\NUL\181164\1053602 \1025622\169338Ad_i?r\34872\1017917\14693\169159~y~\186034\"ByOiY1\186908 \RS+qaG9\1027588b\f\SIW!\1067149srx~j\6197\SUB\1064674Z\160086\1084367\1096818\SOH~\72194\b\NULC=H\f9\180087+\113759\1026072\131157\DC4p\b") - ), + _iconUpdate = fromByteString' "3-1-8a5fd50d-9c32-494f-83ff-69db6d290fca", _iconKeyUpdate = Nothing } @@ -325,11 +266,7 @@ testObject_TeamUpdateData_team_18 = ( unsafeRange ("\1079724g^\SI4\SYN\1058518(\1009158dn\15153\5338\1106457\&8\3255\FS\NULd}W\1077482\1112219a\1045348\&8.DV\1112683\DC2Q!\SUB\1015114\NUL\165488|k\141351Y\b&]P\NAKM\23995{\SOH\US\1084668\8678fEL5\1099186^xy.\1081341\1097387ZD2EOw\1067991\1103136Z\990193v\SUB\17778:U,yu3)*\31312]\61413\&6t:Q\nQ\70111\DC3\ETXCd\983894&\165641p\1107770u|\1097560wh:%KJQB>I\20517W\169935\11540\135417\vIP+|9C\43303XBM\1070327$FR\68308J5d\GSK\DEL\167980\CAN\1107001\EMt'\RS[zmz\ESC-\1090175\1053386{o\153401/\DLE\NAK\1071487\DLE\DC2\DELz~>iz\1035567j`\156674G\rat{\b&\1091867\175116,W\1102256\1102670\1041725\180873G\1032893\1051388Q\SI\32211\RSg^&>\EOT&BB]\SUB\183680^^n\83211\1056047\DC3\33295\RS2\120638^I>^e\1088165\&2\1060054$+\1099972\&2\DC3>&4%4\1049880\DC1\985577M\95025\99763\&0\10709\ESCM\GSu") ), - _iconUpdate = - Just - ( unsafeRange - ("jc3h\1005747\&8\1104604j2\163578s\145282nw\1028815\43326\fkOO\SO\50268\&3Adqz0\a^AU\NUL/\63034?\RSz\t\1013555.[\FS\97617\ACKK\188176\DELg\147687Y\US\1051347OZ\a\164115(H\48697\143951\STX8(\1080538\64417\1059160?e\984507_M\148578Q~o\38053X6WAL\SUB\DEL!\998015\10180A2\SUB7\157157a\1000210\v[T\39548I\985078\1098938\FS$(\bq\1096594\a\128511\DC4\DC4f\1074329\&3Vc\GS\1083835u\127513V\t\48136\1014895\\\ESC\ETX\119947\145834f\1099291\1005132'\170635w\DC3\1100353M3\1103725&6\v\DLEM \RS") - ), + _iconUpdate = fromByteString' "3-1-a55ba42a-1fff-4720-ab1f-404ac449a8c4", _iconKeyUpdate = Nothing } @@ -341,11 +278,7 @@ testObject_TeamUpdateData_team_19 = ( unsafeRange ("{ag\147194<-\41002\"\1080393Ad%\30025\1023746U>\28518<>g\bt\29617:\1083297^=6\1076845\1001362\95768\DC1\1083749\r\ESCIu%b\DC2\b`/-+`\1071102\\\ETB^\ETBw\DC1L\USb?'\1004489\ETX\DLE\ESC\v\1089138\161384}\1078506\\\10356\DEL$\DC4OE\ETB\RS\GS)Vej\1072959\174859!\DC1W*s\DC2U%-\140833KC`B\\k\1048017\RS:\DC4\1095557\USN\DC3\ESC:ns\GSj\DC2&-\ETX.h\SUBJN\1030050x1c\NAK\ACK\646+\SIb\DC2mnp\1075229\ETX\996854)\EOT ;u\169592\&5\EM;\f \6592") ), - _iconUpdate = - Just - ( unsafeRange - ("'\176588\1108224\RSSD\1078734\SOH\1098229\v\NAKd\US +-- +-- 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.CreateScimToken where + +import Data.Misc (PlainTextPassword (PlainTextPassword)) +import Data.Text.Ascii (AsciiChars (validate)) +import Imports +import Wire.API.User.Activation (ActivationCode (ActivationCode, fromActivationCode)) +import Wire.API.User.Scim (CreateScimToken (..)) + +testObject_CreateScimToken_1 :: CreateScimToken +testObject_CreateScimToken_1 = + CreateScimToken + "description" + (Just (PlainTextPassword "very-geheim")) + (Just ((ActivationCode {fromActivationCode = fromRight undefined (validate "123456")}))) + +testObject_CreateScimToken_2 :: CreateScimToken +testObject_CreateScimToken_2 = + CreateScimToken + "description2" + (Just (PlainTextPassword "secret")) + Nothing + +testObject_CreateScimToken_3 :: CreateScimToken +testObject_CreateScimToken_3 = + CreateScimToken + "description3" + Nothing + (Just ((ActivationCode {fromActivationCode = fromRight undefined (validate "654321")}))) + +testObject_CreateScimToken_4 :: CreateScimToken +testObject_CreateScimToken_4 = + CreateScimToken + "description4" + Nothing + Nothing diff --git a/libs/wire-api/test/golden/testObject_CreateScimToken_1.json b/libs/wire-api/test/golden/testObject_CreateScimToken_1.json new file mode 100644 index 00000000000..3c3d1cbff10 --- /dev/null +++ b/libs/wire-api/test/golden/testObject_CreateScimToken_1.json @@ -0,0 +1,5 @@ +{ + "description": "description", + "password": "very-geheim", + "verification_code": "123456" +} diff --git a/libs/wire-api/test/golden/testObject_CreateScimToken_2.json b/libs/wire-api/test/golden/testObject_CreateScimToken_2.json new file mode 100644 index 00000000000..8364d591e30 --- /dev/null +++ b/libs/wire-api/test/golden/testObject_CreateScimToken_2.json @@ -0,0 +1,5 @@ +{ + "description": "description2", + "password": "secret", + "verification_code": null +} diff --git a/libs/wire-api/test/golden/testObject_CreateScimToken_3.json b/libs/wire-api/test/golden/testObject_CreateScimToken_3.json new file mode 100644 index 00000000000..9b35f1245fc --- /dev/null +++ b/libs/wire-api/test/golden/testObject_CreateScimToken_3.json @@ -0,0 +1,5 @@ +{ + "description": "description3", + "password": null, + "verification_code": "654321" +} diff --git a/libs/wire-api/test/golden/testObject_CreateScimToken_4.json b/libs/wire-api/test/golden/testObject_CreateScimToken_4.json new file mode 100644 index 00000000000..a79a8f35565 --- /dev/null +++ b/libs/wire-api/test/golden/testObject_CreateScimToken_4.json @@ -0,0 +1,5 @@ +{ + "description": "description4", + "password": null, + "verification_code": null +} diff --git a/libs/wire-api/test/golden/testObject_Event_team_2.json b/libs/wire-api/test/golden/testObject_Event_team_2.json index 65da16e3f30..9390895a7a8 100644 --- a/libs/wire-api/test/golden/testObject_Event_team_2.json +++ b/libs/wire-api/test/golden/testObject_Event_team_2.json @@ -1,6 +1,6 @@ { "data": { - "icon": "G*~􌍈\u0004\u001c贕%s\u0013|􊪚ZS󱤵jf𝒾-w􏳓{]R\u0007NwI\u0007󵻽?Z󹀡x𐂧*\t\u000f33􊟪3-j\u0012𩧈\u0002p,n.)*􅿽e=𘩢n􃧫𦺊aK\u000cfeF\u0017x𤛢\u001dX_󹱼R􍎨K𑒜gK􀜛2J\u0016M𘑑-+􁧰uW^Xwjlt\u000cGy;&󰝉\u001b󼏒\u0012^\u00178⌲wtq􈝊⿎󰆄n1~k\u0008k61R!󸤲􈱪􆟲􆙾w𬸔x\u0014􋾪蝚􇗫M05\u0005ZY#𖫡\u001e(ܘ􅓛{'W\u0014\u001a􃿾?n\u0004AhT-\u0018a;󷠟CV\"", + "icon": "3-1-f595b8ed-6dcf-41f2-8a2f-f662a9c0fce4", "icon_key": "𠄛Pp􄤣󱇓\u0005S壵S\u0006\u0015mM䪌0䬹突$rL,XvJ", "name": "i5\u0004󴱏􌃵􄑵1u􍸖1ꍰU*/*󳺾󴢾\u0013󲭷d􋱾4uG𪜿\u000cUh09\\󻇞\u000bPy\t𩯻\u000f\u001d0bV\u0018]䊙𗢔㭢\u001e\u0016X\u001b3[\u0018f\u0015" }, diff --git a/libs/wire-api/test/golden/testObject_Event_team_4.json b/libs/wire-api/test/golden/testObject_Event_team_4.json index b33b92e363a..6c72671656d 100644 --- a/libs/wire-api/test/golden/testObject_Event_team_4.json +++ b/libs/wire-api/test/golden/testObject_Event_team_4.json @@ -1,6 +1,6 @@ { "data": { - "icon": "S&\u0007V2b𣜖L\u0017𤭽z=w􇢇᩿𗰨\u0004\u0014<^󻓭A\u0001󿅰}\t󾴣𝑙F􀢦h\u0004xZZ\u001dg\u00155W6󳸱hn\rx4+\u0019󵌟=\u0013􋣭@o󸒏\u0015kR󹨮l^W)W=󺥎麴\u0016rrN𣕗󺤾S\u000f^,\u00175Q&z8D[㶏\u0017bas\u001aY\u001eR2𢗺󷑡G+'Q+󳚆", + "icon": "3-1-f595b8ed-6dcf-41f2-8a2f-f662a9c0fce4", "icon_key": "\u000fL\u0016~\u0011'](W\u0018뇂\u0004x􊧞Z\u0001봸\"\u001c=𨇚%捿x$󶵉􄂝Y@􃡊ཥ꒛7􂏉󸚑|rI󶳴𨙌[\u001e\tz\u0013r1g𗵊~嵙􄁝1\r𘕺p㥊𮪕i3$󽉏#󴝭f'\"㮐TK\u0017J\u0003o𩽴􇠐𐙨󷵸\u000epij𮐋󼉔?`𬢃oR􉔋~vi欕3Mc|\u0007^\"㠼kK𮖳c􉭜𡄕oiL􉌩~\u001b*𦏱\u000f{*,=𪖞𠄝g\u0013G=\u0019|\u0016A~􂇰qZ!𦸧\u0017M.kZBV󻷵\u0014𡽐\u000bI{𘘕q\u001e7𢲕B[e📹x<98{󵲨I?󼹊𠽴𣦓A\u0017,", "name": "d\u000f𪁤@o󱙾s&na𡐨􊖈𤟯|𔘯󸑴/󱞫v\u0015u\u0012f􋀈󷃠KC룪􄟵􂨺9_\u000b_^󿎖K𥽇\u000e Y*T\u0018􉒆<􂀆>𩾃ፁ\rt󽝓􅯾w2E🆆hS>\u0006_PQN,Vk\u0016􈩂=90\u00192e󰗦\u001fVA!\u0019\u001c\u0004e;𮕔1-Z$Z\u0015\r􌻮a\u001e%\u0000:𮄱먺𦝬?e]\u0003 𢴐 C\u0001\u000fS%8m􊦓V𣺻[󵪶6𩹚󶸓𨌰SX\n%􃆋*>\t+𠕋Y󱥶󲡂\u001dU􄨕6TU!*鲲90􁬜\u001eV𧪳N\t*\u0004{I<􈭶\u0001𬭌!c\\\n􎘭𬭪\u0011,-xX\u0019V?\t𩋈􁘟\u00121\u0001u\u0001콅\u000e+h\u0006::\u001e卬_g,\u000e*\u000b\u0014􋁎HFF𮇶􇻳fF\u001b2\u0001T\u0011)\u000cc豁l􃊫\u000c#~\u0002]󼭎/Or)kY󻳿\u0001NCk􄮲5􈡎x=H\u0000峐􂝖􌕙E/$pbi𡤲\u001cKi㴼󸤖\t7\"OL폀ᵜ5꧊\u0000(󻫄𨩲\u0001𤝟󲸓掩C==\u001dTV3l6󳹞.Z)$䅓|𪊼􊋿J;O\u001dbw\u000bI􌳠I\u0016\u0012^𤡾\u00023%i\u0019W𡵶\u0014􏸓tsL5𣺏W𗦼(_,􊙫*󾎇rckx\u0001\u000fs\u0001Jd𢔞\u0016ev.\u0014\u0010𘌊.􎍡󳚀𣁘\u001f_\u0017f\u0002\u000e\u0013󾴤6O\u0011Q\u0001'\u001d,|]W\u000fa𤸖.\u000b\u0007H&-L\u0012+𣻫􋝤\u0004m)䷕𬎛𬱈!𭎇𢹢m\u0014\u0013󼠪m\u001d𭒥>>\"NDw􆺍hY󼙧sFKz^ 􎣛5Qec\u0015}|􎣢.Q𪐺imb󺲔 p;􉸺\u0016􄌔kF􍐆r8o\u0011" + "password": "\u00085Ta𤱷𭥜fa&󿳄o!ov\u000f􌥥i\u0014\u0003Y\u000eR󲁛􉋏Ta^s\u0017\u000f[𮊌󱛣枌\u00186\u0002p􅆖-9󰌏&\u0015􀶤]^㋴;>-Z$Z\u0015\r􌻮a\u001e%\u0000:𮄱먺𦝬?e]\u0003 𢴐 C\u0001\u000fS%8m􊦓V𣺻[󵪶6𩹚󶸓𨌰SX\n%􃆋*>\t+𠕋Y󱥶󲡂\u001dU􄨕6TU!*鲲90􁬜\u001eV𧪳N\t*\u0004{I<􈭶\u0001𬭌!c\\\n􎘭𬭪\u0011,-xX\u0019V?\t𩋈􁘟\u00121\u0001u\u0001콅\u000e+h\u0006::\u001e卬_g,\u000e*\u000b\u0014􋁎HFF𮇶􇻳fF\u001b2\u0001T\u0011)\u000cc豁l􃊫\u000c#~\u0002]󼭎/Or)kY󻳿\u0001NCk􄮲5􈡎x=H\u0000峐􂝖􌕙E/$pbi𡤲\u001cKi㴼󸤖\t7\"OL폀ᵜ5꧊\u0000(󻫄𨩲\u0001𤝟󲸓掩C==\u001dTV3l6󳹞.Z)$䅓|𪊼􊋿J;O\u001dbw\u000bI􌳠I\u0016\u0012^𤡾\u00023%i\u0019W𡵶\u0014􏸓tsL5𣺏W𗦼(_,􊙫*󾎇rckx\u0001\u000fs\u0001Jd𢔞\u0016ev.\u0014\u0010𘌊.􎍡󳚀𣁘\u001f_\u0017f\u0002\u000e\u0013󾴤6O\u0011Q\u0001'\u001d,|]W\u000fa𤸖.\u000b\u0007H&-L\u0012+𣻫􋝤\u0004m)䷕𬎛𬱈!𭎇𢹢m\u0014\u0013󼠪m\u001d𭒥>>\"NDw􆺍hY󼙧sFKz^ 􎣛5Qec\u0015}|􎣢.Q𪐺imb󺲔 p;􉸺\u0016􄌔kF􍐆r8o\u0011", + "verification_code": null } diff --git a/libs/wire-api/test/golden/testObject_Login_user_12.json b/libs/wire-api/test/golden/testObject_Login_user_12.json index 8a64a9fb2f7..eaf85348770 100644 --- a/libs/wire-api/test/golden/testObject_Login_user_12.json +++ b/libs/wire-api/test/golden/testObject_Login_user_12.json @@ -1,5 +1,6 @@ { "label": "\u000f🜓-𞡌:𡍁鮸\u0006\u000e", "password": "n􋜩Q𩗀\u001b󵅀&Q/\rdꠚ\u001f\u0004w2C\u0006􁹬𫝔\u0004\u0004v󶥜\u0008f,b\u0002󷜰'𪹐C]G듡󸓯𮤾4\u0000Y.𪘲\u000e3sI菌F􈲾5剑rG/:\"󷣦X띟6\u001c:\u0018\u0007eYwWT􈦚𡛑Msbm\u0015@󰗜󷜉\u0004^\u001c𣹘\u0015@\u0005>\u000c\u001eUc\u0004V9&cල\u0007󰱴a'PPG𘡝𫶶>[ൽ2ﷄXc𠃪[0󴲖\u0008𘕄B\u0011[󻑵\u001d䰻\u001f\u0019s-u\u0017s􄡽󵗐𧝿n􅳀?󿒋ck\u00148XC𪣑\u001eI2ମ\u0002\u0010M\u001b\n?<\\\u0013E𑨛\u001d\n$cyS𡐆!,\u000b9\u0017/\u0011?P\u0017ꌞ\u0012󴁱~􂟉W-W4K8.\u00127\u0019L􇌡h\u000f}t+H\u001a\u001bX𝛋s\u0004t𫘧taa\u001d\u000c𥌭(v󺈨M\u001bvg3P1󼊃]gཝ4T\u0015$镄);\\8􎲭\nK\u0015}D'^fJ'𢽥e𪟤骭!\u0019.\u0012{\\CEp󿎈\u0017k_􈨀䟝𨄪􃨬]MG$𭴂[E􏠾\u0008􆅏{b엚\u001b^b@W\u0015$\u001c<󹾗&𦅘R\u0006J\u000f􊷴􌳱ꇞn󵸞8]𤍀\u0005}|k\u0002\u0018Q\u001fI\u0007\u0018DZ􃟝\u0000쐕rb䨃3G%\u001c𧤡\u0004\u00154YB0-i󸣑IM􆋴[􏘂:Cr$𘔴)L𡚅W鿁.x;ꇵ󻨷󳃅\u001fkb\u0018Y9)\u00164\u000f􍙥Av.\r\u000c􃏥9{\u000e\u0017P\u000c茂u\r-9cB4󸄛G\u001e夡󷯔r📷HcsPms𝢛!|J<\u00108\u001c[\u0015WYAK𒔃^󰾪c3󾜀\u0007C\u0003\u0017􁐫Y\u0014f\u0006􃁑!󼀑:RlQ!BX\u000c=􅙦f𤽂𛰿O\u0003\\\"퀛B<\u001eLV4g%3􌅏\u0006`\u0015>\n깒kp󰯶𩷗H冘lyJ\u0012)􁦭(󺰛A\u001ch\u0004j観\u0014M\u001bP-q\u0008n\u0018𢿎~\u001d\u0019\"o合%*e2𨛝L󹼿sy𥕑2m\u001d􀇖{EG]\u00116B+{󰉆IYa󶈙5,<\u001bX\u000c\u000f𭣵𥢐E𠴇󶶐L<\u0019dUO\u0017\u001aZYm\u0006􉍰R\u001a󲋒\u0013^s\u000cu_g\u0019?i~}V2𤓉R\u001c\u00043j댑m؆􌱔\n7S\u000fT5j𩮢\u000f󷵝𢤓h𬣐Q𣲺\u0015ZM􏈮02f🤼l!*\u0001󺯙\u0001􅔰􋑷\t𑱥\u001ba:q UKEN\u001e-\n\u0003H坝a􆘓\u0008鉶\"󼳴𤢿󼎳R4\u0003\u0010\u001c\u0002󵓎%\"@󶛙6=/x\u0000P\u0004𪬗/𮙙\u000c\u000c󵙚?*\u000cIcKWQ\"󴣾P*􋢩6=d\n𦟰\u001e􉧚\u0004\u0012I릍U\u0008=Pc\u0010", - "phone": "+153353668" + "phone": "+153353668", + "verification_code": "123456" } diff --git a/libs/wire-api/test/golden/testObject_Login_user_20.json b/libs/wire-api/test/golden/testObject_Login_user_20.json index 6e65bd2e8e5..df758baf81d 100644 --- a/libs/wire-api/test/golden/testObject_Login_user_20.json +++ b/libs/wire-api/test/golden/testObject_Login_user_20.json @@ -1,5 +1,6 @@ { "email": "[%@,", "label": "􁫀\r9󳰔`\u0015x", - "password": "ryzP\u00139⬓-1A)\u0008,u℉j~0􊐔󼘏\u000cI𩤎er\u0014V|}'kzG%A;3H\u0007mD\u0002U1\u0000^󾴴\u0010O&5u\u0004\u001a𨲆0A󳍿X\u0012\u001c7fEt𗱖rPvytT𡛓!𘥩$Q|BI+EM5\u0015\tRKrE\u0010\u001f\r?.\u0002|@1v^\u000bycpu\n$\u0012𭤳𠊆-Q𤸩\n\r󼛽𐬝O\u0005*𐰴Z\u001fo\u0004n𮂕%&\u0013Me*\u0002;\u0010034\nv\u0015𢑮(􆤦󱮺n@􎥹|봥d\n*\u000f\u0000}\u0015A!󿕺󽃯Hx\u00173\u0002{#T|5|GC􉸮z.\u001fN􇸓圴\u000bu\u0016~LP𤁿CV\u000e q𥆐\u0012e8h\u001fg󸷞;\u000c󳌋􎫐At󹦊)\u001fG\u0013𨪍馩|󾙻\u000f𠮹\u0004c~6\u0010:u𨘑##^\nn{d\u0018\ng㽭\u001b\u001f\u001f~A8};T\u001e\u0015)&\u0008\u0006􎁼\u001d(\u0013u;􋐛;=e􀨚\"黝vCt)o󰽾mꮈ𓄈l1+󼿼[\u0002FLx􇹤:󻼥󲗰71/kE𖹛p\u0014Ij\u0017蓳&\u001a^\u001cl1\u0006󸺜\u0003W,+3🐺𗖷\u001077rG'􇚂JC9M􁑬\u0016\u0012괾>~󸇴Y􃒫=i-\u000cS𪆘𦍨K2-@\u0005\u000c􎭳_1D-&🖂lR𭭰/󲫄$:窷:찫Dg󷷋O󶧽𩢅\u000e𫹟2z\u0015q𢣫c\u001cliJ{􁲵􂳦'BL𩋞;\u0002󿤼䠋B\u0000ẟb􅶹:w􎠰Ad\u001a6\u0015oퟯ\nsPWM{\u0003fW󸨅JT󹖱$󱞍핐𮝮𪓋u4􍖶\t蓥󽱢\"𥚰UM􈫴􋛮蔹􍶭\t\nIn'􅗄剩㻛\u0019\u0011<\u000b\u0008W\u000f}𢧯\u0008􅳓󼰓\u001d`􋍃x\u0000󰼹K\u001cj􇟷\u0011\u000f𩐠d󲆄k4\u001a󶣔쌗^􀾃󸐫i2=$:[f􃺃\u0012n\u0015J<=߬\u000f!z􍷔\u000eN\u0015\u0019𬈌V󺍬CQ_G\nY#ky𠚫k\u0013\u0005}OC𗤶}~M\u0019p\u0003\u001ex\u0008𬺚􅽰\u00088/\u0014?􈄶B󺝎\u0004\u000eU󹏩\u001b=%읶J𩎗\u0017󲕑󱱨󰡢\to􌳬X_@@뀷ꮰ$" + "password": "ryzP\u00139⬓-1A)\u0008,u℉j~0􊐔󼘏\u000cI𩤎er\u0014V|}'kzG%A;3H\u0007mD\u0002U1\u0000^󾴴\u0010O&5u\u0004\u001a𨲆0A󳍿X\u0012\u001c7fEt𗱖rPvytT𡛓!𘥩$Q|BI+EM5\u0015\tRKrE\u0010\u001f\r?.\u0002|@1v^\u000bycpu\n$\u0012𭤳𠊆-Q𤸩\n\r󼛽𐬝O\u0005*𐰴Z\u001fo\u0004n𮂕%&\u0013Me*\u0002;\u0010034\nv\u0015𢑮(􆤦󱮺n@􎥹|봥d\n*\u000f\u0000}\u0015A!󿕺󽃯Hx\u00173\u0002{#T|5|GC􉸮z.\u001fN􇸓圴\u000bu\u0016~LP𤁿CV\u000e q𥆐\u0012e8h\u001fg󸷞;\u000c󳌋􎫐At󹦊)\u001fG\u0013𨪍馩|󾙻\u000f𠮹\u0004c~6\u0010:u𨘑##^\nn{d\u0018\ng㽭\u001b\u001f\u001f~A8};T\u001e\u0015)&\u0008\u0006􎁼\u001d(\u0013u;􋐛;=e􀨚\"黝vCt)o󰽾mꮈ𓄈l1+󼿼[\u0002FLx􇹤:󻼥󲗰71/kE𖹛p\u0014Ij\u0017蓳&\u001a^\u001cl1\u0006󸺜\u0003W,+3🐺𗖷\u001077rG'􇚂JC9M􁑬\u0016\u0012괾>~󸇴Y􃒫=i-\u000cS𪆘𦍨K2-@\u0005\u000c􎭳_1D-&🖂lR𭭰/󲫄$:窷:찫Dg󷷋O󶧽𩢅\u000e𫹟2z\u0015q𢣫c\u001cliJ{􁲵􂳦'BL𩋞;\u0002󿤼䠋B\u0000ẟb􅶹:w􎠰Ad\u001a6\u0015oퟯ\nsPWM{\u0003fW󸨅JT󹖱$󱞍핐𮝮𪓋u4􍖶\t蓥󽱢\"𥚰UM􈫴􋛮蔹􍶭\t\nIn'􅗄剩㻛\u0019\u0011<\u000b\u0008W\u000f}𢧯\u0008􅳓󼰓\u001d`􋍃x\u0000󰼹K\u001cj􇟷\u0011\u000f𩐠d󲆄k4\u001a󶣔쌗^􀾃󸐫i2=$:[f􃺃\u0012n\u0015J<=߬\u000f!z􍷔\u000eN\u0015\u0019𬈌V󺍬CQ_G\nY#ky𠚫k\u0013\u0005}OC𗤶}~M\u0019p\u0003\u001ex\u0008𬺚􅽰\u00088/\u0014?􈄶B󺝎\u0004\u000eU󹏩\u001b=%읶J𩎗\u0017󲕑󱱨󰡢\to􌳬X_@@뀷ꮰ$", + "verification_code": "123456" } diff --git a/libs/wire-api/test/golden/testObject_Login_user_3.json b/libs/wire-api/test/golden/testObject_Login_user_3.json index 2bf69d1d46d..54947a4de3b 100644 --- a/libs/wire-api/test/golden/testObject_Login_user_3.json +++ b/libs/wire-api/test/golden/testObject_Login_user_3.json @@ -1,5 +1,6 @@ { "handle": "c2wp.7s5.", "label": "􈏺𐌺>XC", - "password": "&\u001e\u0014􍢴ZⲚn\u000e𦯣󶳚/🄕'􃡾m󶪩\"☬𡷝\u001e(&󳓮\u000ef1Wf'I\u000f𘞾󿫦󼛩\u0011Jq􀠱Y\\Bedu@󷭷c󵇒D쿛􀟶S𣐞\u0003\u0003W>󵜮\u0014\rSO8FXy𨮱a\u0019𩠡\u001aNF𦧁L\u001e$5\u0000k\u001ez*s𤔬𦤜\u000b𪴹\"SY\u0002󲶃􍚚ub5q\u0005󷨛\u000bN.\t𬳰:l􍷴\u001e󺺉\u0007𩁁\u000e\u000bt􌏐W\u0016󾟜􎿛\u0007'v\u0017䀘\u0015\u0002 \u0015\u0002숔,􏙎x󿱴^􄡷櫦I;\u0015b􊧑o𧯋_𮡒MME󹩀\u000f􋨼H;\u000e\u0017s\u000e􄏑{Knlrd;讦\u0014\u000f􆝀TO􊏡󴃗U뺓􌢗t􄺈^y䍴u$\u0011Jp􁙤𨐩𨉞\u0002\"􋛧*\u000e󵌎綦󱻌X􌑜\u0003sK}\u0008𣈮\u00000󱘴12𩱬\tM052𮑯\u00040\u001e󰰚􈴐{ji\u001b󹎀橻&t \u000f\u001by\u0007L𡎯𠇦󲫫\r􁡥ga,\u0014do,tx[I&\u0014h\u0010\u0003\u0010Bpm󴬴-\u0007]/ZI󼎝q]w3n뜿e岌kYo5􊔜'K􊄜}v𣵇;󸮨\\=ꄰ8g\u0010g*has걿󵨦\u0013\u001fYg?I䰆\u0015aW2𤮏m\t}h𥸙RbU\u0002\u0017lz2!\u0013JW5\u001b󺡬U\u000eg,rpOᛡ]0\u001bǟ󵞃F\u000f󿗪\u001e\u000e⺄rl􍦲~\u0006+Mn{5󲧸a\u00192\u000b{jM\u0017T􂔹$\u0011􌣆\u001dj_~Z󵸥P\u0001\u0004o@TJhk\u0004\u0017k:-𗥇[p\u0010\u0011\u001e'\r\u0002Q,,󸐢?H\rh瘑\rj𤈎\u0012\\(u\u001bu𥱑󴳈o\u0014󱕌􍙩􀶂\u0011q\u001d-\u0008齧\u0011qW>\u000cysῂ,'𧃒<" + "password": "&\u001e\u0014􍢴ZⲚn\u000e𦯣󶳚/🄕'􃡾m󶪩\"☬𡷝\u001e(&󳓮\u000ef1Wf'I\u000f𘞾󿫦󼛩\u0011Jq􀠱Y\\Bedu@󷭷c󵇒D쿛􀟶S𣐞\u0003\u0003W>󵜮\u0014\rSO8FXy𨮱a\u0019𩠡\u001aNF𦧁L\u001e$5\u0000k\u001ez*s𤔬𦤜\u000b𪴹\"SY\u0002󲶃􍚚ub5q\u0005󷨛\u000bN.\t𬳰:l􍷴\u001e󺺉\u0007𩁁\u000e\u000bt􌏐W\u0016󾟜􎿛\u0007'v\u0017䀘\u0015\u0002 \u0015\u0002숔,􏙎x󿱴^􄡷櫦I;\u0015b􊧑o𧯋_𮡒MME󹩀\u000f􋨼H;\u000e\u0017s\u000e􄏑{Knlrd;讦\u0014\u000f􆝀TO􊏡󴃗U뺓􌢗t􄺈^y䍴u$\u0011Jp􁙤𨐩𨉞\u0002\"􋛧*\u000e󵌎綦󱻌X􌑜\u0003sK}\u0008𣈮\u00000󱘴12𩱬\tM052𮑯\u00040\u001e󰰚􈴐{ji\u001b󹎀橻&t \u000f\u001by\u0007L𡎯𠇦󲫫\r􁡥ga,\u0014do,tx[I&\u0014h\u0010\u0003\u0010Bpm󴬴-\u0007]/ZI󼎝q]w3n뜿e岌kYo5􊔜'K􊄜}v𣵇;󸮨\\=ꄰ8g\u0010g*has걿󵨦\u0013\u001fYg?I䰆\u0015aW2𤮏m\t}h𥸙RbU\u0002\u0017lz2!\u0013JW5\u001b󺡬U\u000eg,rpOᛡ]0\u001bǟ󵞃F\u000f󿗪\u001e\u000e⺄rl􍦲~\u0006+Mn{5󲧸a\u00192\u000b{jM\u0017T􂔹$\u0011􌣆\u001dj_~Z󵸥P\u0001\u0004o@TJhk\u0004\u0017k:-𗥇[p\u0010\u0011\u001e'\r\u0002Q,,󸐢?H\rh瘑\rj𤈎\u0012\\(u\u001bu𥱑󴳈o\u0014󱕌􍙩􀶂\u0011q\u001d-\u0008齧\u0011qW>\u000cysῂ,'𧃒<", + "verification_code": "123456" } diff --git a/libs/wire-api/test/golden/testObject_Login_user_5.json b/libs/wire-api/test/golden/testObject_Login_user_5.json index 13b2f46ee04..19f1aa2570f 100644 --- a/libs/wire-api/test/golden/testObject_Login_user_5.json +++ b/libs/wire-api/test/golden/testObject_Login_user_5.json @@ -1,5 +1,6 @@ { "handle": "c372iaa_v5onjcck67rlzq4dn5_oxhtx7dpx7v82lp1rhx0e97i26--8r3c6k773bxtlzmkjc20-11_047ydua_o9_5u4sll_fl3ng_0sa.", "label": "LGz%𝒍j\u000c\u001e/\u0001", - "password": "𝘛𭆴DU󼸸hp󵱻t~\u0012\u0001\u0002*􁈚y1􇑮H𪒧{e\\S\u000e?c_7\t\u0014X𡀓6𪊲E𘝈j\u001a\t\u0016􉯿>HO]60󱭓\u0003\"+w,t􄐸\u0007k(b%u𤺝`>b󻂰e\u0006c𤽡􎠜)し7􈑠`𭟉yO+v%󼗀\rc<𐃤2>8u􋉲􇵝􏸗𒔙a𫯹\u0015=\u0004􆼝8R&j󾣆\u001b\t4sj-󲉛鷔n𡘽􃲙N\u001d\\󺡋𑩠5\r𗫬(P!爳棧\u0008􄫼Mr~﹣\u0019jt>Z\u001d~𢖼A󻲾\u000e\\>\u00116\">%댤􈵏I@u5𭷳\u000brY\r;7􅟌#􇒇󸇞\u0018'󾏵\u0019_I_zY󱂤𤟁\u0019d󽷤cd􃑯𡒆Cp3曕\u001dXj\n듡jy값\t-䩹ꥈd󿠓L\u001aYF\u0006POL헮\u0012\u0011\u0011\u0012*\rH\u0010(?\u0013F擜\u0010\r]􅆋j𩣁 @\u0005T􌮍s\u001cF2\u0015]8\u0007\u0013!\u0015W𫅕􏌲K󺐢􏢞_%󴥚􏯭'􌆥𑋘(#\u0001ky\t\u0017!䒢\u0015\u0014\u001b{𝈕U2LS'" + "password": "𝘛𭆴DU󼸸hp󵱻t~\u0012\u0001\u0002*􁈚y1􇑮H𪒧{e\\S\u000e?c_7\t\u0014X𡀓6𪊲E𘝈j\u001a\t\u0016􉯿>HO]60󱭓\u0003\"+w,t􄐸\u0007k(b%u𤺝`>b󻂰e\u0006c𤽡􎠜)し7􈑠`𭟉yO+v%󼗀\rc<𐃤2>8u􋉲􇵝􏸗𒔙a𫯹\u0015=\u0004􆼝8R&j󾣆\u001b\t4sj-󲉛鷔n𡘽􃲙N\u001d\\󺡋𑩠5\r𗫬(P!爳棧\u0008􄫼Mr~﹣\u0019jt>Z\u001d~𢖼A󻲾\u000e\\>\u00116\">%댤􈵏I@u5𭷳\u000brY\r;7􅟌#􇒇󸇞\u0018'󾏵\u0019_I_zY󱂤𤟁\u0019d󽷤cd􃑯𡒆Cp3曕\u001dXj\n듡jy값\t-䩹ꥈd󿠓L\u001aYF\u0006POL헮\u0012\u0011\u0011\u0012*\rH\u0010(?\u0013F擜\u0010\r]􅆋j𩣁 @\u0005T􌮍s\u001cF2\u0015]8\u0007\u0013!\u0015W𫅕􏌲K󺐢􏢞_%󴥚􏯭'􌆥𑋘(#\u0001ky\t\u0017!䒢\u0015\u0014\u001b{𝈕U2LS'", + "verification_code": "123456" } diff --git a/libs/wire-api/test/golden/testObject_Login_user_6.json b/libs/wire-api/test/golden/testObject_Login_user_6.json index 27db0bca02d..498aa516f3e 100644 --- a/libs/wire-api/test/golden/testObject_Login_user_6.json +++ b/libs/wire-api/test/golden/testObject_Login_user_6.json @@ -1,5 +1,6 @@ { "label": "󷭄'󳩽KW\\\u0000\u0014", "password": "K?)V𤊊}_𭏷􃁘\u000cJ3!󰷕􃕍즟𨪷􅟘\u0007󷽻\u00017\\#z9𠥿􇽋󰩚󾏒\u0019\u0013𦈎'\r)~Ke9+𪷶𪺢󲭎M􌔩\"h\u0001Th\u0004`;\u0006􊶠\u0005󺦪'e{\u001cv鼵\u001f𢿻*㽬􆺦츟:E]:R􋂿K}l􏙠Y집􀋦S~\u0004#T󻓄1hIWn\u000b`놏Kb~\u001b\u0010dT\u001c\u000f􊨭f\u0017Y7\u001e𠋜\t󳸻㑦뱲\u001dG\u0013BH#\\RAd𨣓g􅳤􁙼\u000fk&\u0002E囉\u001c\u001c\u001c$t󴧥:O􌐑q}_󽯀.\u0001\u0014\u0002𦙎c`L>􀡸l􉔂m'BtB5󴼐,t\"􄕤9(#\u00054\u000fIy>󻯶􌫾\u001dbf\"i\u0017㠟a􉊡C@􇘼􊨩纟\u0015󳻹嬰*N\u0016\u001b:iXibA𡚓𩘤q􀁗]:9r𒁉\u0000􀢋\u001fCN\u001f𤃾􀁹󸐝eR\u001eZbD5!8N\u001bVᲰ\u0006𪐈\u001auz􁓾𭾔~\u001b\u000f%{3I/F抐/DMS\u001f>o𭬿Z􎬞\u001d[K𭇡𗇅􉭱󳀒\u001bO-4\u0018\u001f\u001cZp", - "phone": "+930266260693371" + "phone": "+930266260693371", + "verification_code": "123456" } diff --git a/libs/wire-api/test/golden/testObject_Login_user_7.json b/libs/wire-api/test/golden/testObject_Login_user_7.json index 08463885c07..a492835be1e 100644 --- a/libs/wire-api/test/golden/testObject_Login_user_7.json +++ b/libs/wire-api/test/golden/testObject_Login_user_7.json @@ -1,5 +1,6 @@ { "email": "BG@⽩c\u000b}\u000fL$_", "label": "\u000e\u0015eC/", - "password": "&󲉊󹴌𔖘\u0002J<-~\u0002>\u000b𒇴𥄿5QN틐𨤨ql\u0015𒈲3}{\u0013𪒺S壓;\t7𬺖_F~D*f􀕔)􄥂-9僛7GK= %\u001e@kOF#𫻩􋌁𞡂8_ꕅ\u001dL鍂\u0003󿶊0Wl1A`LYz\u001fy僸\u001ao\u001b[\u0014\u0008t𐑐a\u0003s~\u001fF𪰤G`$\u000bG\u0011󾿅🙣/󷪺C>\u000f" + "password": "&󲉊󹴌𔖘\u0002J<-~\u0002>\u000b𒇴𥄿5QN틐𨤨ql\u0015𒈲3}{\u0013𪒺S壓;\t7𬺖_F~D*f􀕔)􄥂-9僛7GK= %\u001e@kOF#𫻩􋌁𞡂8_ꕅ\u001dL鍂\u0003󿶊0Wl1A`LYz\u001fy僸\u001ao\u001b[\u0014\u0008t𐑐a\u0003s~\u001fF𪰤G`$\u000bG\u0011󾿅🙣/󷪺C>\u000f", + "verification_code": "123456" } diff --git a/libs/wire-api/test/golden/testObject_Login_user_8.json b/libs/wire-api/test/golden/testObject_Login_user_8.json index 5ac8f6d0b56..49eb4bbfed5 100644 --- a/libs/wire-api/test/golden/testObject_Login_user_8.json +++ b/libs/wire-api/test/golden/testObject_Login_user_8.json @@ -1,5 +1,6 @@ { "email": "@~^G􆪐\\", "label": null, - "password": "z>􉰃󺎇/𡞯􊳌\u0008%$󽖨𣄄:}\t\u0018􂜙󾺽)㊝󵙼s󵪾\u0018}鱢\u0019[ꅾ\u000bX#VG,df4𢢵8m5딝OTK𣑌鋎꺯◆Z\"ZS\u001bms|[Q%􉲡\u0005W\\󴖙C𭌈+􅕺ဒ䖡v𬁡ꎞ){󻆡𣃒f𭬔}:X-\u00082N\u0019\u001fl🎢쇈Y􅤡󷐛r2.1싸\u0004+𡥙\u0013𣡈]'󻂳s󳤴ꜵ.}𭋣o󲍶X𠜥⬅\r\u001aNq6󸻕'\u000cd\u001e㢧􋾜,:%\t𥅬𒃣QD􉖠\u001b(q4KDQ2zcI\u0010>\u00195󲤼1\u000cBkd\u0013\u0006:F:\u0004𘨥ⶂO N\u001c,N􁚶󴌷[h9ᜬ:xZ=\u000c􈾀\u0013u\u001e\u000ce#\u001a^$lkx耤 \rr\u001aJ󷝦󸓡\u001cR][_5\u0015ⷤ诃5惵􁮵󳗴鉅K!􁁠eRR%絬+h~1󲞮谟lTzS$\u0010􂶳\"*􉕷pmRE\u0013(\u001f^ὯJc➑􅫇i\n+G$|󲫉𦉻g\u001c\u000cgU3Y𝄜\u0006f)􊾺\u0016𓈄􌭞/\u0000Piꩦ{󿸟j􈞅\u001c9𠚽󺊬翉w$눟𞴦)Si𨴄牿FX􂋒j{`궤`󳿗𧁁4u%􅔪P*􂉻捎C\u001eR\u0016-잚󶽕g𐰺:S>c㮢𠝌\u0010Y􄝏~a)YW_J􃢤P\u0007+ U􈷓j\u0019k\u0001􋴘\u0011䣷e𪋘𪳠,ᐏg@\u0012\u001dHXl.\u0017𥣁2\u0013mY􁢫\tv?L8L􆍼N𠦽\nb1j󾸸𤋵xfQ=\\\u0005e󳇪󹽶U\u0012p{\u000e􌚌jd^@U󲯝tP.\u0012Y%R`a\r𧍮7}HnUf𠛸m^7:\u0015=챼>l𗑑hwp27𤦾jE\u000cx=!.\u0013]Ar\tw\u0014&\u001ak㒞s󾦄ᆒI𣪗􂼥dsY\u0010𬚢dX.𣭷i]𤹉󻃀\rWS\u001fU􌏬\u001a시􈨂\u0010\u0002N~-\u000e6𮙏􏄲\\O𭍍Jc􀻇􅢮\u0000HSo\u0010-W\u00136𩥑I􄺨)𘗘={𘗔h洹M󹩪FwJQ􏞨ck\u001a\u0018|UV-\u0015\u0001|\u0014;\u000c𦓫𣦃\u0005S\u0015.B\"D𧲿#o*𞹱胜m\u001e􀓪B3Gg;\u0011\\𬆳􌒮\u0005 B^\u000f𥐶$e餴𩠵>fMgC𭮌,o🗨\\?󼛣~/s\u0001?MMc;D18Ne\u0004\u0018)*\u0002\u001d㾌􉰃󺎇/𡞯􊳌\u0008%$󽖨𣄄:}\t\u0018􂜙󾺽)㊝󵙼s󵪾\u0018}鱢\u0019[ꅾ\u000bX#VG,df4𢢵8m5딝OTK𣑌鋎꺯◆Z\"ZS\u001bms|[Q%􉲡\u0005W\\󴖙C𭌈+􅕺ဒ䖡v𬁡ꎞ){󻆡𣃒f𭬔}:X-\u00082N\u0019\u001fl🎢쇈Y􅤡󷐛r2.1싸\u0004+𡥙\u0013𣡈]'󻂳s󳤴ꜵ.}𭋣o󲍶X𠜥⬅\r\u001aNq6󸻕'\u000cd\u001e㢧􋾜,:%\t𥅬𒃣QD􉖠\u001b(q4KDQ2zcI\u0010>\u00195󲤼1\u000cBkd\u0013\u0006:F:\u0004𘨥ⶂO N\u001c,N􁚶󴌷[h9ᜬ:xZ=\u000c􈾀\u0013u\u001e\u000ce#\u001a^$lkx耤 \rr\u001aJ󷝦󸓡\u001cR][_5\u0015ⷤ诃5惵􁮵󳗴鉅K!􁁠eRR%絬+h~1󲞮谟lTzS$\u0010􂶳\"*􉕷pmRE\u0013(\u001f^ὯJc➑􅫇i\n+G$|󲫉𦉻g\u001c\u000cgU3Y𝄜\u0006f)􊾺\u0016𓈄􌭞/\u0000Piꩦ{󿸟j􈞅\u001c9𠚽󺊬翉w$눟𞴦)Si𨴄牿FX􂋒j{`궤`󳿗𧁁4u%􅔪P*􂉻捎C\u001eR\u0016-잚󶽕g𐰺:S>c㮢𠝌\u0010Y􄝏~a)YW_J􃢤P\u0007+ U􈷓j\u0019k\u0001􋴘\u0011䣷e𪋘𪳠,ᐏg@\u0012\u001dHXl.\u0017𥣁2\u0013mY􁢫\tv?L8L􆍼N𠦽\nb1j󾸸𤋵xfQ=\\\u0005e󳇪󹽶U\u0012p{\u000e􌚌jd^@U󲯝tP.\u0012Y%R`a\r𧍮7}HnUf𠛸m^7:\u0015=챼>l𗑑hwp27𤦾jE\u000cx=!.\u0013]Ar\tw\u0014&\u001ak㒞s󾦄ᆒI𣪗􂼥dsY\u0010𬚢dX.𣭷i]𤹉󻃀\rWS\u001fU􌏬\u001a시􈨂\u0010\u0002N~-\u000e6𮙏􏄲\\O𭍍Jc􀻇􅢮\u0000HSo\u0010-W\u00136𩥑I􄺨)𘗘={𘗔h洹M󹩪FwJQ􏞨ck\u001a\u0018|UV-\u0015\u0001|\u0014;\u000c𦓫𣦃\u0005S\u0015.B\"D𧲿#o*𞹱胜m\u001e􀓪B3Gg;\u0011\\𬆳􌒮\u0005 B^\u000f𥐶$e餴𩠵>fMgC𭮌,o🗨\\?󼛣~/s\u0001?MMc;D18Ne\u0004\u0018)*\u0002\u001d㾌1/\t\u0015 󶫒󷘿z苐Bv􎲋(=<\u000eq􍪬?L᪽􄗻ஜc󳤌<&!􍚌󴆏j~O3USw\u0012\u0003\u0007\u0017+󺀡Ny粰(/Sco\u0002{3\u000fEh\u0016󼆏󹫐气-\u001c.'\u0005X𘉸𤮓Ti3􀩲\"%\u0016\u0008𮀜+\u0004\u0002^􎧯)2bR\u0006\u000fJB[󿊻&O9{w{aV\u0005gZ?3z􄈭8፳𦔖󱴵`􃥔\"PE)uKq|w\u00160\u001b. \u0003𑻠sxW𧉥󴚗m\u00057e)𓁘󶑼:s\u0018Yj⚎㿤\u0006\u001flTu􏄥I.􉙜O#kQ\u001e!g􃔗\u0018Q\u001f𪍃\u0016\u0006|\"M\"P\u001f\u0003@ZPq󸌖gY𤒍=\u0007􂍭l8󾌀3󲻄󹪢CN<𤆤gJ󽡢]𗋔mX~\u0006w3\u0010𫸴8\u00076\u0004}\u0010i\u0013L5󼂐PY^|!Vz\u001b4走!iLa⼻\u0014􂭺𩀜\u001d:󾟿𤢈h\\dLx􉣕\u0019𥚚\u001a𠷫R%ps7𗏀s􆓓fg\nIf􄢿\u0011l\u001a󹮗-n_ឱUY?4d]|c\\[T\u0007jS䦖휆鄐aK󺖖􏩠\u0003\u001cx+" + "password": ">1/\t\u0015 󶫒󷘿z苐Bv􎲋(=<\u000eq􍪬?L᪽􄗻ஜc󳤌<&!􍚌󴆏j~O3USw\u0012\u0003\u0007\u0017+󺀡Ny粰(/Sco\u0002{3\u000fEh\u0016󼆏󹫐气-\u001c.'\u0005X𘉸𤮓Ti3􀩲\"%\u0016\u0008𮀜+\u0004\u0002^􎧯)2bR\u0006\u000fJB[󿊻&O9{w{aV\u0005gZ?3z􄈭8፳𦔖󱴵`􃥔\"PE)uKq|w\u00160\u001b. \u0003𑻠sxW𧉥󴚗m\u00057e)𓁘󶑼:s\u0018Yj⚎㿤\u0006\u001flTu􏄥I.􉙜O#kQ\u001e!g􃔗\u0018Q\u001f𪍃\u0016\u0006|\"M\"P\u001f\u0003@ZPq󸌖gY𤒍=\u0007􂍭l8󾌀3󲻄󹪢CN<𤆤gJ󽡢]𗋔mX~\u0006w3\u0010𫸴8\u00076\u0004}\u0010i\u0013L5󼂐PY^|!Vz\u001b4走!iLa⼻\u0014􂭺𩀜\u001d:󾟿𤢈h\\dLx􉣕\u0019𥚚\u001a𠷫R%ps7𗏀s􆓓fg\nIf􄢿\u0011l\u001a󹮗-n_ឱUY?4d]|c\\[T\u0007jS䦖휆鄐aK󺖖􏩠\u0003\u001cx+", + "verification_code": "123456" } diff --git a/libs/wire-api/test/golden/testObject_NewConvManaged_user_1.json b/libs/wire-api/test/golden/testObject_NewConvManaged_user_1.json deleted file mode 100644 index 7772dcde548..00000000000 --- a/libs/wire-api/test/golden/testObject_NewConvManaged_user_1.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "access": [], - "access_role": "non_activated", - "access_role_v2": [ - "team_member", - "guest" - ], - "conversation_role": "37q9eeybycp5972td4oo9_r7y16eh6n67z5spda8sffy8qv", - "message_timer": 193643728192048, - "qualified_users": [], - "receipt_mode": 4, - "team": { - "managed": true, - "teamid": "00000001-0000-0001-0000-000200000000" - }, - "users": [] -} diff --git a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_1.json b/libs/wire-api/test/golden/testObject_NewConv_user_1.json similarity index 100% rename from libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_1.json rename to libs/wire-api/test/golden/testObject_NewConv_user_1.json diff --git a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_3.json b/libs/wire-api/test/golden/testObject_NewConv_user_3.json similarity index 100% rename from libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_3.json rename to libs/wire-api/test/golden/testObject_NewConv_user_3.json diff --git a/libs/wire-api/test/golden/testObject_PasswordChange_user_1.json b/libs/wire-api/test/golden/testObject_PasswordChange_user_1.json index efc8fa9efea..56d9750fae6 100644 --- a/libs/wire-api/test/golden/testObject_PasswordChange_user_1.json +++ b/libs/wire-api/test/golden/testObject_PasswordChange_user_1.json @@ -1,4 +1,3 @@ { - "new_password": "\u0001f;0B+CKY󾃹W\u0005𫧻𐑹􇟚󽀰f\u0001&𨫳􀟘󹪊􊤍𣧚\tX𮥥\u001chl󹏂^A𥠮=𤻆\u0019_ow?\\h4𥼫𐬤\u0016⹋4O\u0015nJT\u0004r\u0018~\u0006\u0017R'󲏴sX󴤦mlCHvg􏥩ba󵂐R\\󼝬o󱢀􁹎*k\r)H𬂳􀧘\n#㠞~\\9Q|\u000b;\u001fd.􄙔0SHP%󸹆囇'!󾺴N\u001a!Kz@\u0000𒅟􉤛\u001aVp􌥏鞴\u00023#\u0014}}􋉝N𝙺`𩖂7󼽅\u0010𥱥^\u0002{`i:\rUT!\u0013􏚔𥏟\u0015WK\u0000􌋍􍅦eA𢚊\u0003𩿡󼣩t@?󷭺\u0001J􆔶7\u001eg{𓆲5R_\u0013u\u000f𥝛􈑉`}𐔔X\u0011𪱠D懷b𫋄6T𢨐𨳔p*7\n\\'\u000bO#\u001c𪫫(H\u0015n𫪢󷾡}2s𣀩8GA&󵏡\u0018􄱤d⍠\u001a􂤠t @􂀰I/𪻢痰\u00135烙c\u0004󿜉塂Uk\u0016\u0010􌕟8\u001d󼞚𗁬R-x󴇝󶁑󶏺\u0010O,Z\u0003􆌧f*\u000c^>\u0004D\r\u000f_AQPO33𗣃/F\u001e𭍙y𓀞|Fn󶬼E<󿩫9\u0003[y`e𩍈コL\u001a@4i/*󷂯􍋍⍮Ih\u000fC1󻴈\t%?kFt\u0006\u0010\u001f\u001dN𩰟\u000c􋆋:\u0007V\u0003j䙞\u0016\u0016𤨷\u0019K􈤚𧥃鸶Uez)􇹨\u001c)8vT;\u001d呭ay\u0003\u000f\u001d{C=\u0019,\u0001\u0003O𧰫\u0003&\u00012%<2s\u000c\u0016\r6ivo{󺿷WN􁓱R󽸖󻟱󳆅𘉋[ :\u001fu𬆺^f􉤮\u0018𡪧𬰥N\u000f𣝶\u0019@pK􇖌9\r@Ze𥐣f\u0012xM痽;j\u0016珥K_~:v&Dpx~_\u0002:b;bv\u0013=㧜6\u001aꄚ\u001by0Ho.B\"u*{󸪴Vw\u001aW𡰗𪞫rbY쬎I{q󾏞\u0005&_Pt𬪎'󠀷5\u000b𤵫谺觻Ue@YM𨌙)\n\u0019\u001dn\u0004Z\u000e\\\u000c`f3T+_\u001e@\u0007\u001e𭤦}", - "old_password": null + "new_password": "\u0001f;0B+CKY󾃹W\u0005𫧻𐑹􇟚󽀰f\u0001&𨫳􀟘󹪊􊤍𣧚\tX𮥥\u001chl󹏂^A𥠮=𤻆\u0019_ow?\\h4𥼫𐬤\u0016⹋4O\u0015nJT\u0004r\u0018~\u0006\u0017R'󲏴sX󴤦mlCHvg􏥩ba󵂐R\\󼝬o󱢀􁹎*k\r)H𬂳􀧘\n#㠞~\\9Q|\u000b;\u001fd.􄙔0SHP%󸹆囇'!󾺴N\u001a!Kz@\u0000𒅟􉤛\u001aVp􌥏鞴\u00023#\u0014}}􋉝N𝙺`𩖂7󼽅\u0010𥱥^\u0002{`i:\rUT!\u0013􏚔𥏟\u0015WK\u0000􌋍􍅦eA𢚊\u0003𩿡󼣩t@?󷭺\u0001J􆔶7\u001eg{𓆲5R_\u0013u\u000f𥝛􈑉`}𐔔X\u0011𪱠D懷b𫋄6T𢨐𨳔p*7\n\\'\u000bO#\u001c𪫫(H\u0015n𫪢󷾡}2s𣀩8GA&󵏡\u0018􄱤d⍠\u001a􂤠t @􂀰I/𪻢痰\u00135烙c\u0004󿜉塂Uk\u0016\u0010􌕟8\u001d󼞚𗁬R-x󴇝󶁑󶏺\u0010O,Z\u0003􆌧f*\u000c^>\u0004D\r\u000f_AQPO33𗣃/F\u001e𭍙y𓀞|Fn󶬼E<󿩫9\u0003[y`e𩍈コL\u001a@4i/*󷂯􍋍⍮Ih\u000fC1󻴈\t%?kFt\u0006\u0010\u001f\u001dN𩰟\u000c􋆋:\u0007V\u0003j䙞\u0016\u0016𤨷\u0019K􈤚𧥃鸶Uez)􇹨\u001c)8vT;\u001d呭ay\u0003\u000f\u001d{C=\u0019,\u0001\u0003O𧰫\u0003&\u00012%<2s\u000c\u0016\r6ivo{󺿷WN􁓱R󽸖󻟱󳆅𘉋[ :\u001fu𬆺^f􉤮\u0018𡪧𬰥N\u000f𣝶\u0019@pK􇖌9\r@Ze𥐣f\u0012xM痽;j\u0016珥K_~:v&Dpx~_\u0002:b;bv\u0013=㧜6\u001aꄚ\u001by0Ho.B\"u*{󸪴Vw\u001aW𡰗𪞫rbY쬎I{q󾏞\u0005&_Pt𬪎'󠀷5\u000b𤵫谺觻Ue@YM𨌙)\n\u0019\u001dn\u0004Z\u000e\\\u000c`f3T+_\u001e@\u0007\u001e𭤦}" } diff --git a/libs/wire-api/test/golden/testObject_PasswordChange_user_13.json b/libs/wire-api/test/golden/testObject_PasswordChange_user_13.json index 7ce0b18e5f7..03878d7165d 100644 --- a/libs/wire-api/test/golden/testObject_PasswordChange_user_13.json +++ b/libs/wire-api/test/golden/testObject_PasswordChange_user_13.json @@ -1,4 +1,3 @@ { - "new_password": "\tY6b􅟘\u0018J0\u001e\u0013\u000cg]Ⅼ^\u0012󻏡S<\u0003n\u0010𧔞\u0000󶜅\"\u0000𠾿\u000fU󻄡K;la\u000e𠜻`쎩$0#䷉\u000c~\u001e􀙓kyBx\u0007\u0005<bY%􎆪{𣖓\u0005󵪒\u001a\n􏛶=􈊯\nl󺱶쌴\t:Z\\덧7,\u0017\\󾱀󽼁m^\u0005+\u0003epC\u0018VO2:!𦂲\n\u00186g}􌪂q6􁵋6<𩺐\u0001%yP𪳿\u0002\u0006o𫵞g􅨘p󵮋T󸬔\u0005\u000e0'F#^IVd\n𦗔𢞫A,@􁊙C-𬒓g``𨤳\u0000ṹ􀎏𡱼5}q쑜\u0016􉿉𠴦u(P芷\u000eP뤘e\n\u0011𨃁6􈤲\u0016M𩂹7󺻩\u00149󽭑O9 ,Op󰂺UR\u0013\u0000뮽􀔭>(󺙦\u0019_Nn8\u001cb𖩅r[p􏹻za1\u0012w\u0011慧\u001a𩛅\u001c_\u0018\u0004N6Vyi&)󷋢^𡈴\u0004\u0018ꂦBuJ\u0012`𓈢)qq^@$*\u0016𝅘𝅥𝅮P\u000bể\u0006g𠸹Mg}\u001b\u0019󲤜󼾦w󰍴-e窓p&\u000ed󹭘𒄔a㮰󽼋􁸞\u001e𢾀􁵈'E𬌖𗽈9\u0014\u0014A$𬆴h/A`\u0011l]3Qv㧗MR3W\u001csn] a\u0000:3`𗐴{`罕\n\u001f\u0012.𪂺:?y`\u0014􈼒_%S𥻲:\u0000𩷛\u0019k\"\u000cWYu8-jr)¸?D〴c􎘍􋲹􉽙^x\u0001{b3Sl:&0xgT321𬄏FU􄵹N􏽊P*L𣣿ﱔi:뻜\u0016𨏇0#\u0006뺗0v􀐍n\u000e𦴧P:", - "old_password": null + "new_password": "\tY6b􅟘\u0018J0\u001e\u0013\u000cg]Ⅼ^\u0012󻏡S<\u0003n\u0010𧔞\u0000󶜅\"\u0000𠾿\u000fU󻄡K;la\u000e𠜻`쎩$0#䷉\u000c~\u001e􀙓kyBx\u0007\u0005<bY%􎆪{𣖓\u0005󵪒\u001a\n􏛶=􈊯\nl󺱶쌴\t:Z\\덧7,\u0017\\󾱀󽼁m^\u0005+\u0003epC\u0018VO2:!𦂲\n\u00186g}􌪂q6􁵋6<𩺐\u0001%yP𪳿\u0002\u0006o𫵞g􅨘p󵮋T󸬔\u0005\u000e0'F#^IVd\n𦗔𢞫A,@􁊙C-𬒓g``𨤳\u0000ṹ􀎏𡱼5}q쑜\u0016􉿉𠴦u(P芷\u000eP뤘e\n\u0011𨃁6􈤲\u0016M𩂹7󺻩\u00149󽭑O9 ,Op󰂺UR\u0013\u0000뮽􀔭>(󺙦\u0019_Nn8\u001cb𖩅r[p􏹻za1\u0012w\u0011慧\u001a𩛅\u001c_\u0018\u0004N6Vyi&)󷋢^𡈴\u0004\u0018ꂦBuJ\u0012`𓈢)qq^@$*\u0016𝅘𝅥𝅮P\u000bể\u0006g𠸹Mg}\u001b\u0019󲤜󼾦w󰍴-e窓p&\u000ed󹭘𒄔a㮰󽼋􁸞\u001e𢾀􁵈'E𬌖𗽈9\u0014\u0014A$𬆴h/A`\u0011l]3Qv㧗MR3W\u001csn] a\u0000:3`𗐴{`罕\n\u001f\u0012.𪂺:?y`\u0014􈼒_%S𥻲:\u0000𩷛\u0019k\"\u000cWYu8-jr)¸?D〴c􎘍􋲹􉽙^x\u0001{b3Sl:&0xgT321𬄏FU􄵹N􏽊P*L𣣿ﱔi:뻜\u0016𨏇0#\u0006뺗0v􀐍n\u000e𦴧P:" } diff --git a/libs/wire-api/test/golden/testObject_PasswordChange_user_14.json b/libs/wire-api/test/golden/testObject_PasswordChange_user_14.json index 9ecfcc28252..1685f45fe13 100644 --- a/libs/wire-api/test/golden/testObject_PasswordChange_user_14.json +++ b/libs/wire-api/test/golden/testObject_PasswordChange_user_14.json @@ -1,4 +1,3 @@ { - "new_password": "6go<􂵨fꃽY􈳇*KE󽤥])9󻆯􊕞K।􄉎1𘗢>qb2`\u00157*al\u000b3I*𦒁\u000e􊉪\u000c}\u0010𢁞~🩏@jjd\u0001O\u001a\u0001𗌗lv䃂B\u00083􄔙?Ih1bv\u000b\u001az\u0001\u001c😈0.\u0005b윮\u001cR<􏒫\u0004\u0011l\"E𞡊X6\u0015S󷂒?\u001b\u0004EKÒh팥䄰\u000c6󠁸>\u000b_Zd n<\u0012k􉖈s􅌻~\u0001󶹅\u0018r\u0010G󳶒\u0000`.)lj\u0010Rr/𤞸\\𫈘󴆴𭵝𗨦kI󾁓9\\.!\u0002pj&X?=r􅸤A5𩚏䤜\u0000ex`\u0001{ 𝩌\u0003󳽯^𘊏D􀩬𧵼𠐲m裠61JU\u0000tn@pCF3\u000cM𥸢ZrHM󺉄i𡔰zhn󶧼󰂧\u0005\u001b\u000b\u001f\u0003􇓚󺹼\u0014lU\u001d⥴[\u0011B\u0004 Y𧰏&cnLev俏awe\n𪵑+O𑀎N󽱴󰻦=x[7\"B\u001e\u0013\u0011\u0008Qy\u0010Nq~􌩔G*󾈲C𮨹Ho[𗜷9'󸤖6𭡑e#\u0017K샥W\u000e􎡥&kH7MQ\u0003\u0019,v2\u0001N/󾹍\tO𩑥\u000e󶩐b𭒦􈂫𤐕q󽖍􊙲ww}=$D\u0003w\u0010b􅀦\u0019~𢱜T\u001b\u0018𒑅Y~{\u000c{p𡱱*w􃑶Juo\n0𤵺sYXHT\u001fK\u0007󶡄\u0005\n%q iF𗙾\u001b\u001d.c\u001d󷪉􄞵\u001fW\u0019%x󵄢\u0015>\u0013􂟈", - "old_password": null + "new_password": "6go<􂵨fꃽY􈳇*KE󽤥])9󻆯􊕞K।􄉎1𘗢>qb2`\u00157*al\u000b3I*𦒁\u000e􊉪\u000c}\u0010𢁞~🩏@jjd\u0001O\u001a\u0001𗌗lv䃂B\u00083􄔙?Ih1bv\u000b\u001az\u0001\u001c😈0.\u0005b윮\u001cR<􏒫\u0004\u0011l\"E𞡊X6\u0015S󷂒?\u001b\u0004EKÒh팥䄰\u000c6󠁸>\u000b_Zd n<\u0012k􉖈s􅌻~\u0001󶹅\u0018r\u0010G󳶒\u0000`.)lj\u0010Rr/𤞸\\𫈘󴆴𭵝𗨦kI󾁓9\\.!\u0002pj&X?=r􅸤A5𩚏䤜\u0000ex`\u0001{ 𝩌\u0003󳽯^𘊏D􀩬𧵼𠐲m裠61JU\u0000tn@pCF3\u000cM𥸢ZrHM󺉄i𡔰zhn󶧼󰂧\u0005\u001b\u000b\u001f\u0003􇓚󺹼\u0014lU\u001d⥴[\u0011B\u0004 Y𧰏&cnLev俏awe\n𪵑+O𑀎N󽱴󰻦=x[7\"B\u001e\u0013\u0011\u0008Qy\u0010Nq~􌩔G*󾈲C𮨹Ho[𗜷9'󸤖6𭡑e#\u0017K샥W\u000e􎡥&kH7MQ\u0003\u0019,v2\u0001N/󾹍\tO𩑥\u000e󶩐b𭒦􈂫𤐕q󽖍􊙲ww}=$D\u0003w\u0010b􅀦\u0019~𢱜T\u001b\u0018𒑅Y~{\u000c{p𡱱*w􃑶Juo\n0𤵺sYXHT\u001fK\u0007󶡄\u0005\n%q iF𗙾\u001b\u001d.c\u001d󷪉􄞵\u001fW\u0019%x󵄢\u0015>\u0013􂟈" } diff --git a/libs/wire-api/test/golden/testObject_PasswordChange_user_17.json b/libs/wire-api/test/golden/testObject_PasswordChange_user_17.json index 079dbd79a9e..8dc1a26d5bb 100644 --- a/libs/wire-api/test/golden/testObject_PasswordChange_user_17.json +++ b/libs/wire-api/test/golden/testObject_PasswordChange_user_17.json @@ -1,4 +1,3 @@ { - "new_password": "󿞫X2cZf\tI\u0010.3樑ꊩ󱊝\u000e𨳮糽𦡈U峖𦿤\u0012󲪍xy􍘒DB􋣨\u001fP!≈3뒗\u000c㤈.1󹇚󾭏ji𡆁C𗍮䉇酖䪛\u001f\u0000~BYB\u001eUYc\u0003􊘨e🈨𭱐ᄻ\"cN&\t.9􌉽?\u001d^\u000b\u0001\u0008\u0018ꊛ&㔋􎭙a󷪠)z6Z𫴦􊱴\u001bX\u000f㗿xJ\u0016֔\u0019-\u0011I\nB=󾉏n!l𠆗~U􅅖;𛰔X𭱩\u0004𦹁󹷹uY鴇z􁚺D󹃹\u000cFbZtt\u0018:\u0018YQ\u0001h󵬙W􏄺\u0010𩌧!1}k\\􁭿z+\u0015􎉯\u0001􋇸%䑂?v􎡃h\u000cN\u000c\u0012\u0000\u001a𮒗󳏵P\u001dbP􎜘>ie􌫲Ỵi𩃮-癈]4i-\u0002/\u001dA\u0008&\u000b󾶽<􍍵􎋯M\\󲇎-pG\u001c𩕵\u0010HEJO\u0007|\t(⏹D=x<\u0017 aV󷏱O󳺅n|mdg󾯸@􏌿f\u0007󺒝W𮨌䵨\u0010h𨯽􊨀\u0017~⦜K~􅴪\u001b|\rdi\u001dﱽ𗈵􇾁;󺩗8e^𢬼T/D\u001f\u0015󰌧hTTe|N8􇅇1􊮋tJR\u0007𥺘iJ2󳩶}赛瀩扱\u000e􎱴0ુ!y\u00011W\u001fzX\u00010󲄬s𝒳h𓊖𡈵#􆒳𡯮SR󱖟,V𬃧v\u0015𨑊1g\u001a\u001b𗶱􇻦\u0002Q):\u001f/E'𠆔\u0010z!\u000e@󹋾Hy5*R󶩿\u001a󱳖󾼹󷬼􃰇𫢬􃒛`솖\u0012.M􏺪W𩴰\u0006󺋆Imf#dHF\u0017c娛\u0013QcG􅝄B\u0000W<\u001eC􂭸󱔉󹡃L7", - "old_password": null + "new_password": "󿞫X2cZf\tI\u0010.3樑ꊩ󱊝\u000e𨳮糽𦡈U峖𦿤\u0012󲪍xy􍘒DB􋣨\u001fP!≈3뒗\u000c㤈.1󹇚󾭏ji𡆁C𗍮䉇酖䪛\u001f\u0000~BYB\u001eUYc\u0003􊘨e🈨𭱐ᄻ\"cN&\t.9􌉽?\u001d^\u000b\u0001\u0008\u0018ꊛ&㔋􎭙a󷪠)z6Z𫴦􊱴\u001bX\u000f㗿xJ\u0016֔\u0019-\u0011I\nB=󾉏n!l𠆗~U􅅖;𛰔X𭱩\u0004𦹁󹷹uY鴇z􁚺D󹃹\u000cFbZtt\u0018:\u0018YQ\u0001h󵬙W􏄺\u0010𩌧!1}k\\􁭿z+\u0015􎉯\u0001􋇸%䑂?v􎡃h\u000cN\u000c\u0012\u0000\u001a𮒗󳏵P\u001dbP􎜘>ie􌫲Ỵi𩃮-癈]4i-\u0002/\u001dA\u0008&\u000b󾶽<􍍵􎋯M\\󲇎-pG\u001c𩕵\u0010HEJO\u0007|\t(⏹D=x<\u0017 aV󷏱O󳺅n|mdg󾯸@􏌿f\u0007󺒝W𮨌䵨\u0010h𨯽􊨀\u0017~⦜K~􅴪\u001b|\rdi\u001dﱽ𗈵􇾁;󺩗8e^𢬼T/D\u001f\u0015󰌧hTTe|N8􇅇1􊮋tJR\u0007𥺘iJ2󳩶}赛瀩扱\u000e􎱴0ુ!y\u00011W\u001fzX\u00010󲄬s𝒳h𓊖𡈵#􆒳𡯮SR󱖟,V𬃧v\u0015𨑊1g\u001a\u001b𗶱􇻦\u0002Q):\u001f/E'𠆔\u0010z!\u000e@󹋾Hy5*R󶩿\u001a󱳖󾼹󷬼􃰇𫢬􃒛`솖\u0012.M􏺪W𩴰\u0006󺋆Imf#dHF\u0017c娛\u0013QcG􅝄B\u0000W<\u001eC􂭸󱔉󹡃L7" } diff --git a/libs/wire-api/test/golden/testObject_PasswordChange_user_18.json b/libs/wire-api/test/golden/testObject_PasswordChange_user_18.json index 9b87cd015c1..52c7a9d4b59 100644 --- a/libs/wire-api/test/golden/testObject_PasswordChange_user_18.json +++ b/libs/wire-api/test/golden/testObject_PasswordChange_user_18.json @@ -1,4 +1,3 @@ { - "new_password": "$󾮝izIhKE𥋌b\u000ci𨒼v=:\t9󻗨\r⣴!􀲃:󴽌&/#􊛋G𭿷$W󱲯>\u0019Do`F\nY\u0019L\u0004\t\u00005󳒈bC8ᑱBq󸢵$p\u0000\u000b┆R^\u0016GF&󷅀+]𦐧]壢鞈;:𠉵𦄍w􄉷\u0015\u0014\u0016􂾥󷺴gi\u000f\"\u000bqᢹvV󾃑\u0010Yya􍍕};,K3\u0010n\u0003\u0006\u0012+𭅵𢭯\u000e^q\u00134+Iby-\u0005􁎦𧮉_", - "old_password": null + "new_password": "$󾮝izIhKE𥋌b\u000ci𨒼v=:\t9󻗨\r⣴!􀲃:󴽌&/#􊛋G𭿷$W󱲯>\u0019Do`F\nY\u0019L\u0004\t\u00005󳒈bC8ᑱBq󸢵$p\u0000\u000b┆R^\u0016GF&󷅀+]𦐧]壢鞈;:𠉵𦄍w􄉷\u0015\u0014\u0016􂾥󷺴gi\u000f\"\u000bqᢹvV󾃑\u0010Yya􍍕};,K3\u0010n\u0003\u0006\u0012+𭅵𢭯\u000e^q\u00134+Iby-\u0005􁎦𧮉_" } diff --git a/libs/wire-api/test/golden/testObject_PasswordChange_user_7.json b/libs/wire-api/test/golden/testObject_PasswordChange_user_7.json index aeb51f98f69..19fe98e5026 100644 --- a/libs/wire-api/test/golden/testObject_PasswordChange_user_7.json +++ b/libs/wire-api/test/golden/testObject_PasswordChange_user_7.json @@ -1,4 +1,3 @@ { - "new_password": "􇎃7ﻂ\u001eA󴇌k\u000b\u001b\u0015g&:\u00006K\u0000n>\u001a𝒦$\u001b8B'z\"Yc󻾅6\u001bU)\u0000\u0010􌵬Zj굡𔓻bJ-\u001f\"𠋜L\u0007􄫓y\u0010s-}𢨂}\u001d\u0018\u0006\rR}R\u0018L\u001cqkZ\n\t𮉈1|\u000c5󰏵L\u00178gL䝴5⨓cf\\V~\u0011Ⲕ\u0019𝠁󱮄1n\"*\u0017\u000faTxUcZ\r#5/\u0008k\u000b[\u0007`􎉒\u0000R;(\u0018\rFMN>󳆴\u001bt\u0006{'(𢣤\u0003\u0000T􂄷m\u000c6綠B\n󱋢", - "old_password": null + "new_password": "􇎃7ﻂ\u001eA󴇌k\u000b\u001b\u0015g&:\u00006K\u0000n>\u001a𝒦$\u001b8B'z\"Yc󻾅6\u001bU)\u0000\u0010􌵬Zj굡𔓻bJ-\u001f\"𠋜L\u0007􄫓y\u0010s-}𢨂}\u001d\u0018\u0006\rR}R\u0018L\u001cqkZ\n\t𮉈1|\u000c5󰏵L\u00178gL䝴5⨓cf\\V~\u0011Ⲕ\u0019𝠁󱮄1n\"*\u0017\u000faTxUcZ\r#5/\u0008k\u000b[\u0007`􎉒\u0000R;(\u0018\rFMN>󳆴\u001bt\u0006{'(𢣤\u0003\u0000T􂄷m\u000c6綠B\n󱋢" } diff --git a/libs/wire-api/test/golden/testObject_SndFactorPasswordChallengeAction_user_1 b/libs/wire-api/test/golden/testObject_SndFactorPasswordChallengeAction_user_1 new file mode 100644 index 00000000000..d7d7e107861 --- /dev/null +++ b/libs/wire-api/test/golden/testObject_SndFactorPasswordChallengeAction_user_1 @@ -0,0 +1 @@ +"generate_scim_token" diff --git a/libs/wire-api/test/golden/testObject_SndFactorPasswordChallengeAction_user_2 b/libs/wire-api/test/golden/testObject_SndFactorPasswordChallengeAction_user_2 new file mode 100644 index 00000000000..4608126e46a --- /dev/null +++ b/libs/wire-api/test/golden/testObject_SndFactorPasswordChallengeAction_user_2 @@ -0,0 +1 @@ +"login" diff --git a/libs/wire-api/test/golden/testObject_TeamConversationList_team_1.json b/libs/wire-api/test/golden/testObject_TeamConversationList_team_1.json index 41110defa64..3248c22295b 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversationList_team_1.json +++ b/libs/wire-api/test/golden/testObject_TeamConversationList_team_1.json @@ -2,7 +2,7 @@ "conversations": [ { "conversation": "00000012-0000-0018-0000-00260000002b", - "managed": true + "managed": false }, { "conversation": "0000002d-0000-0063-0000-006900000013", @@ -18,11 +18,11 @@ }, { "conversation": "0000005f-0000-0003-0000-005a00000075", - "managed": true + "managed": false }, { "conversation": "0000007f-0000-0018-0000-00250000007c", - "managed": true + "managed": false }, { "conversation": "0000006a-0000-0020-0000-001a00000073", @@ -30,11 +30,11 @@ }, { "conversation": "0000002e-0000-006a-0000-005f00000003", - "managed": true + "managed": false }, { "conversation": "00000034-0000-0021-0000-00330000005b", - "managed": true + "managed": false }, { "conversation": "00000048-0000-0011-0000-002a00000004", @@ -46,7 +46,7 @@ }, { "conversation": "00000056-0000-000e-0000-004300000028", - "managed": true + "managed": false }, { "conversation": "00000067-0000-007f-0000-003600000031", @@ -58,11 +58,11 @@ }, { "conversation": "0000000f-0000-0071-0000-001b00000057", - "managed": true + "managed": false }, { "conversation": "00000032-0000-0035-0000-00210000003b", - "managed": true + "managed": false }, { "conversation": "00000004-0000-000d-0000-002100000067", diff --git a/libs/wire-api/test/golden/testObject_TeamConversationList_team_10.json b/libs/wire-api/test/golden/testObject_TeamConversationList_team_10.json index cad7fd3c3a0..5a7f04fd53d 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversationList_team_10.json +++ b/libs/wire-api/test/golden/testObject_TeamConversationList_team_10.json @@ -2,11 +2,11 @@ "conversations": [ { "conversation": "00000070-0000-007d-0000-001400000009", - "managed": true + "managed": false }, { "conversation": "00000065-0000-0057-0000-00190000004a", - "managed": true + "managed": false }, { "conversation": "00000049-0000-0030-0000-006b00000005", @@ -14,11 +14,11 @@ }, { "conversation": "0000007c-0000-0065-0000-001100000066", - "managed": true + "managed": false }, { "conversation": "00000057-0000-0039-0000-000400000071", - "managed": true + "managed": false }, { "conversation": "0000003e-0000-0053-0000-007f0000003c", diff --git a/libs/wire-api/test/golden/testObject_TeamConversationList_team_11.json b/libs/wire-api/test/golden/testObject_TeamConversationList_team_11.json index ec81eabb4c9..cd3dde1b948 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversationList_team_11.json +++ b/libs/wire-api/test/golden/testObject_TeamConversationList_team_11.json @@ -2,15 +2,15 @@ "conversations": [ { "conversation": "00000038-0000-0030-0000-006700000067", - "managed": true + "managed": false }, { "conversation": "00000000-0000-006a-0000-00220000007c", - "managed": true + "managed": false }, { "conversation": "00000055-0000-004f-0000-005500000047", - "managed": true + "managed": false }, { "conversation": "00000064-0000-003d-0000-006500000060", diff --git a/libs/wire-api/test/golden/testObject_TeamConversationList_team_12.json b/libs/wire-api/test/golden/testObject_TeamConversationList_team_12.json index 656f2f172bb..1ce03386ffd 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversationList_team_12.json +++ b/libs/wire-api/test/golden/testObject_TeamConversationList_team_12.json @@ -14,15 +14,15 @@ }, { "conversation": "0000000e-0000-006f-0000-000c00000038", - "managed": true + "managed": false }, { "conversation": "00000038-0000-0021-0000-005500000008", - "managed": true + "managed": false }, { "conversation": "0000005b-0000-007a-0000-00230000002d", - "managed": true + "managed": false }, { "conversation": "00000078-0000-000e-0000-004300000065", @@ -30,7 +30,7 @@ }, { "conversation": "00000036-0000-0003-0000-000500000011", - "managed": true + "managed": false }, { "conversation": "00000043-0000-0032-0000-005200000069", @@ -50,7 +50,7 @@ }, { "conversation": "0000001a-0000-005b-0000-00250000000c", - "managed": true + "managed": false }, { "conversation": "0000004b-0000-005b-0000-004200000001", @@ -58,7 +58,7 @@ }, { "conversation": "00000057-0000-0073-0000-003d00000006", - "managed": true + "managed": false }, { "conversation": "00000053-0000-0038-0000-006600000048", @@ -82,7 +82,7 @@ }, { "conversation": "00000054-0000-0056-0000-007000000058", - "managed": true + "managed": false }, { "conversation": "00000046-0000-0011-0000-001500000007", diff --git a/libs/wire-api/test/golden/testObject_TeamConversationList_team_13.json b/libs/wire-api/test/golden/testObject_TeamConversationList_team_13.json index 08c6bfd87e8..76a0c7ecc99 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversationList_team_13.json +++ b/libs/wire-api/test/golden/testObject_TeamConversationList_team_13.json @@ -2,11 +2,11 @@ "conversations": [ { "conversation": "0000006a-0000-0043-0000-007f00000048", - "managed": true + "managed": false }, { "conversation": "0000007d-0000-005f-0000-000a00000024", - "managed": true + "managed": false }, { "conversation": "0000007a-0000-0046-0000-003800000023", @@ -34,15 +34,15 @@ }, { "conversation": "00000065-0000-001a-0000-00680000004d", - "managed": true + "managed": false }, { "conversation": "0000002f-0000-0037-0000-00020000000f", - "managed": true + "managed": false }, { "conversation": "00000023-0000-0040-0000-005b0000001c", - "managed": true + "managed": false }, { "conversation": "00000000-0000-0074-0000-007b00000019", @@ -50,7 +50,7 @@ }, { "conversation": "0000004e-0000-0025-0000-006900000014", - "managed": true + "managed": false }, { "conversation": "00000063-0000-0000-0000-002100000043", @@ -62,15 +62,15 @@ }, { "conversation": "00000052-0000-004e-0000-002700000075", - "managed": true + "managed": false }, { "conversation": "00000046-0000-0014-0000-000100000040", - "managed": true + "managed": false }, { "conversation": "00000049-0000-0004-0000-00280000000a", - "managed": true + "managed": false }, { "conversation": "00000004-0000-0012-0000-00150000006e", diff --git a/libs/wire-api/test/golden/testObject_TeamConversationList_team_14.json b/libs/wire-api/test/golden/testObject_TeamConversationList_team_14.json index bb5f5dc09f2..3c118b9fcef 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversationList_team_14.json +++ b/libs/wire-api/test/golden/testObject_TeamConversationList_team_14.json @@ -18,7 +18,7 @@ }, { "conversation": "00000023-0000-005e-0000-007300000058", - "managed": true + "managed": false }, { "conversation": "00000056-0000-006a-0000-004100000045", @@ -26,7 +26,7 @@ }, { "conversation": "0000006d-0000-0027-0000-00080000000d", - "managed": true + "managed": false }, { "conversation": "00000024-0000-0028-0000-007700000051", @@ -34,7 +34,7 @@ }, { "conversation": "00000004-0000-001c-0000-004c00000073", - "managed": true + "managed": false }, { "conversation": "0000006f-0000-002f-0000-003400000023", @@ -42,7 +42,7 @@ }, { "conversation": "0000005d-0000-0057-0000-00580000006a", - "managed": true + "managed": false }, { "conversation": "00000034-0000-0016-0000-002500000036", @@ -50,11 +50,11 @@ }, { "conversation": "00000033-0000-006c-0000-00420000003d", - "managed": true + "managed": false }, { "conversation": "00000008-0000-005d-0000-004600000002", - "managed": true + "managed": false }, { "conversation": "0000006a-0000-002b-0000-005800000035", @@ -66,7 +66,7 @@ }, { "conversation": "00000047-0000-002b-0000-000100000080", - "managed": true + "managed": false }, { "conversation": "00000013-0000-001b-0000-003200000000", @@ -74,11 +74,11 @@ }, { "conversation": "00000006-0000-0013-0000-004d0000006e", - "managed": true + "managed": false }, { "conversation": "00000074-0000-0041-0000-007200000079", - "managed": true + "managed": false } ] } diff --git a/libs/wire-api/test/golden/testObject_TeamConversationList_team_15.json b/libs/wire-api/test/golden/testObject_TeamConversationList_team_15.json index 8eaf78dade9..05b3262d3a0 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversationList_team_15.json +++ b/libs/wire-api/test/golden/testObject_TeamConversationList_team_15.json @@ -10,7 +10,7 @@ }, { "conversation": "00000002-0000-006e-0000-006800000040", - "managed": true + "managed": false }, { "conversation": "00000080-0000-005a-0000-000e00000024", @@ -18,7 +18,7 @@ }, { "conversation": "00000069-0000-007c-0000-00550000002f", - "managed": true + "managed": false }, { "conversation": "00000068-0000-0041-0000-000e0000003e", diff --git a/libs/wire-api/test/golden/testObject_TeamConversationList_team_16.json b/libs/wire-api/test/golden/testObject_TeamConversationList_team_16.json index 0b984a33263..04835850c9f 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversationList_team_16.json +++ b/libs/wire-api/test/golden/testObject_TeamConversationList_team_16.json @@ -6,7 +6,7 @@ }, { "conversation": "00000049-0000-0007-0000-003f0000001d", - "managed": true + "managed": false }, { "conversation": "00000045-0000-0038-0000-005f00000072", @@ -26,7 +26,7 @@ }, { "conversation": "00000003-0000-0056-0000-00780000000f", - "managed": true + "managed": false }, { "conversation": "0000006b-0000-0064-0000-001b00000024", @@ -42,7 +42,7 @@ }, { "conversation": "00000079-0000-0018-0000-000600000047", - "managed": true + "managed": false }, { "conversation": "00000009-0000-0029-0000-003100000043", @@ -58,11 +58,11 @@ }, { "conversation": "0000006c-0000-0028-0000-002100000076", - "managed": true + "managed": false }, { "conversation": "00000033-0000-0052-0000-003300000080", - "managed": true + "managed": false }, { "conversation": "0000004c-0000-005f-0000-00390000004d", @@ -70,11 +70,11 @@ }, { "conversation": "0000007a-0000-004b-0000-00440000003e", - "managed": true + "managed": false }, { "conversation": "00000052-0000-007a-0000-003d00000036", - "managed": true + "managed": false }, { "conversation": "00000018-0000-0058-0000-003700000019", @@ -94,7 +94,7 @@ }, { "conversation": "0000004b-0000-0011-0000-002100000014", - "managed": true + "managed": false }, { "conversation": "00000060-0000-0003-0000-00490000001b", diff --git a/libs/wire-api/test/golden/testObject_TeamConversationList_team_17.json b/libs/wire-api/test/golden/testObject_TeamConversationList_team_17.json index b94c027a5d7..7361261cb69 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversationList_team_17.json +++ b/libs/wire-api/test/golden/testObject_TeamConversationList_team_17.json @@ -2,7 +2,7 @@ "conversations": [ { "conversation": "00000053-0000-0070-0000-007f0000001c", - "managed": true + "managed": false }, { "conversation": "00000036-0000-0017-0000-002a00000076", @@ -10,11 +10,11 @@ }, { "conversation": "00000065-0000-004f-0000-00710000002d", - "managed": true + "managed": false }, { "conversation": "00000067-0000-0037-0000-004d0000007b", - "managed": true + "managed": false }, { "conversation": "00000004-0000-0071-0000-000800000015", @@ -22,39 +22,39 @@ }, { "conversation": "00000047-0000-0062-0000-002900000024", - "managed": true + "managed": false }, { "conversation": "00000072-0000-0027-0000-001300000046", - "managed": true + "managed": false }, { "conversation": "0000000f-0000-0034-0000-00720000000f", - "managed": true + "managed": false }, { "conversation": "00000021-0000-005d-0000-003300000024", - "managed": true + "managed": false }, { "conversation": "00000023-0000-000b-0000-00160000000d", - "managed": true + "managed": false }, { "conversation": "00000022-0000-0042-0000-003400000043", - "managed": true + "managed": false }, { "conversation": "00000020-0000-0033-0000-00780000006b", - "managed": true + "managed": false }, { "conversation": "00000074-0000-0067-0000-005f00000042", - "managed": true + "managed": false }, { "conversation": "0000000f-0000-0079-0000-00630000007e", - "managed": true + "managed": false }, { "conversation": "0000001a-0000-0045-0000-003900000053", @@ -62,7 +62,7 @@ }, { "conversation": "00000000-0000-003e-0000-003d00000000", - "managed": true + "managed": false }, { "conversation": "00000039-0000-0052-0000-000500000034", @@ -74,7 +74,7 @@ }, { "conversation": "00000036-0000-0067-0000-007400000054", - "managed": true + "managed": false }, { "conversation": "00000047-0000-0075-0000-001200000054", @@ -82,7 +82,7 @@ }, { "conversation": "0000002e-0000-003d-0000-000700000080", - "managed": true + "managed": false }, { "conversation": "0000005d-0000-0006-0000-00010000001a", @@ -90,7 +90,7 @@ }, { "conversation": "00000012-0000-0073-0000-002000000058", - "managed": true + "managed": false }, { "conversation": "00000073-0000-0015-0000-005e0000006e", @@ -102,11 +102,11 @@ }, { "conversation": "0000004b-0000-0074-0000-007000000021", - "managed": true + "managed": false }, { "conversation": "0000007a-0000-0040-0000-006f00000075", - "managed": true + "managed": false } ] } diff --git a/libs/wire-api/test/golden/testObject_TeamConversationList_team_18.json b/libs/wire-api/test/golden/testObject_TeamConversationList_team_18.json index 821999129ba..2ddbcdbac5e 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversationList_team_18.json +++ b/libs/wire-api/test/golden/testObject_TeamConversationList_team_18.json @@ -2,7 +2,7 @@ "conversations": [ { "conversation": "00000049-0000-000d-0000-007600000068", - "managed": true + "managed": false }, { "conversation": "0000002a-0000-0033-0000-006400000019", @@ -22,11 +22,11 @@ }, { "conversation": "00000016-0000-001a-0000-00430000003d", - "managed": true + "managed": false }, { "conversation": "0000002f-0000-0005-0000-004f00000031", - "managed": true + "managed": false }, { "conversation": "00000000-0000-0043-0000-001a0000000c", @@ -34,15 +34,15 @@ }, { "conversation": "00000003-0000-001c-0000-003a0000002b", - "managed": true + "managed": false }, { "conversation": "0000001c-0000-007b-0000-00170000000a", - "managed": true + "managed": false }, { "conversation": "00000073-0000-0073-0000-000000000074", - "managed": true + "managed": false }, { "conversation": "0000005b-0000-0069-0000-00490000002d", @@ -50,7 +50,7 @@ }, { "conversation": "0000003c-0000-0012-0000-000400000000", - "managed": true + "managed": false }, { "conversation": "00000016-0000-004e-0000-003800000057", @@ -58,7 +58,7 @@ }, { "conversation": "00000008-0000-0022-0000-002000000004", - "managed": true + "managed": false }, { "conversation": "00000070-0000-0011-0000-00260000004a", @@ -66,7 +66,7 @@ }, { "conversation": "0000002c-0000-007a-0000-00340000006e", - "managed": true + "managed": false } ] } diff --git a/libs/wire-api/test/golden/testObject_TeamConversationList_team_19.json b/libs/wire-api/test/golden/testObject_TeamConversationList_team_19.json index 9da6005c025..f8ff6ae2764 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversationList_team_19.json +++ b/libs/wire-api/test/golden/testObject_TeamConversationList_team_19.json @@ -2,7 +2,7 @@ "conversations": [ { "conversation": "00000000-0000-0041-0000-007b00000060", - "managed": true + "managed": false }, { "conversation": "0000003f-0000-0059-0000-000700000073", @@ -14,7 +14,7 @@ }, { "conversation": "0000002b-0000-000b-0000-007a00000065", - "managed": true + "managed": false }, { "conversation": "00000071-0000-003a-0000-001b00000027", @@ -30,11 +30,11 @@ }, { "conversation": "00000045-0000-006e-0000-004200000072", - "managed": true + "managed": false }, { "conversation": "0000001b-0000-003b-0000-007900000004", - "managed": true + "managed": false }, { "conversation": "0000002d-0000-0077-0000-006400000054", @@ -42,27 +42,27 @@ }, { "conversation": "0000001a-0000-005e-0000-003e00000012", - "managed": true + "managed": false }, { "conversation": "00000057-0000-000c-0000-00370000003b", - "managed": true + "managed": false }, { "conversation": "00000031-0000-0010-0000-006500000077", - "managed": true + "managed": false }, { "conversation": "00000028-0000-004b-0000-00460000007b", - "managed": true + "managed": false }, { "conversation": "00000005-0000-0040-0000-006400000024", - "managed": true + "managed": false }, { "conversation": "00000042-0000-005b-0000-002d00000031", - "managed": true + "managed": false }, { "conversation": "00000065-0000-0067-0000-00610000006d", @@ -74,15 +74,15 @@ }, { "conversation": "00000058-0000-0042-0000-003700000054", - "managed": true + "managed": false }, { "conversation": "0000002a-0000-0001-0000-000700000015", - "managed": true + "managed": false }, { "conversation": "0000002f-0000-003c-0000-003b00000000", - "managed": true + "managed": false }, { "conversation": "00000065-0000-0049-0000-00720000006c", @@ -90,7 +90,7 @@ }, { "conversation": "0000000f-0000-0021-0000-004c00000055", - "managed": true + "managed": false }, { "conversation": "0000005b-0000-002e-0000-00140000003d", diff --git a/libs/wire-api/test/golden/testObject_TeamConversationList_team_2.json b/libs/wire-api/test/golden/testObject_TeamConversationList_team_2.json index 69323ad2c61..19a9f17bde5 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversationList_team_2.json +++ b/libs/wire-api/test/golden/testObject_TeamConversationList_team_2.json @@ -6,11 +6,11 @@ }, { "conversation": "0000000d-0000-0080-0000-00550000001b", - "managed": true + "managed": false }, { "conversation": "0000004e-0000-0053-0000-004600000056", - "managed": true + "managed": false }, { "conversation": "0000006e-0000-003c-0000-003200000071", @@ -18,7 +18,7 @@ }, { "conversation": "00000067-0000-002f-0000-007a0000007f", - "managed": true + "managed": false }, { "conversation": "0000000a-0000-0027-0000-004e0000005f", @@ -26,15 +26,15 @@ }, { "conversation": "00000006-0000-0026-0000-000000000054", - "managed": true + "managed": false }, { "conversation": "0000006e-0000-007e-0000-001600000035", - "managed": true + "managed": false }, { "conversation": "0000002c-0000-0057-0000-007e00000070", - "managed": true + "managed": false }, { "conversation": "00000074-0000-0053-0000-005f00000006", @@ -58,11 +58,11 @@ }, { "conversation": "00000056-0000-0018-0000-003f00000001", - "managed": true + "managed": false }, { "conversation": "00000014-0000-0066-0000-00440000001b", - "managed": true + "managed": false }, { "conversation": "0000007f-0000-0071-0000-007f0000001b", @@ -82,7 +82,7 @@ }, { "conversation": "0000004d-0000-0021-0000-00360000000e", - "managed": true + "managed": false }, { "conversation": "00000057-0000-003f-0000-003700000065", @@ -90,19 +90,19 @@ }, { "conversation": "0000006f-0000-003e-0000-000300000051", - "managed": true + "managed": false }, { "conversation": "00000038-0000-0025-0000-00030000003b", - "managed": true + "managed": false }, { "conversation": "0000003c-0000-0069-0000-005000000035", - "managed": true + "managed": false }, { "conversation": "0000005f-0000-006b-0000-00260000004e", - "managed": true + "managed": false }, { "conversation": "0000001c-0000-001c-0000-00530000000c", diff --git a/libs/wire-api/test/golden/testObject_TeamConversationList_team_20.json b/libs/wire-api/test/golden/testObject_TeamConversationList_team_20.json index e89ec253558..ac130821ff7 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversationList_team_20.json +++ b/libs/wire-api/test/golden/testObject_TeamConversationList_team_20.json @@ -6,7 +6,7 @@ }, { "conversation": "0000003b-0000-0055-0000-003f00000059", - "managed": true + "managed": false } ] } diff --git a/libs/wire-api/test/golden/testObject_TeamConversationList_team_3.json b/libs/wire-api/test/golden/testObject_TeamConversationList_team_3.json index ee0b5f72fad..c492ea34289 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversationList_team_3.json +++ b/libs/wire-api/test/golden/testObject_TeamConversationList_team_3.json @@ -2,15 +2,15 @@ "conversations": [ { "conversation": "00000046-0000-0026-0000-005600000014", - "managed": true + "managed": false }, { "conversation": "0000006b-0000-0042-0000-002c00000074", - "managed": true + "managed": false }, { "conversation": "0000006d-0000-006d-0000-006100000027", - "managed": true + "managed": false }, { "conversation": "00000079-0000-0024-0000-004600000011", @@ -34,7 +34,7 @@ }, { "conversation": "0000001e-0000-0036-0000-006400000045", - "managed": true + "managed": false }, { "conversation": "00000050-0000-0066-0000-000500000075", diff --git a/libs/wire-api/test/golden/testObject_TeamConversationList_team_4.json b/libs/wire-api/test/golden/testObject_TeamConversationList_team_4.json index 32de2044cf4..56cca582891 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversationList_team_4.json +++ b/libs/wire-api/test/golden/testObject_TeamConversationList_team_4.json @@ -2,23 +2,23 @@ "conversations": [ { "conversation": "00000076-0000-0038-0000-003c00000043", - "managed": true + "managed": false }, { "conversation": "00000046-0000-001f-0000-005800000080", - "managed": true + "managed": false }, { "conversation": "00000023-0000-0070-0000-006f00000077", - "managed": true + "managed": false }, { "conversation": "00000006-0000-0031-0000-004700000053", - "managed": true + "managed": false }, { "conversation": "00000057-0000-0041-0000-001600000013", - "managed": true + "managed": false }, { "conversation": "0000007b-0000-003c-0000-004800000063", @@ -30,7 +30,7 @@ }, { "conversation": "0000001a-0000-007b-0000-00460000007f", - "managed": true + "managed": false }, { "conversation": "00000052-0000-002e-0000-001000000064", @@ -42,15 +42,15 @@ }, { "conversation": "00000004-0000-0033-0000-00780000005e", - "managed": true + "managed": false }, { "conversation": "0000006b-0000-007f-0000-001d0000002c", - "managed": true + "managed": false }, { "conversation": "00000017-0000-0079-0000-001c00000066", - "managed": true + "managed": false }, { "conversation": "0000002f-0000-0024-0000-001000000074", @@ -58,11 +58,11 @@ }, { "conversation": "00000010-0000-000c-0000-001700000046", - "managed": true + "managed": false }, { "conversation": "00000003-0000-0049-0000-003100000022", - "managed": true + "managed": false }, { "conversation": "00000011-0000-0051-0000-003300000061", @@ -70,7 +70,7 @@ }, { "conversation": "0000003e-0000-0077-0000-004c00000022", - "managed": true + "managed": false }, { "conversation": "0000007e-0000-0048-0000-007200000056", @@ -86,11 +86,11 @@ }, { "conversation": "0000004c-0000-0071-0000-007a00000071", - "managed": true + "managed": false }, { "conversation": "00000006-0000-0002-0000-002000000068", - "managed": true + "managed": false }, { "conversation": "0000002e-0000-0037-0000-005e00000027", @@ -98,7 +98,7 @@ }, { "conversation": "00000056-0000-006d-0000-004d00000024", - "managed": true + "managed": false }, { "conversation": "0000004a-0000-0038-0000-001e0000003b", @@ -114,7 +114,7 @@ }, { "conversation": "0000006b-0000-0013-0000-004a00000018", - "managed": true + "managed": false } ] } diff --git a/libs/wire-api/test/golden/testObject_TeamConversationList_team_5.json b/libs/wire-api/test/golden/testObject_TeamConversationList_team_5.json index fcabdfcd1cc..9fc01c729f4 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversationList_team_5.json +++ b/libs/wire-api/test/golden/testObject_TeamConversationList_team_5.json @@ -2,11 +2,11 @@ "conversations": [ { "conversation": "00000073-0000-005a-0000-00250000000d", - "managed": true + "managed": false }, { "conversation": "00000033-0000-005c-0000-006e00000014", - "managed": true + "managed": false }, { "conversation": "00000017-0000-005d-0000-003b00000023", diff --git a/libs/wire-api/test/golden/testObject_TeamConversationList_team_6.json b/libs/wire-api/test/golden/testObject_TeamConversationList_team_6.json index 9a5247fb31d..e3e2854092a 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversationList_team_6.json +++ b/libs/wire-api/test/golden/testObject_TeamConversationList_team_6.json @@ -6,23 +6,23 @@ }, { "conversation": "00000028-0000-0037-0000-000b00000016", - "managed": true + "managed": false }, { "conversation": "00000021-0000-0064-0000-003900000002", - "managed": true + "managed": false }, { "conversation": "00000064-0000-001f-0000-00350000001b", - "managed": true + "managed": false }, { "conversation": "0000002d-0000-007b-0000-00770000003e", - "managed": true + "managed": false }, { "conversation": "00000064-0000-0068-0000-007700000068", - "managed": true + "managed": false }, { "conversation": "00000061-0000-000b-0000-00170000005c", @@ -30,7 +30,7 @@ }, { "conversation": "0000005c-0000-0001-0000-004e00000003", - "managed": true + "managed": false }, { "conversation": "00000008-0000-002b-0000-002d00000022", diff --git a/libs/wire-api/test/golden/testObject_TeamConversationList_team_7.json b/libs/wire-api/test/golden/testObject_TeamConversationList_team_7.json index cfe09817a41..a3a49965496 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversationList_team_7.json +++ b/libs/wire-api/test/golden/testObject_TeamConversationList_team_7.json @@ -18,15 +18,15 @@ }, { "conversation": "00000040-0000-0001-0000-00670000002e", - "managed": true + "managed": false }, { "conversation": "00000002-0000-0016-0000-004300000052", - "managed": true + "managed": false }, { "conversation": "0000007b-0000-0073-0000-002700000048", - "managed": true + "managed": false }, { "conversation": "0000003b-0000-0048-0000-002500000015", @@ -34,7 +34,7 @@ }, { "conversation": "00000055-0000-007c-0000-001500000051", - "managed": true + "managed": false } ] } diff --git a/libs/wire-api/test/golden/testObject_TeamConversationList_team_8.json b/libs/wire-api/test/golden/testObject_TeamConversationList_team_8.json index 492bf3e33c5..cf6455ca885 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversationList_team_8.json +++ b/libs/wire-api/test/golden/testObject_TeamConversationList_team_8.json @@ -2,11 +2,11 @@ "conversations": [ { "conversation": "00000026-0000-0066-0000-00170000007b", - "managed": true + "managed": false }, { "conversation": "00000064-0000-0015-0000-001f00000071", - "managed": true + "managed": false }, { "conversation": "00000063-0000-0049-0000-004100000018", @@ -14,19 +14,19 @@ }, { "conversation": "00000050-0000-002b-0000-000300000001", - "managed": true + "managed": false }, { "conversation": "00000035-0000-006e-0000-002f00000057", - "managed": true + "managed": false }, { "conversation": "0000006f-0000-0064-0000-003b0000002d", - "managed": true + "managed": false }, { "conversation": "0000003e-0000-0009-0000-00630000001d", - "managed": true + "managed": false }, { "conversation": "0000002a-0000-004d-0000-001b00000036", @@ -38,7 +38,7 @@ }, { "conversation": "00000016-0000-0007-0000-00690000002d", - "managed": true + "managed": false }, { "conversation": "00000043-0000-001f-0000-007500000002", @@ -46,19 +46,19 @@ }, { "conversation": "00000002-0000-0012-0000-006200000028", - "managed": true + "managed": false }, { "conversation": "00000019-0000-003a-0000-002300000023", - "managed": true + "managed": false }, { "conversation": "00000050-0000-006d-0000-00610000000c", - "managed": true + "managed": false }, { "conversation": "00000068-0000-0048-0000-003200000004", - "managed": true + "managed": false }, { "conversation": "00000003-0000-0024-0000-002000000015", @@ -66,15 +66,15 @@ }, { "conversation": "00000027-0000-0003-0000-007600000028", - "managed": true + "managed": false }, { "conversation": "00000074-0000-005d-0000-00100000005d", - "managed": true + "managed": false }, { "conversation": "00000071-0000-0075-0000-000a0000002c", - "managed": true + "managed": false }, { "conversation": "00000012-0000-0071-0000-004d00000010", @@ -94,11 +94,11 @@ }, { "conversation": "0000002d-0000-005f-0000-007f0000001b", - "managed": true + "managed": false }, { "conversation": "00000057-0000-0050-0000-002100000074", - "managed": true + "managed": false }, { "conversation": "00000077-0000-0063-0000-00360000000e", @@ -114,11 +114,11 @@ }, { "conversation": "00000029-0000-0043-0000-006700000030", - "managed": true + "managed": false }, { "conversation": "00000039-0000-003e-0000-008000000051", - "managed": true + "managed": false } ] } diff --git a/libs/wire-api/test/golden/testObject_TeamConversationList_team_9.json b/libs/wire-api/test/golden/testObject_TeamConversationList_team_9.json index c83346051a0..a9c45447710 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversationList_team_9.json +++ b/libs/wire-api/test/golden/testObject_TeamConversationList_team_9.json @@ -6,23 +6,23 @@ }, { "conversation": "00000080-0000-0009-0000-006500000038", - "managed": true + "managed": false }, { "conversation": "0000004e-0000-000a-0000-004e00000039", - "managed": true + "managed": false }, { "conversation": "00000062-0000-001e-0000-004c00000058", - "managed": true + "managed": false }, { "conversation": "00000004-0000-0021-0000-00670000000a", - "managed": true + "managed": false }, { "conversation": "0000004f-0000-0063-0000-004a0000004b", - "managed": true + "managed": false }, { "conversation": "00000044-0000-0017-0000-006300000067", @@ -30,11 +30,11 @@ }, { "conversation": "0000006a-0000-0070-0000-002e0000000a", - "managed": true + "managed": false }, { "conversation": "00000049-0000-0080-0000-006000000025", - "managed": true + "managed": false }, { "conversation": "0000007d-0000-0040-0000-001700000066", @@ -42,15 +42,15 @@ }, { "conversation": "00000057-0000-0045-0000-00610000006c", - "managed": true + "managed": false }, { "conversation": "00000001-0000-0042-0000-005b00000057", - "managed": true + "managed": false }, { "conversation": "00000048-0000-0032-0000-000000000069", - "managed": true + "managed": false }, { "conversation": "00000003-0000-0022-0000-00370000005b", @@ -62,7 +62,7 @@ }, { "conversation": "0000003a-0000-0067-0000-00060000003e", - "managed": true + "managed": false }, { "conversation": "0000001e-0000-0043-0000-002800000065", @@ -70,11 +70,11 @@ }, { "conversation": "00000053-0000-001f-0000-001700000006", - "managed": true + "managed": false }, { "conversation": "00000068-0000-0024-0000-004900000037", - "managed": true + "managed": false }, { "conversation": "00000005-0000-0019-0000-00670000005c", @@ -82,15 +82,15 @@ }, { "conversation": "00000029-0000-0003-0000-00520000004c", - "managed": true + "managed": false }, { "conversation": "00000080-0000-002f-0000-002b0000006f", - "managed": true + "managed": false }, { "conversation": "00000021-0000-002e-0000-004f0000005e", - "managed": true + "managed": false }, { "conversation": "0000006a-0000-0023-0000-00560000001b", @@ -98,23 +98,23 @@ }, { "conversation": "00000066-0000-007b-0000-00160000005c", - "managed": true + "managed": false }, { "conversation": "0000004e-0000-0008-0000-006b00000049", - "managed": true + "managed": false }, { "conversation": "0000005b-0000-0020-0000-005000000006", - "managed": true + "managed": false }, { "conversation": "00000052-0000-0038-0000-003400000074", - "managed": true + "managed": false }, { "conversation": "00000067-0000-006f-0000-00370000002e", - "managed": true + "managed": false } ] } diff --git a/libs/wire-api/test/golden/testObject_TeamConversation_team_11.json b/libs/wire-api/test/golden/testObject_TeamConversation_team_11.json index c3ed7357a11..25cf779c07c 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversation_team_11.json +++ b/libs/wire-api/test/golden/testObject_TeamConversation_team_11.json @@ -1,4 +1,4 @@ { "conversation": "00000003-0000-0041-0000-002600000041", - "managed": true + "managed": false } diff --git a/libs/wire-api/test/golden/testObject_TeamConversation_team_14.json b/libs/wire-api/test/golden/testObject_TeamConversation_team_14.json index 0e57b1c36d9..70ac6497480 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversation_team_14.json +++ b/libs/wire-api/test/golden/testObject_TeamConversation_team_14.json @@ -1,4 +1,4 @@ { "conversation": "0000005b-0000-0065-0000-002a00000060", - "managed": true + "managed": false } diff --git a/libs/wire-api/test/golden/testObject_TeamConversation_team_15.json b/libs/wire-api/test/golden/testObject_TeamConversation_team_15.json index d57937b90e8..bc6c7526159 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversation_team_15.json +++ b/libs/wire-api/test/golden/testObject_TeamConversation_team_15.json @@ -1,4 +1,4 @@ { "conversation": "0000001f-0000-0037-0000-005a0000004d", - "managed": true + "managed": false } diff --git a/libs/wire-api/test/golden/testObject_TeamConversation_team_17.json b/libs/wire-api/test/golden/testObject_TeamConversation_team_17.json index 6cee0ea4117..49f37f2faec 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversation_team_17.json +++ b/libs/wire-api/test/golden/testObject_TeamConversation_team_17.json @@ -1,4 +1,4 @@ { "conversation": "00000009-0000-0060-0000-005c00000049", - "managed": true + "managed": false } diff --git a/libs/wire-api/test/golden/testObject_TeamConversation_team_19.json b/libs/wire-api/test/golden/testObject_TeamConversation_team_19.json index e89e0e335b8..f4165939bdc 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversation_team_19.json +++ b/libs/wire-api/test/golden/testObject_TeamConversation_team_19.json @@ -1,4 +1,4 @@ { "conversation": "0000003d-0000-0025-0000-00170000002e", - "managed": true + "managed": false } diff --git a/libs/wire-api/test/golden/testObject_TeamConversation_team_4.json b/libs/wire-api/test/golden/testObject_TeamConversation_team_4.json index 4649545ec67..36244e41508 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversation_team_4.json +++ b/libs/wire-api/test/golden/testObject_TeamConversation_team_4.json @@ -1,4 +1,4 @@ { "conversation": "0000002d-0000-0034-0000-004600000023", - "managed": true + "managed": false } diff --git a/libs/wire-api/test/golden/testObject_TeamConversation_team_8.json b/libs/wire-api/test/golden/testObject_TeamConversation_team_8.json index 4c6909cb827..32455b74b26 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversation_team_8.json +++ b/libs/wire-api/test/golden/testObject_TeamConversation_team_8.json @@ -1,4 +1,4 @@ { "conversation": "0000002e-0000-006d-0000-003700000042", - "managed": true + "managed": false } diff --git a/libs/wire-api/test/golden/testObject_TeamConversation_team_9.json b/libs/wire-api/test/golden/testObject_TeamConversation_team_9.json index 62036a981f9..4e9f0537f6c 100644 --- a/libs/wire-api/test/golden/testObject_TeamConversation_team_9.json +++ b/libs/wire-api/test/golden/testObject_TeamConversation_team_9.json @@ -1,4 +1,4 @@ { "conversation": "0000000d-0000-001b-0000-006800000047", - "managed": true + "managed": false } diff --git a/libs/wire-api/test/golden/testObject_TeamUpdateData_team_1.json b/libs/wire-api/test/golden/testObject_TeamUpdateData_team_1.json index b5350e59d4f..d18604aedae 100644 --- a/libs/wire-api/test/golden/testObject_TeamUpdateData_team_1.json +++ b/libs/wire-api/test/golden/testObject_TeamUpdateData_team_1.json @@ -1,5 +1,5 @@ { - "icon": "\u001cH\n0󽯝L\u001b􏰉\u0011􇴗MT\u001ek\u001bC𒅔􈽏S~X󲾏\u0006𬖀~Lm괼\u0016𬀚6\u000f\u0010=􇰹􂅱U8h\u0003=𣆨􇦗\u0002b7Q\u0005t\u000b*|I{Ps𑿟W,\u001b\u0013m5ᒊ\u0004\u001f𭟧I\u0013󿅉X8\u000c\u0013a2(Ic􈨥t\u001dS\u001e\u0014M\u0017\u00104;0)j\u000e\u000e2yN\u001a𮟈L\u0012P$8𡚷V󼚗U𑚷𬿋\r\u0008󺅖\u001c𥨋bx㣂3;[Qb!i뤥J|ca$0n-\u000fAZ9\"9\u00175=𤌆\u0007\rZ\u0019n󽇀\u000cZevj}$`ᄄn\u0002\u00018𨽨8􁆁)|\u001b\u0011󵙵\u0019X8eLZ\u0012⡙U\u00034aI\u0003972GQF\u000eU*F󿵯\r3󾑘?d󳄚u<\u000c\u0011t𢥽\\gᅄ)c4꼵eP󺆳^\r_𦓫h󼶧Z\u0006𨁭􉫨\u0005\u000c{Y\u0016p\u0000𮓘&'\u0000\u001d*\n-\u001e\u0003\u001e[", + "icon": "3-1-47de4580-ae51-4650-acbb-d10c028cb0ac", "icon_key": "󹬄d^\u001d\u0005\u0018𨁾ey{𠌍􇘀q8󱞆^\u001d\u0007(\u0015春1𢺽(U!w\u001fqC\u0000#g\u0018=󴠦󾁀\u000e𨫿zEJ\u001d御𧱋𠮔\u0002e0󴜡D\u0003\u00089x@`VN7𨧰i𑦣uq\u000fdjL\u001c\u001d\u000e􈍚<\\􇎼%.\u000b0K䯄;5h\u0010flQr\u0004\u0010%󻮢x\u000c\u001cL&:/IK𐘻:𫰖E􏇍n7~󱜫*tOI3\u0001\u001e󱭒pp-㿤吺󽥚@O𬋭\t9]27󲺠U􄸼짎3[􋂅B9𬳺+\u0015S󲇞\u0018~\u001d92sLo􃍻t\u000f🀦𡳔2𝞪,\\𠚩􏦢ⰹ^Q𣼝W헃dY型\u001d𠣐N𒍒;\u0001󹗩'𨽕𨚇|𤰇(􋹽]\u001d!􏨪$\u001f𥤴)hloBRpT􆳝\u000c<", "name": "@t􍰳K􍒠\u000b藥\u0003e^󰖰x~U;^􉎤\u0008\u0016wn\\aS󹨾g\u0018󷺬\u0005'+\u0012~yJ𮢟%y𛁁!#3\u0018tZ􋞹[&{?\u0016X`욢f\u001c=j\u001f󿡧+d\u001b0𛇳쓨Ft`U𗶂g𦯯􅯺󱄤F\u0007vEBjP𥘻𗅆c𢭕󾐒e𫂿\u000fL𮐓􇸥GW󳃛I(XBV8\u0003\u0004󸞑C霥􆣚\u0000,𮘶/P󴒻[y\u0015Z \u001f즗c\u0014X`%􄙚8@\tP>em󠄵E\u0001" } diff --git a/libs/wire-api/test/golden/testObject_TeamUpdateData_team_10.json b/libs/wire-api/test/golden/testObject_TeamUpdateData_team_10.json index f93a378ce46..ac1ff8e6567 100644 --- a/libs/wire-api/test/golden/testObject_TeamUpdateData_team_10.json +++ b/libs/wire-api/test/golden/testObject_TeamUpdateData_team_10.json @@ -1,5 +1,5 @@ { - "icon": "𗍽m\u0010,6\u0000\"`􉴇𦎶*]4]\u0002𦶑󽡧\n𤇷;q$^V􀣭𬭄𐨝jG$𫋽S$fO[i=$􄿨\u0003\"􃵬􈑀𝣼;i\"@\u001d\u0012𝧟ඩIpA\u001a󲡈󿏺\\􉣊jnu𮪰:<\u0016\u0019\u0017'\u00189Q􅅵󶑌𩾴𗱐\u001fp%\u001f._b[􏂵75Z􁽬6􃐽g\u0003󶖠+\u0019\u000b\u0017𭯢Z:f\u0014\u0001I7#PK,\u0001􅌩<(3\u0006\u001fp􂠓e\u0015i^%c\u000eh􇤳%o\u0015𔐪􈱇\u001cE40u \u0007aR󻦥0<'~\"\u001b/􂯧𦷸[󿇳<\u0019H$LLV{\nfze\u0003j\u000c\u001b踰\u0006xoi\u0012~󺣯ϊy\rv\u000fM悍^X\u0008!锦\u0002*G\u0002m\u000bU\u0014\u0018", "name": "v􅰣!\u001fc\u0000F\u0003\u0014\u0010>\u0005)󵈦;󿏈\u001aF\u000b󱁼I\u0016g\u000cb\nXB37\u0012HHhO\u0013\u0007D\u0003\u001cmm𐂩W*󿐸d\u000b\u001a^󽌼ow𨮣&9𭕴B󸂽\nK!i\u0011􍘦\\𡝕󾸶𗑹4󾌳J8g\u0006􆶶𧿡􆔎N쩦|^􌂌@􅫆􋚼,Wi헝4\u000f\u0000`\u000f;\u000f=\u0001Pu]\u0000螗Q##\u0003𭔜\u0012", "name": "L\\\u000e7W󽚻\u001e\u0014S}\u000bj\u000c0,\u0007􀼞cz9󳭺𧪄tw𮙈x舋𫂯\u0011𬆛o+r;ntv\u0001t\u001d\r6􏤿G|󴈙S\u0008󾐓~eR9󺊘\u0004.9<􇾡\u0010\u0014掎󸘸HFꮀs텕𗡊i󿞹D$9M용駩'S􆿙5\u0005n\u001d󼶤:j(w(d\r􄶏\u001dO\u0008\u001e𩢬f\u001f󲈳Zj𩰨􎺯\u001aꋺ\u00198c􈄛\u0014`𤸓w*Cj-󸓄8{l\u00170#ꀀ)􆐵\u000b􅒀w!k𡉥󹢮ᬧ7󠇔𭸐x<銰0󴙳\u0006)󾍈( 󷪘K贂5ijLj\u000f𬲰zK𨭉c\u0007r􀝼󺓙{U+\u0010#𭱫2\rsH]\\'􈃳ㄐ#􂼦F>}jY󲧙:𬦖f:M󱿩􍚄󳁙#5𪃳\u0010𡿆*𘐵U𦃃\u0003\u0012U󰄳웾PU\u000b\u00021\u0008" } diff --git a/libs/wire-api/test/golden/testObject_TeamUpdateData_team_14.json b/libs/wire-api/test/golden/testObject_TeamUpdateData_team_14.json index f93c3fbc59d..86f3cc25965 100644 --- a/libs/wire-api/test/golden/testObject_TeamUpdateData_team_14.json +++ b/libs/wire-api/test/golden/testObject_TeamUpdateData_team_14.json @@ -1,5 +1,5 @@ { - "icon": "i\u001b\u0014I\u001b>X\u0018擯q+?\u00074J焛􎔤,뉆􊡝\u0016o,􉜲#O\u00184􂶇\r􏓬5.\u0011\u0002B*F\u0015𧘋l󷟕𫦤zJ\u0013'5.R\u0019\u0002󰃞\u00028M􎄫󻿵𩔓D𫞝\"\u0019\\!Z[ t\u001502t=\u001f􁈰󼬸􀓞A>4\u0014晴`₿\u0012d\\oB깶󹬿\u0003\u0007p!B厍􀒒\na^\u0018U!󽯫𘂏QOjEm𐂒`g𘟠\r\u000c\u001f褘\u0012n1\tD:S𐜩<8\r<󵁰bVi\u001doS'2o\u0017}𞡽=q􉊵\u0004\u0002w}\u0000궽hK󿟦+gud:W!󷟢\u000c𒐠}\r\u0004$L\n𡚉`\u0007𞴑M4y󴞥􀔳zYW\u0019󵼌􂜃Ty󿷐\u001c3\u0002V7윗j\u001d\u0007y7i\u0013\r􎔹l􈗮􀣸𘒼缷H%,(1j󳯌\u000b󰴯H:\u0016󵺳W\u0007i)𬎡C𧙮rm{1\u001b\u001brde\u0016$80%𠫲\u0019䄹O", + "icon": "3-1-bc1b2714-64a1-4cd5-bf28-769f2726c204", "icon_key": "yG𨄺|􀞂c󸒺\"N\u0002j\u000fb\u001aH\u001dNt𦇷􄿘?p,l!䱵o뫧),k\u0007󲅁쵷-\u0001qT@󲏋S\u0019𣋎7頪\r^T:=𐎮𥴉,DV\u0014𤊼􉁸iῑ\u000f𩽁Kx\u0017𫑖\u000ftLw󹳹􇶰􃗲\u0002Af莁􀆎亄􀛦*}0󶶽\u000e󼳦􌰫en[𠠦\u000fG\u0018i𛈡U.\u000c@\u0000Cr㋣\u0006\u001c@󺳊PX\u0018\u0019\u0001㺌0ꑔ\u0000A𠠻\u00075ﷸM􅌦", "name": "W\u000b👾삇\u0014\u0002\u0001􅨐􉺀0𫐗󹇜􌁡N􋳍j؏7]v\u0010\u0003􃶦􉫤do]?\"飙4\u0000^I5⌞{𥞏{\u000c>\u000eTx\n!dR8%󶔻N5\u000bI𡽠R\u0014\u0014uo󲟍2𪅩[V\u0010\u001d\u000f\u0008n𝤝J𫓷\u0004^x<𠇽W􇸬H#oF(t\tQ阘􆣔V3𫽪𥽝\u000f*T]r5G􊜹𦪽>礊𘑯󻔭qwVi|󸬢𧽄^b^/\\%\u0014𢳁A\u0003gL뚕Zt^Y\u0000 \u0006cv4𮊈f𬒿\u00135𡉢u秢\nOI부􍌾~􋂶𫢸4\u0000)%X󲏵Ar p􇖜󷫐y{DB|]lbI:3$䒢1bX+ 󼇸N󹖕\u000e󰒕\r짳\u000c~%\"{&𖹺jME\u001fmg{\u0010烱Q/𗝃XW" } diff --git a/libs/wire-api/test/golden/testObject_TeamUpdateData_team_15.json b/libs/wire-api/test/golden/testObject_TeamUpdateData_team_15.json index acb9d2adff0..206ee72d9cf 100644 --- a/libs/wire-api/test/golden/testObject_TeamUpdateData_team_15.json +++ b/libs/wire-api/test/golden/testObject_TeamUpdateData_team_15.json @@ -1,5 +1,5 @@ { - "icon": "𨑋Vn\u0002󶤆I𡷮靍􁢄w㝯@\u0015R7\u0000\r&3Lc\u0014?-𗄧\u0017hu􇒺$𬮍\u0016󿏘󿶭􃸉\u000c'ꜜ=\\鼝Bw\u0013\u0008󳖙=a\u001fF/Lx􏤆'\u0018\nkF􅇴􌸞\u001a*缮\u001fT&.􄹺*t\u0002\u001f􏇶rq4u3Frr𬦕$\u001fX&􏛮1U+8Tg𤅮뺻+􅷯~j\u0015F@\u0015%x\r|\u0013􊜗xC\u001b𑇮\u0003\u0011_*wA 􇊙𥞘X󽶣xx󿯶p\n?𗃷􈇩𬌚\u0002󹫆d䛺󽒀멊:wmx𧋔\u0001􆳫\u00138V;𞴧𣩓􉐀K侟𢱇v𒃳0a\u000f􊴇\u0011a8=@􆽇>\u00044J$e\u001c\u0008WP&\u0008󷗑􃡌􍝪5[ฦlYC􀦈z8\u0010󵖖\u001f䟥𗥷\u0011􉃵g𠂪\u0015𥣉RS􎺌Q󶘃𭟗tᜑ\u0002LAS3I~ 9\u0000󰩋rj𤪛C􂠎794&􏒺\u000evk`h?", + "icon": "3-1-f7d6739b-9dd7-4dff-acbf-972b0864158f", "icon_key": "`Ai(se􃳝q󷕚O\\wꈺz(.\u0006@g𐰮\r@􂻔\u000b\u001a8\u0001c䚏;\u001cq𝥽\u0013󶏲\u0002𫐧w#\\<\u0001𗂶v𨎳ĪK鐒AB\u000f㽬󽈪k(􂣎\u0010<􊽬\u001ae[\u0006dHU'^=rX織Gv\u00134𧀘o𢤌3*󳬁)", "name": "p\u0003맲\u001d\u0000_🎦n4􆦬6Vﹼ\u0002𦏎a􇯺97ퟣX躸)Y]󼷀t􃾨\u0011󱹍􅱡冗G`\tV\u001b0&\u0012tᄾ&󰛉oq\u0010M𬪊)+>Dh7󷀍c𦚣卮R𪻢a󲉨H󲐝􌒮\r_pU\r\u0006XP-\raR8P\u0019T5RD􆨟j>\u001eD0.𞄑\u001dtW􅯜YER)M@jid\u0003𤄝\u000eSQ-\u0010O@\u0008o\u0005@iV𒊫󴥐P}s걎f3\u0013\u001eAN=*^[󺐈\t𮐸q\u000f,s3?𪳶D􌜍*𠗧6𩗤!*\u001bTk􂪗Fyc\"L\rlv!𡕺NxB\u0005\rs&>M[\u0010\u000e𬽆q\u000e6n:\u001bb\u000fV\u0005LNT𫷠", "name": "{,W𛰽j𐔳VW,|\u0011-\u0019𖢤챍􉈧\u0005SdM𬺜~𭵠𨮗\u000b뙅󷉂󶎋ⷺm󴧒{R\\勢𬼩(\u0010@􎀈Eiw𬒃󱬅7􉱯쾥\u000bc[L[\u0015⑭_\u0018](H\u0007Oj󲧍\u001cgdV𫳿" } diff --git a/libs/wire-api/test/golden/testObject_TeamUpdateData_team_17.json b/libs/wire-api/test/golden/testObject_TeamUpdateData_team_17.json index 3b2bb052b73..958d0908e23 100644 --- a/libs/wire-api/test/golden/testObject_TeamUpdateData_team_17.json +++ b/libs/wire-api/test/golden/testObject_TeamUpdateData_team_17.json @@ -1,4 +1,4 @@ { - "icon": "𭪌𐫎𐪇􋉟(!WtCYcX$\u0018e-9Gr󳼒=M){w\u0012𦐆\\􏉻_\u0011*j\u001f.􏌅􅱅󺭅Ԣ;􌖖go\u000b:Dr0ΠWom􌠁P*𑩷\u0000𬎬􁎢 󺙖𩕺Ad_i?r蠸󸠽㥥𩓇~y~𭚲\"ByOiY1𭨜 \u001e+qaG9󺸄b\u000c\u000fW!􄢍srx~jᠵ\u001a􃻢Z𧅖􈯏􋱲\u0001~𑨂\u0008\u0000C=H\u000c9𫽷+𛱟󺠘𠁕\u0014p\u0008", + "icon": "3-1-8a5fd50d-9c32-494f-83ff-69db6d290fca", "name": "\u0003\u001cS\u000ff𘩄6,/󴯪𠖆,𗲱𨜣D𨺎~𥓃🃕av-W\n􈥳s(y`D;䉻n\u0001G\"]\u000f𦜫\"''A𩥅G\u0017eu𣶝􌧛 pM!4r􆞒\u001aK󻶤R⪤\u0011\u001c\u0005\u000bNu\r|{\u0001\u0006𢑍v-𤃌砀} 6]\u0006p󲖘>\u0003?𩩠[\u001f8􌒋jT1𡼧y\u0018)" } diff --git a/libs/wire-api/test/golden/testObject_TeamUpdateData_team_18.json b/libs/wire-api/test/golden/testObject_TeamUpdateData_team_18.json index 9c88c852df5..c745fc878fb 100644 --- a/libs/wire-api/test/golden/testObject_TeamUpdateData_team_18.json +++ b/libs/wire-api/test/golden/testObject_TeamUpdateData_team_18.json @@ -1,4 +1,4 @@ { - "icon": "jc3h󵢳8􍫜j2𧻺s𣞂nw󻋏ꤾ\u000ckOO\u000e쑜3Adqz0\u0007^AU\u0000/?\u001ez\t󷜳.[\u001c𗵑\u0006K𭼐g𤃧Y\u001f􀫓OZ\u0007𨄓(H븹𣉏\u00028(􇳚ﮡ􂥘?e󰖻_M𤑢Q~o钥X6WAL\u001a!󳩿⟄A2\u001a7𦗥a󴌒\u000b[T驼I󰟶􌒺\u001c$(\u0008q􋮒\u0007🗿\u0014\u0014f􆒙3Vc\u001d􈦻u🈙V\t밈󷱯\\\u001b\u0003𝒋𣦪f􌘛󵙌'𩪋w\u0013􌩁M3􍝭&6\u000b\u0010M \u001e", + "icon": "3-1-a55ba42a-1fff-4720-ab1f-404ac449a8c4", "name": "􇦬g^\u000f4\u0016􂛖(󶘆dn㬱ᓚ􎈙8ಷ\u001c\u0000d}W􇃪􏢛a󿍤8.DV􏩫\u0012Q!\u001a󷵊\u0000𨙰|k𢠧Y\u0008&]P\u0015M嶻{\u0001\u001f􈳼⇦fEL5􌖲^xy.􇿽􋺫ZD2EOw􄯗􍔠Z󱯱v\u001a䕲:U,yu3)*穐]6t:Q\nQ𑇟\u0013\u0003Cd󰍖&𨜉p􎜺u|􋽘wh:%KJQB>I倥W𩟏ⴔ𡃹\u000bIP+|9CꤧXBM􅓷$FR𐫔J5d\u001dK𩀬\u0018􎐹\u0019t'\u001e[zmz\u001b-􊉿􁋊{o𥜹/\u0010\u0015􅥿\u0010\u0012z~>iz󼴯j`𦐂G\rat{\u0008&􊤛𪰌,W􍆰􍍎󾔽𬊉G󼊽􀫼Q\u000f緓\u001eg^&>\u0004&BB]\u001a𬶀^^n𔔋􁴯\u0013舏\u001e2𝜾^I>^e􉪥2􂳖$+􌣄2\u0013>&4%4􀔘\u0011󰧩M𗌱𘖳0⧕\u001bM\u001du" } diff --git a/libs/wire-api/test/golden/testObject_TeamUpdateData_team_19.json b/libs/wire-api/test/golden/testObject_TeamUpdateData_team_19.json index 216f3824b26..90af802049f 100644 --- a/libs/wire-api/test/golden/testObject_TeamUpdateData_team_19.json +++ b/libs/wire-api/test/golden/testObject_TeamUpdateData_team_19.json @@ -1,5 +1,5 @@ { - "icon": "'𫇌􎤀\u001eSD􇗎\u0001􌇵\u000b\u0015d\u001f潦<>g\u0008t玱:􈞡^=6􆹭󴞒𗘘\u0011􈥥\r\u001bIu%b\u0012\u0008`/-+`􅟾\\\u0017^\u0017w\u0011L\u001fb?'󵏉\u0003\u0010\u001b\u000b􉹲𧙨}􇓪\\⡴$\u0014OE\u0017\u001e\u001d)Vej􅼿𪬋!\u0011W*s\u0012U%-𢘡KC`B\\k󿷑\u001e:\u0014􋞅\u001fN\u0013\u001b:ns\u001dj\u0012&-\u0003.h\u001aJN󻞢x1c\u0015\u0006ʆ+\u000fb\u0012mnp􆠝\u0003󳗶)\u0004 ;u𩙸5\u0019;\u000c ᧀ" } diff --git a/libs/wire-api/test/golden/testObject_TeamUpdateData_team_2.json b/libs/wire-api/test/golden/testObject_TeamUpdateData_team_2.json index d5e42c5ed6c..7f2c91f6a9a 100644 --- a/libs/wire-api/test/golden/testObject_TeamUpdateData_team_2.json +++ b/libs/wire-api/test/golden/testObject_TeamUpdateData_team_2.json @@ -1,5 +1,5 @@ { - "icon": "l𭧺t\u0018u~z􏕊x󳀁􉸳𣠩&\u0011󸵗\u0014󳶎𭏟SRv𔗉8\u0005\\L*􈴌:Y", + "icon": "3-1-43f5979a-b8b3-4a6d-86f8-532445d025dc", "icon_key": "8Q(󳁰\"慒􈚬]1'N𪊠?7&\n􈖕\u0007\u000c𭯵K\u00152󳧦𓐐e7󳵋)Gm􍋗ueG󷔏􀧟^\u0016\u0004i\u001fXKLxz􍱪\u0019󸫧I\u0018@p𣯲􋴽 4|D\rg𠜵K\u001e􁝙bHr􎀭󲊁\u0001R𥷇󺳟\u0002r)\u0017t\u0000y>􉊫lE\"􄯠\u0005𗳛v'T󹛫(VJn\u0014옋feOI󶞮D&saC\u000c)k73\u0001Cp9o\u0016𗺃鸫􎰫󸹅IO􈒱󽈂􋒎K*w`xc8%ᔴ\u0008Q􎪣󺡶\u0013\u0015\t𭣔\u0008d󻙒~󾺑\u0002pźF󱺸󿸞-\u0002\u000b𩔁!􉒊\u0017𨶑+\u0011&A\u000b\u001f$\u001c4􍡗𬻧C\u000f􋮺W6{BNO<\u0011\u0008@!a󴶙r.-󴣞𪴵󿛊⍾b", "name": "𮕯ZV\u001b􎧶:RV\u00060%U\r\u0005ᒹ\u000bg\u001a\u0013𐙘\u000b䉭\u000b𨑩\u0013\u0018`\t𥛮\t_󻟩䪰\u001f\r\"𬣬𗖚󶻦\u000e@鰢\u0010\u0006y8*𠻄P;o`53L\u0006󴄍\t㼝e쐖\u0016$\u0001􍁯􊺸\u0015󻴥𘝺\u0008\u001ceW,􈒳#UU\u0011%岻dF𐼇Z^󲠵u𬫓v󺟜gb􉧟\u001fR 뿭\u0001m\u0005+\u001edHcX󾰀\u00161ꉚ\t.r^\n\u0013掜^\u0019p嶇h>󶉼%􄋕#:仰\u0011EB.󳏄0H6𪠬𮣛䭈󷏤o>Lᢑ_\u0003󱙢7.9􆁖\u0011WQ\u000br3󷪝g\u001f𫪌dZ\u0014󱀯\\󼚗󳷑~磟R\tl3Fz\u001d\u0013\u001a\u0006D󻾗KjM\u001c^\u000f,鯲Rjve\u0015$\u0014!\\\u000f/13xE𫋩ꐌX:B\u0011h闰0㸸>􈪡\u000b5\u001d'󻌊\u0003\u0001gj*𬙯#P" } diff --git a/libs/wire-api/test/golden/testObject_TeamUpdateData_team_4.json b/libs/wire-api/test/golden/testObject_TeamUpdateData_team_4.json index 68f374519ee..7a0c102aa82 100644 --- a/libs/wire-api/test/golden/testObject_TeamUpdateData_team_4.json +++ b/libs/wire-api/test/golden/testObject_TeamUpdateData_team_4.json @@ -1,5 +1,5 @@ { - "icon": "破5\u001c㒞\u0003\t5l𘩪j\u0011iuﴄ󴢆􀧻\u0013)$M82pC,Hwy\u0003>Vf\u000ez\u001a،\u0017_4B\u000c􋵓󾰋𦉓a􍈂𭞺FZ", + "icon": "3-1-2f002dda-74de-457b-aabe-831229662e4d", "icon_key": "ch=\u0013zyXV֮􌶐🚽[4N𗭾􏸳(sN􋏊=J\u0018\u0006uZ󺗾[L\u0016f-󾸁S+zKX\u0013 L\u0015\u0004𦿥,\u0014𩆴'􌓗#b]\u000b𮮶:7𮞿󴰍b󷌥|c𠋳,깅󸒀O𝓊l=󺮠QA勸볷E󿹅#J\u000focH&\u000f𮬿h\u001a^\t\u0000󰽇󾇛n[􌝁o𝂘\u0012ꬽꐉ,\u001a\u0007\u0005\u0019亟*m􂳸&𩰲􂒿\u0004E#W'\u0007;+I\u0016\u001cg\n^i[󾿁\u0002.\u0018]卢b\\􎆳󵓞\u0014J󶦗􎾌s:D\u001c\u000eBwpPF\u001b\\\u000c󾬺𦚏羍H󽣤󸱌j􄷒䮺𝅨\u0001\\̑NE쪼𑦮\u0011\rU\t𤤑j\u0016X\n󾜆}󾗉󿧵􅈈\u000bS󹬝{􃧂𑧇\u001fi󾫛\u0012􌋸^8𓈅\u0006\u000ebGᢽX􎙼\u001a!R^\u0016qjf}|\u0002$\u001b0Q\u000f\u0006󺒳\u0007뎿F𪗴~\u0014T􏌫𠾚j+I\u0019\u001eWK\u0012f" } diff --git a/libs/wire-api/test/golden/testObject_TeamUpdateData_team_5.json b/libs/wire-api/test/golden/testObject_TeamUpdateData_team_5.json index f9aefa1e1ba..63c4b71a029 100644 --- a/libs/wire-api/test/golden/testObject_TeamUpdateData_team_5.json +++ b/libs/wire-api/test/golden/testObject_TeamUpdateData_team_5.json @@ -1,4 +1,4 @@ { - "icon": "0yl+Ej0A)D\u0015\u0017}󽳮􃨰FB쑻𗸟𣭈蝜+꜏2&\u0016􉺛\u0003?𥈕[Td*K!c󸦓𧶥<󾪩􁑒󼴝󰏜ecC𭷔ᑇ\u000c􌻒{-\\\u0004󹥥\u001c1qhH\u0002\u0006Nq\u0015f\u0008{&ጮ󼘮𢍞\u0016\u00133\u0013-.:K𪼊F7I\u0003􋺄il􎳋𫷵A𨿢F~N\u0008𘡪큔螌Z!\u0014?𦚍ꕕn\u001b􉧝", + "icon": "3-1-1ab65a15-a0de-4e3a-b5cf-533b43df652e", "name": "c\u0014嗅􈛊Am\u0001,0ᘂ󷁚\u0010\u0011􀀕\u001b>\u0011rj5Hd􃇁I'$.𗾧\u0012}\u0005\u0012󶟡𦯷𘜥|\u001c\r\u0010^⅚t𮝋􂲿vf󿪄`d' '={4뼐\u00085][T𨕋7A1缃\u0015Y\\frek5$f6b_4%🧩\u0012󿱀!\u00116\u000c\u0003K[SQH\n语󸚲􉳟\u0019d\\@RQB􏵺𒔺A^ l􈺄󼗷X^i󷦷\u0001󾧱𫀳I􎟩\u001f3󿄚xIC#C-􁝢\u001a𡎥r诣\u000c,\u0016=\u0001J鹮I=󱆙" } diff --git a/libs/wire-api/test/golden/testObject_TeamUpdateData_team_7.json b/libs/wire-api/test/golden/testObject_TeamUpdateData_team_7.json index bcbb9f5e80f..9c435ffceeb 100644 --- a/libs/wire-api/test/golden/testObject_TeamUpdateData_team_7.json +++ b/libs/wire-api/test/golden/testObject_TeamUpdateData_team_7.json @@ -1,4 +1,4 @@ { - "icon": "󱸑\u0018萾3􌁠EE\u001etQb\u001c`:;ህ=󳤀P2\u0013\\\u0015Z􀤾\u0011p'WrP3\u001b𥍖9o\u001c,H\u0012.&𛄃💛\u0001o\u001e ^Jak&\u0012󶫛㌙W\u00185\t?;/󰍫\u0010A𢉡A\u000b*Q𐓀<𨧋i80\u000b%c{U\u000e\u0006W􇁲&~{R>iP\u0016쉂\u0005I󶿵*K󳕸a君\"gi\u0015􋉔.􅦕􇅟L\u0015𠛸<󸗚\u0017\u0014\u0018𘢗U󳥾f🕜𧥿~\u0004b\t󴴞\u000e꧶󼍺ት\u0008\u0015ᴳ𨂤QaZ)\u001ft\u0005\u0008.󶍳^𮑐\u0014󲭨{S𡭳sRGr󺠤󸘖$􈴘2~b𡨑", + "icon": "3-1-b5aa5007-2939-4c53-874b-aecfbb6244fd", "name": "n{􂇭oZn\u0011\u001bJt kj\u0006\r󶛟\"'{\u001aX𬵓?@􅵡Ly󼝟\u0005@$𞱾W𬩠𥏕mW󻺕\u0011\u0010^c)𭖇󱋢𩎓곽9󺣐􇻱􉭓\u0017\u0014\u0012My\u0019󳷤\u0018𦁩gmi䙓\u000fy:r[󻋻i.\\\u0001󷣯\u00175𭃩H#\u0012𘛸#l\u000f@𤞏@\u0015)𦀗Jg㺽c\u0006V\u0004􀷓𨫛􎅎\u0013Ჴ󷬹󽭮fQ躼󷱚􌞸|Ik\u0011X\u0016\u001c}ii󿂹M&.)𧽠\u0016L󵰲\u001bk\u0007! \u001a􈤅𤵬+\u001eRW\\x\u001f\u001dt󱏮􂸛芝\u0018\u0002쟲+\u0012􀬤,\\F%,w𪨎a\\]\u001e𢥟~X;f𠵒 􄋀p\u0011d8mhY7w\u001ee\u0003\u0011􏡱l\u0017{3&뾄\u0006\u0014V=D\u0000\u0005\u000f𖼅\u0007K_di=," } diff --git a/libs/wire-api/test/golden/testObject_TeamUpdateData_team_9.json b/libs/wire-api/test/golden/testObject_TeamUpdateData_team_9.json index 26171e0268a..28253966b76 100644 --- a/libs/wire-api/test/golden/testObject_TeamUpdateData_team_9.json +++ b/libs/wire-api/test/golden/testObject_TeamUpdateData_team_9.json @@ -1,5 +1,5 @@ { - "icon": "A\u001f𪔆𪑂\u0016\u0006ZmN\u00009RY\t1;\u001dp#'带\u0010l\n󴻉󰻅T𤲸\u0017󸽵 r+𞤪P/P𦕦1󲥺-w𦅒𪟠y\u000fC86\taO_􌈄;\u001e@\n\u0000𣪑\u0011󼨚𧅼7QX󾾢ᵪ7󼌹𭗻􃚐JP\n", + "icon": "3-1-3f6de95d-a973-4652-93c4-0ffb3fa381fb", "icon_key": "F;\u0003O\rTm\u0011𨈙0󼴪\u0014􂤗􁆌뫲7\u0004𭼴![\u0002󼐫󿧺\u0012\u0001\u0008\n\u000emV󼍵􏤁\u0006B𭻻z&𣏄\u0014HP౨WS𬤕\u0001QT\u001e\u0003$b_𭞁臭\u0000􈅃􎻠@~\u0010.~𢝞sl\u0018\u0005\u0019􇤃䈌V(^|𮦱\u001d䛔\u000er􏷱\u0019eb2(#*!𭄔B\u0007^󰊳\u001a{\u0012t\\𛀟\u000cu\"\u0017\u000e\np􀉷[\u0001lB𧖽lꭦL녿\u000f𡰯/\n\u001d2Gw~y𢻄uᅳ1󳶀\t𝢊l8CE\u0011L𮄖󳸎CB\u001d{Xl𤒯󲂺,भ#{^􁧮S=.g\u0016\u0003%\u0002b􏳊𬖏4:숸󿺀􎑞u󶪸\u001d\u0003!𝣟q\u001d.J􍃐􆲁*2􅎧\u0002􄹰x,.\u0013\u000cR\u001b\u0008󵉰/=2n^f\u001fRB\u000b\u0004K􆀍䚍$䎺\u0019~-}S󳐔ipLl𮧕IJ\u001d\u0016\u0008u渨\u0018kq@1m𞹒5|\u0012O1<' +-- +-- 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.Team.Export where + +import Data.ByteString.Arbitrary +import qualified Data.ByteString.Char8 as C +import Imports +import Test.Tasty +import Test.Tasty.HUnit +import Test.Tasty.QuickCheck (conjoin, counterexample, testProperty, (.||.), (===)) +import Wire.API.Team.Export + +tests :: TestTree +tests = + testGroup + "Export - quoted and unquoted" + [ testTrivialExamples, + testRoundTrip, + testUnquotedProp, + testQuotedProp + ] + +testTrivialExamples :: TestTree +testTrivialExamples = testCase "quoted/unquoted examples" $ do + unquoted "'foobar" @?= "foobar" + unquoted "foobar" @?= "foobar" + unquoted "" @?= "" + quoted "" @?= "" + quoted "foobar" @?= "foobar" + quoted "=1+2" @?= "'=1+2" + +testRoundTrip :: TestTree +testRoundTrip = testProperty "quoted roundtrip" prop + where + prop (ABS bs) = counterexample (show $ quoted bs) $ bs === (unquoted . quoted) bs + +testUnquotedProp :: TestTree +testUnquotedProp = testProperty "unquoted arbitrary" prop + where + prop (ABS bs) = counterexample (show $ unquoted bs) $ (bs === unquoted bs) .||. startsWithSingleQuote bs + startsWithSingleQuote bs = case C.uncons bs of + Just ('\'', _) -> True + _ -> False + +testQuotedProp :: TestTree +testQuotedProp = testProperty "quoted arbitrary" prop + where + prop (ABS bs) = counterexample (show $ quoted bs) $ conjoin (checkQuoted bs <$> disallowedChars) + checkQuoted bs char = quoted (char `C.cons` bs) === '\'' `C.cons` char `C.cons` bs + disallowedChars = ['@', '+', '-', '=', '\'', '\x0009', '\x000D'] diff --git a/libs/wire-api/wire-api.cabal b/libs/wire-api/wire-api.cabal index c26cfc4b431..cca067eaaaa 100644 --- a/libs/wire-api/wire-api.cabal +++ b/libs/wire-api/wire-api.cabal @@ -45,10 +45,13 @@ library Wire.API.Provider.Service.Tag Wire.API.Push.Token Wire.API.Push.V2.Token + Wire.API.RawJson Wire.API.Routes.AssetBody Wire.API.Routes.Internal.Brig Wire.API.Routes.Internal.Brig.Connection Wire.API.Routes.Internal.Brig.EJPD + Wire.API.Routes.Internal.Cargohold + Wire.API.Routes.Internal.LegalHold Wire.API.Routes.MultiTablePaging Wire.API.Routes.MultiTablePaging.State Wire.API.Routes.MultiVerb @@ -62,6 +65,8 @@ library Wire.API.Routes.Public.Spar Wire.API.Routes.Public.Util Wire.API.Routes.QualifiedCapture + Wire.API.Routes.Version + Wire.API.Routes.Version.Wai Wire.API.Routes.WebSocket Wire.API.ServantProto Wire.API.Swagger @@ -94,6 +99,7 @@ library Wire.API.User.Search Wire.API.UserMap Wire.API.Util.Aeson + Wire.API.VersionInfo Wire.API.Wrapped other-modules: Paths_wire_api @@ -161,6 +167,7 @@ library , currency-codes >=2.0 , deriving-aeson >=0.2 , deriving-swagger2 + , either , email-validate >=2.0 , errors , extended @@ -214,6 +221,8 @@ library , uuid >=1.3 , vector >=0.12 , wai + , wai-extra + , wai-utilities , wai-websockets , websockets , wire-message-proto-lens @@ -320,8 +329,7 @@ test-suite wire-api-golden-tests Test.Wire.API.Golden.Generated.NewBotRequest_provider Test.Wire.API.Golden.Generated.NewBotResponse_provider Test.Wire.API.Golden.Generated.NewClient_user - Test.Wire.API.Golden.Generated.NewConvManaged_user - Test.Wire.API.Golden.Generated.NewConvUnmanaged_user + Test.Wire.API.Golden.Generated.NewConv_user Test.Wire.API.Golden.Generated.NewLegalHoldClient_team Test.Wire.API.Golden.Generated.NewLegalHoldService_team Test.Wire.API.Golden.Generated.NewOtrMessage_user @@ -398,6 +406,7 @@ test-suite wire-api-golden-tests Test.Wire.API.Golden.Generated.SFTServer_user Test.Wire.API.Golden.Generated.SimpleMember_user Test.Wire.API.Golden.Generated.SimpleMembers_user + Test.Wire.API.Golden.Generated.SndFactorPasswordChallengeAction_user Test.Wire.API.Golden.Generated.Team_team Test.Wire.API.Golden.Generated.TeamBinding_team Test.Wire.API.Golden.Generated.TeamContact_user @@ -452,6 +461,7 @@ 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.CreateScimToken Test.Wire.API.Golden.Manual.FeatureConfigEvent Test.Wire.API.Golden.Manual.GetPaginatedConversationIds Test.Wire.API.Golden.Manual.ListConversations @@ -559,6 +569,7 @@ test-suite wire-api-tests Test.Wire.API.Roundtrip.CSV Test.Wire.API.Routes Test.Wire.API.Swagger + Test.Wire.API.Team.Export Test.Wire.API.Team.Member Test.Wire.API.User Test.Wire.API.User.RichInfo @@ -614,6 +625,7 @@ test-suite wire-api-tests , aeson-qq , base , bytestring + , bytestring-arbitrary >=0.1.3 , bytestring-conversion , case-insensitive , cassava diff --git a/libs/zauth/test/ZAuth.hs b/libs/zauth/test/ZAuth.hs index 13c2de15611..703cbd69b5a 100644 --- a/libs/zauth/test/ZAuth.hs +++ b/libs/zauth/test/ZAuth.hs @@ -94,7 +94,7 @@ testNotExpired p = do liftIO $ assertBool "testNotExpired: validation failed" (isRight x) -- The testExpired test conforms to the following testing standards: --- @SF.Channel @TSFI.RESTfulAPI @S2 @S3 +-- @SF.Channel @TSFI.RESTfulAPI @TSFI.NTP @S2 @S3 -- -- Using an expired access token should fail testExpired :: V.Env -> Create () diff --git a/nix/default.nix b/nix/default.nix index 00a7df196fa..00bf284b9b7 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -4,11 +4,11 @@ let config.allowUnfree = true; overlays = [ # the tool we use for versioning (The thing that generates sources.json) - (_: _: { niv = (import sources.niv {}).niv; }) + (_: _: { niv = (import sources.niv { }).niv; }) # All wire-server specific packages - (import ./overlays/wire-server.nix) + (import ./overlay.nix) ]; }; in - pkgs +pkgs diff --git a/nix/overlay.nix b/nix/overlay.nix new file mode 100644 index 00000000000..771ec26e503 --- /dev/null +++ b/nix/overlay.nix @@ -0,0 +1,19 @@ +self: super: { + cryptobox = self.callPackage ./pkgs/cryptobox { }; + zauth = self.callPackage ./pkgs/zauth { }; + + nginxModules = super.nginxModules // { + zauth = { + src = ../services/nginz/third_party/nginx-zauth-module; + inputs = [ self.pkg-config self.zauth ]; + }; + }; + + nginz = super.nginx.override { + modules = [ + self.nginxModules.vts + self.nginxModules.moreheaders + self.nginxModules.zauth + ]; + }; +} diff --git a/nix/overlays/wire-server.nix b/nix/overlays/wire-server.nix deleted file mode 100644 index 9ba8ca872fb..00000000000 --- a/nix/overlays/wire-server.nix +++ /dev/null @@ -1,73 +0,0 @@ -self: super: { - # TODO: Do not use buildRustPackage. Ces't horrible - cryptobox = self.callPackage ( - { fetchFromGitHub, rustPlatform, pkgconfig, libsodium }: - rustPlatform.buildRustPackage rec { - name = "cryptobox-c-${version}"; - version = "2019-06-17"; - nativeBuildInputs = [ pkgconfig ]; - buildInputs = [ libsodium ]; - src = fetchFromGitHub { - owner = "wireapp"; - repo = "cryptobox-c"; - rev = "4067ad96b125942545dbdec8c1a89f1e1b65d013"; - sha256 = "1i9dlhw0xk1viglyhail9fb36v1awrypps8jmhrkz8k1bhx98ci3"; - }; - cargoSha256 = "sha256-Afr3ShCXDCwTQNdeCZbA5/aosRt+KFpGfT1mrob6cog="; - - patchLibs = super.lib.optionalString super.stdenv.isDarwin '' - install_name_tool -id $out/lib/libcryptobox.dylib $out/lib/libcryptobox.dylib - ''; - - postInstall = '' - ${patchLibs} - mkdir -p $out/include - cp src/cbox.h $out/include - ''; - } - ) {}; - - zauth = self.callPackage ( - { fetchFromGitHub, rustPlatform, pkgconfig, libsodium }: - rustPlatform.buildRustPackage rec { - name = "libzauth-${version}"; - version = "3.0.0"; - nativeBuildInputs = [ pkgconfig ]; - buildInputs = [ libsodium ]; - src = self.nix-gitignore.gitignoreSourcePure [ ../../.gitignore ] ../../libs/libzauth; - sourceRoot = "libzauth/libzauth-c"; - - cargoSha256 = "sha256-umwOVCFHtszu64aIc8eqMPGCS7vt1nYQFAQh2XuV+v4="; # self.lib.fakeSha256; - - patchLibs = super.lib.optionalString super.stdenv.isDarwin '' - install_name_tool -id $out/lib/libzauth.dylib $out/lib/libzauth.dylib - ''; - - postInstall = '' - mkdir -p $out/lib/pkgconfig - mkdir -p $out/include - cp src/zauth.h $out/include - sed -e "s~<>~${version}~" \ - -e "s~<>~$out~" \ - src/libzauth.pc > $out/lib/pkgconfig/libzauth.pc - cp target/release-tmp/libzauth.* $out/lib/ - ${patchLibs} - ''; - } - ) {}; - - nginxModules = super.nginxModules // { - zauth = { - src = ../../services/nginz/third_party/nginx-zauth-module; - inputs = [ self.pkg-config self.zauth ]; - }; - }; - - nginz = super.nginx.override { - modules = [ - self.nginxModules.vts - self.nginxModules.moreheaders - self.nginxModules.zauth - ]; - }; -} diff --git a/nix/pkgs/cryptobox/default.nix b/nix/pkgs/cryptobox/default.nix new file mode 100644 index 00000000000..5945c616f22 --- /dev/null +++ b/nix/pkgs/cryptobox/default.nix @@ -0,0 +1,31 @@ +{ fetchFromGitHub +, lib +, libsodium +, pkg-config +, rustPlatform +, stdenv +}: + +rustPlatform.buildRustPackage rec { + name = "cryptobox-c-${version}"; + version = "2019-06-17"; + nativeBuildInputs = [ pkg-config ]; + buildInputs = [ libsodium ]; + src = fetchFromGitHub { + owner = "wireapp"; + repo = "cryptobox-c"; + rev = "4067ad96b125942545dbdec8c1a89f1e1b65d013"; + sha256 = "1i9dlhw0xk1viglyhail9fb36v1awrypps8jmhrkz8k1bhx98ci3"; + }; + cargoSha256 = "sha256-Afr3ShCXDCwTQNdeCZbA5/aosRt+KFpGfT1mrob6cog="; + + patchLibs = lib.optionalString stdenv.isDarwin '' + install_name_tool -id $out/lib/libcryptobox.dylib $out/lib/libcryptobox.dylib + ''; + + postInstall = '' + ${patchLibs} + mkdir -p $out/include + cp src/cbox.h $out/include + ''; +} diff --git a/nix/pkgs/zauth/default.nix b/nix/pkgs/zauth/default.nix new file mode 100644 index 00000000000..7572ec3478e --- /dev/null +++ b/nix/pkgs/zauth/default.nix @@ -0,0 +1,34 @@ +{ fetchFromGitHub +, lib +, libsodium +, nix-gitignore +, pkg-config +, rustPlatform +, stdenv +}: + +rustPlatform.buildRustPackage rec { + name = "libzauth-${version}"; + version = "3.0.0"; + nativeBuildInputs = [ pkg-config ]; + buildInputs = [ libsodium ]; + src = nix-gitignore.gitignoreSourcePure [ ../../../.gitignore ] ../../../libs/libzauth; + sourceRoot = "libzauth/libzauth-c"; + + cargoSha256 = "umwOVCFHtszu64aIc8eqMPGCS7vt1nYQFAQh2XuV+v4="; # self.lib.fakeSha256; + + patchLibs = lib.optionalString stdenv.isDarwin '' + install_name_tool -id $out/lib/libzauth.dylib $out/lib/libzauth.dylib + ''; + + postInstall = '' + mkdir -p $out/lib/pkgconfig + mkdir -p $out/include + cp src/zauth.h $out/include + sed -e "s~<>~${version}~" \ + -e "s~<>~$out~" \ + src/libzauth.pc > $out/lib/pkgconfig/libzauth.pc + cp target/release-tmp/libzauth.* $out/lib/ + ${patchLibs} + ''; +} diff --git a/services/brig/brig.cabal b/services/brig/brig.cabal index 48fcbc3f34e..eee5ef22f98 100644 --- a/services/brig/brig.cabal +++ b/services/brig/brig.cabal @@ -424,6 +424,7 @@ executable brig-integration API.User.RichInfo API.User.Util API.UserPendingActivation + API.Version Federation.End2end Federation.Util Index.Create diff --git a/services/brig/brig.integration.yaml b/services/brig/brig.integration.yaml index fa2d13ffe14..57a98b11fef 100644 --- a/services/brig/brig.integration.yaml +++ b/services/brig/brig.integration.yaml @@ -152,7 +152,6 @@ optSettings: setSuspendInactiveUsers: # if this is omitted: never suspend inactive users. suspendTimeout: 10 setRichInfoLimit: 5000 # should be in sync with Spar - setDefaultTemplateLocale: en setDefaultUserLocale: en setMaxTeamSize: 32 setMaxConvSize: 16 diff --git a/services/brig/src/Brig/API.hs b/services/brig/src/Brig/API.hs index 9856f8520e5..3a668844344 100644 --- a/services/brig/src/Brig/API.hs +++ b/services/brig/src/Brig/API.hs @@ -26,7 +26,7 @@ import qualified Brig.API.Public as Public import qualified Data.Swagger.Build.Api as Doc import Network.Wai.Routing (Routes) -sitemap :: Routes Doc.ApiBuilder Handler () +sitemap :: Routes Doc.ApiBuilder (Handler r) () sitemap = do Public.sitemap Public.apiDocs diff --git a/services/brig/src/Brig/API/Client.hs b/services/brig/src/Brig/API/Client.hs index a265fbc462e..27324a294b9 100644 --- a/services/brig/src/Brig/API/Client.hs +++ b/services/brig/src/Brig/API/Client.hs @@ -84,18 +84,18 @@ import Wire.API.Team.LegalHold (LegalholdProtectee (..)) import Wire.API.User.Client import Wire.API.UserMap (QualifiedUserMap (QualifiedUserMap, qualifiedUserMap), UserMap (userMap)) -lookupLocalClient :: UserId -> ClientId -> AppIO (Maybe Client) +lookupLocalClient :: UserId -> ClientId -> (AppIO r) (Maybe Client) lookupLocalClient = Data.lookupClient -lookupLocalClients :: UserId -> AppIO [Client] +lookupLocalClients :: UserId -> (AppIO r) [Client] lookupLocalClients = Data.lookupClients -lookupPubClient :: Qualified UserId -> ClientId -> ExceptT ClientError AppIO (Maybe PubClient) +lookupPubClient :: Qualified UserId -> ClientId -> ExceptT ClientError (AppIO r) (Maybe PubClient) lookupPubClient qid cid = do clients <- lookupPubClients qid pure $ find ((== cid) . pubClientId) clients -lookupPubClients :: Qualified UserId -> ExceptT ClientError AppIO [PubClient] +lookupPubClients :: Qualified UserId -> ExceptT ClientError (AppIO r) [PubClient] lookupPubClients qid@(Qualified uid domain) = do getForUser <$> lookupPubClientsBulk [qid] where @@ -104,7 +104,7 @@ lookupPubClients qid@(Qualified uid domain) = do um <- userMap <$> Map.lookup domain (qualifiedUserMap qmap) Set.toList <$> Map.lookup uid um -lookupPubClientsBulk :: [Qualified UserId] -> ExceptT ClientError AppIO (QualifiedUserMap (Set PubClient)) +lookupPubClientsBulk :: [Qualified UserId] -> ExceptT ClientError (AppIO r) (QualifiedUserMap (Set PubClient)) lookupPubClientsBulk qualifiedUids = do loc <- qualifyLocal () let (localUsers, remoteUsers) = partitionQualified loc qualifiedUids @@ -116,12 +116,12 @@ lookupPubClientsBulk qualifiedUids = do localUserClientMap <- Map.singleton (tDomain loc) <$> lookupLocalPubClientsBulk localUsers pure $ QualifiedUserMap (Map.union localUserClientMap remoteUserClientMap) -lookupLocalPubClientsBulk :: [UserId] -> ExceptT ClientError AppIO (UserMap (Set PubClient)) +lookupLocalPubClientsBulk :: [UserId] -> ExceptT ClientError (AppIO r) (UserMap (Set PubClient)) lookupLocalPubClientsBulk = Data.lookupPubClientsBulk -- nb. We must ensure that the set of clients known to brig is always -- a superset of the clients known to galley. -addClient :: UserId -> Maybe ConnId -> Maybe IP -> NewClient -> ExceptT ClientError AppIO Client +addClient :: UserId -> Maybe ConnId -> Maybe IP -> NewClient -> ExceptT ClientError (AppIO r) Client addClient u con ip new = do acc <- lift (Data.lookupAccount u) >>= maybe (throwE (ClientUserNotFound u)) return loc <- maybe (return Nothing) locationOf ip @@ -149,7 +149,7 @@ addClient u con ip new = do where clientId' = clientIdFromPrekey (unpackLastPrekey $ newClientLastKey new) -updateClient :: UserId -> ClientId -> UpdateClient -> ExceptT ClientError AppIO () +updateClient :: UserId -> ClientId -> UpdateClient -> ExceptT ClientError (AppIO r) () updateClient u c r = do client <- lift (Data.lookupClient u c) >>= maybe (throwE ClientNotFound) pure for_ (updateClientLabel r) $ lift . Data.updateClientLabel u c . Just @@ -163,7 +163,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 AppIO () +rmClient :: UserId -> ConnId -> ClientId -> Maybe PlainTextPassword -> ExceptT ClientError (AppIO r) () rmClient u con clt pw = maybe (throwE ClientNotFound) fn =<< lift (Data.lookupClient u clt) where @@ -177,14 +177,14 @@ rmClient u con clt pw = _ -> Data.reauthenticate u pw !>> ClientDataError . ClientReAuthError lift $ execDelete u (Just con) client -claimPrekey :: LegalholdProtectee -> UserId -> Domain -> ClientId -> ExceptT ClientError AppIO (Maybe ClientPrekey) +claimPrekey :: LegalholdProtectee -> UserId -> Domain -> ClientId -> ExceptT ClientError (AppIO r) (Maybe ClientPrekey) claimPrekey protectee u d c = do isLocalDomain <- (d ==) <$> viewFederationDomain if isLocalDomain then claimLocalPrekey protectee u c else claimRemotePrekey (Qualified u d) c -claimLocalPrekey :: LegalholdProtectee -> UserId -> ClientId -> ExceptT ClientError AppIO (Maybe ClientPrekey) +claimLocalPrekey :: LegalholdProtectee -> UserId -> ClientId -> ExceptT ClientError (AppIO r) (Maybe ClientPrekey) claimLocalPrekey protectee user client = do guardLegalhold protectee (mkUserClients [(user, [client])]) lift $ do @@ -192,27 +192,27 @@ claimLocalPrekey protectee user client = do when (isNothing prekey) (noPrekeys user client) pure prekey -claimRemotePrekey :: Qualified UserId -> ClientId -> ExceptT ClientError AppIO (Maybe ClientPrekey) +claimRemotePrekey :: Qualified UserId -> ClientId -> ExceptT ClientError (AppIO r) (Maybe ClientPrekey) claimRemotePrekey quser client = fmapLT ClientFederationError $ Federation.claimPrekey quser client -claimPrekeyBundle :: LegalholdProtectee -> Domain -> UserId -> ExceptT ClientError AppIO PrekeyBundle +claimPrekeyBundle :: LegalholdProtectee -> Domain -> UserId -> ExceptT ClientError (AppIO r) PrekeyBundle claimPrekeyBundle protectee domain uid = do isLocalDomain <- (domain ==) <$> viewFederationDomain if isLocalDomain then claimLocalPrekeyBundle protectee uid else claimRemotePrekeyBundle (Qualified uid domain) -claimLocalPrekeyBundle :: LegalholdProtectee -> UserId -> ExceptT ClientError AppIO PrekeyBundle +claimLocalPrekeyBundle :: LegalholdProtectee -> UserId -> ExceptT ClientError (AppIO r) PrekeyBundle claimLocalPrekeyBundle protectee u = do clients <- map clientId <$> Data.lookupClients u guardLegalhold protectee (mkUserClients [(u, clients)]) PrekeyBundle u . catMaybes <$> lift (mapM (Data.claimPrekey u) clients) -claimRemotePrekeyBundle :: Qualified UserId -> ExceptT ClientError AppIO PrekeyBundle +claimRemotePrekeyBundle :: Qualified UserId -> ExceptT ClientError (AppIO r) PrekeyBundle claimRemotePrekeyBundle quser = do Federation.claimPrekeyBundle quser !>> ClientFederationError -claimMultiPrekeyBundles :: LegalholdProtectee -> QualifiedUserClients -> ExceptT ClientError AppIO QualifiedUserClientPrekeyMap +claimMultiPrekeyBundles :: LegalholdProtectee -> QualifiedUserClients -> ExceptT ClientError (AppIO r) QualifiedUserClientPrekeyMap claimMultiPrekeyBundles protectee quc = do loc <- qualifyLocal () let (locals, remotes) = @@ -232,17 +232,17 @@ claimMultiPrekeyBundles protectee quc = do where claimRemote :: Remote UserClients -> - ExceptT FederationError AppIO (Qualified UserClientPrekeyMap) + ExceptT FederationError (AppIO r) (Qualified UserClientPrekeyMap) claimRemote ruc = qUntagged . qualifyAs ruc <$> Federation.claimMultiPrekeyBundle (tDomain ruc) (tUnqualified ruc) - claimLocal :: Local UserClients -> ExceptT ClientError AppIO (Qualified UserClientPrekeyMap) + claimLocal :: Local UserClients -> ExceptT ClientError (AppIO r) (Qualified UserClientPrekeyMap) claimLocal luc = qUntagged . qualifyAs luc <$> claimLocalMultiPrekeyBundles protectee (tUnqualified luc) -claimLocalMultiPrekeyBundles :: LegalholdProtectee -> UserClients -> ExceptT ClientError AppIO UserClientPrekeyMap +claimLocalMultiPrekeyBundles :: LegalholdProtectee -> UserClients -> ExceptT ClientError (AppIO r) UserClientPrekeyMap claimLocalMultiPrekeyBundles protectee userClients = do guardLegalhold protectee userClients lift @@ -253,13 +253,13 @@ claimLocalMultiPrekeyBundles protectee userClients = do . Message.userClients $ userClients where - getChunk :: Map UserId (Set ClientId) -> AppIO (Map UserId (Map ClientId (Maybe Prekey))) + getChunk :: Map UserId (Set ClientId) -> (AppIO r) (Map UserId (Map ClientId (Maybe Prekey))) getChunk = runConcurrently . Map.traverseWithKey (\u -> Concurrently . getUserKeys u) - getUserKeys :: UserId -> Set ClientId -> AppIO (Map ClientId (Maybe Prekey)) + getUserKeys :: UserId -> Set ClientId -> (AppIO r) (Map ClientId (Maybe Prekey)) getUserKeys u = sequenceA . Map.fromSet (getClientKeys u) - getClientKeys :: UserId -> ClientId -> AppIO (Maybe Prekey) + getClientKeys :: UserId -> ClientId -> (AppIO r) (Maybe Prekey) getClientKeys u c = do key <- fmap prekeyData <$> Data.claimPrekey u c when (isNothing key) $ noPrekeys u c @@ -268,7 +268,7 @@ claimLocalMultiPrekeyBundles protectee userClients = do -- Utilities -- | Perform an orderly deletion of an existing client. -execDelete :: UserId -> Maybe ConnId -> Client -> AppIO () +execDelete :: UserId -> Maybe ConnId -> Client -> (AppIO r) () execDelete u con c = do Intra.rmClient u (clientId c) for_ (clientCookie c) $ \l -> Auth.revokeCookies u [] [l] @@ -280,7 +280,7 @@ execDelete u con c = do -- not exist, since there must be no client without prekeys, -- thus repairing any inconsistencies related to distributed -- (and possibly duplicated) client data. -noPrekeys :: UserId -> ClientId -> AppIO () +noPrekeys :: UserId -> ClientId -> (AppIO r) () noPrekeys u c = do Log.info $ field "user" (toByteString u) @@ -301,7 +301,7 @@ pubClient c = pubClientClass = clientClass c } -legalHoldClientRequested :: UserId -> LegalHoldClientRequest -> AppIO () +legalHoldClientRequested :: UserId -> LegalHoldClientRequest -> (AppIO r) () legalHoldClientRequested targetUser (LegalHoldClientRequest _requester lastPrekey') = Intra.onUserEvent targetUser Nothing lhClientEvent where @@ -312,7 +312,7 @@ legalHoldClientRequested targetUser (LegalHoldClientRequest _requester lastPreke lhClientEvent :: UserEvent lhClientEvent = LegalHoldClientRequested eventData -removeLegalHoldClient :: UserId -> AppIO () +removeLegalHoldClient :: UserId -> (AppIO r) () removeLegalHoldClient uid = do clients <- Data.lookupClients uid -- Should only be one; but just in case we'll treat it as a list diff --git a/services/brig/src/Brig/API/Connection.hs b/services/brig/src/Brig/API/Connection.hs index 9495087c814..2cf4f49372a 100644 --- a/services/brig/src/Brig/API/Connection.hs +++ b/services/brig/src/Brig/API/Connection.hs @@ -59,12 +59,12 @@ import Wire.API.Connection (RelationWithHistory (..)) import Wire.API.ErrorDescription import Wire.API.Routes.Public.Util (ResponseForExistedCreated (..)) -ensureIsActivated :: Local UserId -> MaybeT AppIO () +ensureIsActivated :: Local UserId -> MaybeT (AppIO r) () ensureIsActivated lusr = do active <- lift $ Data.isActivated (tUnqualified lusr) guard active -ensureNotSameTeam :: Local UserId -> Local UserId -> ConnectionM () +ensureNotSameTeam :: Local UserId -> Local UserId -> (ConnectionM r) () ensureNotSameTeam self target = do selfTeam <- lift $ Intra.getTeamId (tUnqualified self) targetTeam <- lift $ Intra.getTeamId (tUnqualified target) @@ -75,7 +75,7 @@ createConnection :: Local UserId -> ConnId -> Qualified UserId -> - ConnectionM (ResponseForExistedCreated UserConnection) + (ConnectionM r) (ResponseForExistedCreated UserConnection) createConnection self con target = do -- basic checks: no need to distinguish between local and remote at this point when (qUntagged self == target) $ @@ -94,7 +94,7 @@ createConnectionToLocalUser :: Local UserId -> ConnId -> Local UserId -> - ConnectionM (ResponseForExistedCreated UserConnection) + (ConnectionM r) (ResponseForExistedCreated UserConnection) createConnectionToLocalUser self conn target = do noteT (InvalidUser (qUntagged target)) $ ensureIsActivated target @@ -109,7 +109,7 @@ createConnectionToLocalUser self conn target = do checkLimit self Created <$> insert Nothing Nothing where - insert :: Maybe UserConnection -> Maybe UserConnection -> ExceptT ConnectionError AppIO UserConnection + insert :: Maybe UserConnection -> Maybe UserConnection -> ExceptT ConnectionError (AppIO r) UserConnection insert s2o o2s = lift $ do Log.info $ logConnection (tUnqualified self) (qUntagged target) @@ -124,7 +124,7 @@ createConnectionToLocalUser self conn target = do mapM_ (Intra.onConnectionEvent (tUnqualified self) (Just conn)) [e2o, e2s] return s2o' - update :: UserConnection -> UserConnection -> ExceptT ConnectionError AppIO (ResponseForExistedCreated UserConnection) + update :: UserConnection -> UserConnection -> ExceptT ConnectionError (AppIO r) (ResponseForExistedCreated UserConnection) update s2o o2s = case (ucStatus s2o, ucStatus o2s) of (MissingLegalholdConsent, _) -> throwE $ InvalidTransition (tUnqualified self) (_, MissingLegalholdConsent) -> throwE $ InvalidTransition (tUnqualified self) @@ -139,7 +139,7 @@ createConnectionToLocalUser self conn target = do (_, Pending) -> resend s2o o2s (_, Cancelled) -> resend s2o o2s - accept :: UserConnection -> UserConnection -> ExceptT ConnectionError AppIO (ResponseForExistedCreated UserConnection) + accept :: UserConnection -> UserConnection -> ExceptT ConnectionError (AppIO r) (ResponseForExistedCreated UserConnection) accept s2o o2s = do when (ucStatus s2o `notElem` [Sent, Accepted]) $ checkLimit self @@ -161,7 +161,7 @@ createConnectionToLocalUser self conn target = do lift $ mapM_ (Intra.onConnectionEvent (tUnqualified self) (Just conn)) [e2o, e2s] return $ Existed s2o' - resend :: UserConnection -> UserConnection -> ExceptT ConnectionError AppIO (ResponseForExistedCreated UserConnection) + resend :: UserConnection -> UserConnection -> ExceptT ConnectionError (AppIO r) (ResponseForExistedCreated UserConnection) resend s2o o2s = do when (ucStatus s2o `notElem` [Sent, Accepted]) $ checkLimit self @@ -171,17 +171,17 @@ createConnectionToLocalUser self conn target = do s2o' <- insert (Just s2o) (Just o2s) return $ Existed s2o' - change :: UserConnection -> RelationWithHistory -> ExceptT ConnectionError AppIO (ResponseForExistedCreated UserConnection) + change :: UserConnection -> RelationWithHistory -> ExceptT ConnectionError (AppIO r) (ResponseForExistedCreated UserConnection) change c s = Existed <$> lift (Data.updateConnection c s) -- | Throw error if one user has a LH device and the other status `no_consent` or vice versa. -- -- FUTUREWORK: we may want to move this to the LH application logic, so we can recycle it for -- group conv creation and possibly other situations. -checkLegalholdPolicyConflict :: UserId -> UserId -> ExceptT ConnectionError AppIO () +checkLegalholdPolicyConflict :: UserId -> UserId -> ExceptT ConnectionError (AppIO r) () checkLegalholdPolicyConflict uid1 uid2 = do let catchProfileNotFound = - -- Does not fit into 'ExceptT', so throw in 'AppIO'. Anyway at the time of writing + -- Does not fit into 'ExceptT', so throw in '(AppIO r)'. Anyway at the time of writing -- this, users are guaranteed to exist when called from 'createConnectionToLocalUser'. maybe (throwM (errorDescriptionTypeToWai @UserNotFound)) return @@ -205,7 +205,7 @@ updateConnection :: Qualified UserId -> Relation -> Maybe ConnId -> - ConnectionM (Maybe UserConnection) + (ConnectionM r) (Maybe UserConnection) updateConnection self other newStatus conn = let doUpdate = foldQualified @@ -228,7 +228,7 @@ updateConnectionToLocalUser :: Relation -> -- | Acting device connection ID Maybe ConnId -> - ConnectionM (Maybe UserConnection) + (ConnectionM r) (Maybe UserConnection) updateConnectionToLocalUser self other newStatus conn = do s2o <- localConnection self other o2s <- localConnection other self @@ -279,7 +279,7 @@ updateConnectionToLocalUser self other newStatus conn = do in Intra.onConnectionEvent (tUnqualified self) conn e2s return s2oUserConn where - accept :: UserConnection -> UserConnection -> ExceptT ConnectionError AppIO (Maybe UserConnection) + accept :: UserConnection -> UserConnection -> ExceptT ConnectionError (AppIO r) (Maybe UserConnection) accept s2o o2s = do checkLimit self Log.info $ @@ -301,7 +301,7 @@ updateConnectionToLocalUser self other newStatus conn = do Intra.onConnectionEvent (tUnqualified self) conn e2o lift $ Just <$> Data.updateConnection s2o AcceptedWithHistory - block :: UserConnection -> ExceptT ConnectionError AppIO (Maybe UserConnection) + block :: UserConnection -> ExceptT ConnectionError (AppIO r) (Maybe UserConnection) block s2o = lift $ do Log.info $ logLocalConnection (tUnqualified self) (qUnqualified (ucTo s2o)) @@ -309,7 +309,7 @@ updateConnectionToLocalUser self other newStatus conn = do traverse_ (Intra.blockConv self conn) (ucConvId s2o) Just <$> Data.updateConnection s2o BlockedWithHistory - unblock :: UserConnection -> UserConnection -> Relation -> ExceptT ConnectionError AppIO (Maybe UserConnection) + unblock :: UserConnection -> UserConnection -> Relation -> ExceptT ConnectionError (AppIO r) (Maybe UserConnection) unblock s2o o2s new = do -- FUTUREWORK: new is always in [Sent, Accepted]. Refactor to total function. when (new `elem` [Sent, Accepted]) $ @@ -330,7 +330,7 @@ updateConnectionToLocalUser self other newStatus conn = do Intra.onConnectionEvent (tUnqualified self) conn e2o lift $ Just <$> Data.updateConnection s2o (mkRelationWithHistory (error "impossible") new) - cancel :: UserConnection -> UserConnection -> ExceptT ConnectionError AppIO (Maybe UserConnection) + cancel :: UserConnection -> UserConnection -> ExceptT ConnectionError (AppIO r) (Maybe UserConnection) cancel s2o o2s = do Log.info $ logLocalConnection (tUnqualified self) (qUnqualified (ucTo s2o)) @@ -342,7 +342,7 @@ updateConnectionToLocalUser self other newStatus conn = do lift $ Intra.onConnectionEvent (tUnqualified self) conn e2o change s2o Cancelled - change :: UserConnection -> Relation -> ExceptT ConnectionError AppIO (Maybe UserConnection) + change :: UserConnection -> Relation -> ExceptT ConnectionError (AppIO r) (Maybe UserConnection) change c s = do -- FUTUREWORK: refactor to total function. Gets only called with either Ignored, Accepted, Cancelled lift $ Just <$> Data.updateConnection c (mkRelationWithHistory (error "impossible") s) @@ -350,7 +350,7 @@ updateConnectionToLocalUser self other newStatus conn = do localConnection :: Local UserId -> Local UserId -> - ExceptT ConnectionError AppIO UserConnection + ExceptT ConnectionError (AppIO r) UserConnection localConnection la lb = do lift (Data.lookupConnection la (qUntagged lb)) >>= tryJust (NotConnected (tUnqualified la) (qUntagged lb)) @@ -374,8 +374,9 @@ mkRelationWithHistory oldRel = \case MissingLegalholdConsent -> error "impossible old relation" updateConnectionInternal :: + forall r. UpdateConnectionsInternal -> - ExceptT ConnectionError AppIO () + ExceptT ConnectionError (AppIO r) () updateConnectionInternal = \case BlockForMissingLHConsent uid others -> do self <- qualifyLocal uid @@ -391,7 +392,7 @@ updateConnectionInternal = \case other where -- inspired by @block@ in 'updateConnection'. - blockForMissingLegalholdConsent :: Local UserId -> [UserId] -> ExceptT ConnectionError AppIO () + blockForMissingLegalholdConsent :: Local UserId -> [UserId] -> ExceptT ConnectionError (AppIO r) () blockForMissingLegalholdConsent self others = do for_ others $ \(qualifyAs self -> other) -> do Log.info $ @@ -407,7 +408,7 @@ updateConnectionInternal = \case let ev = ConnectionUpdated uconn' (Just $ ucStatus uconn) Nothing Intra.onConnectionEvent (tUnqualified self) Nothing ev - removeLHBlocksInvolving :: Local UserId -> ExceptT ConnectionError AppIO () + removeLHBlocksInvolving :: Local UserId -> ExceptT ConnectionError (AppIO r) () removeLHBlocksInvolving self = iterateConnections self (toRange (Proxy @500)) $ \conns -> do for_ conns $ \s2o -> @@ -422,10 +423,10 @@ updateConnectionInternal = \case unblockDirected s2o o2s unblockDirected o2s s2o where - iterateConnections :: Local UserId -> Range 1 500 Int32 -> ([UserConnection] -> ExceptT ConnectionError AppIO ()) -> ExceptT ConnectionError AppIO () + iterateConnections :: Local UserId -> Range 1 500 Int32 -> ([UserConnection] -> ExceptT ConnectionError (AppIO r) ()) -> ExceptT ConnectionError (AppIO r) () iterateConnections user pageSize handleConns = go Nothing where - go :: Maybe UserId -> ExceptT ConnectionError (AppT IO) () + go :: Maybe UserId -> ExceptT ConnectionError (AppT r IO) () go mbStart = do page <- lift $ Data.lookupLocalConnections user mbStart pageSize handleConns (resultList page) @@ -436,7 +437,7 @@ updateConnectionInternal = \case else pure () [] -> pure () - unblockDirected :: UserConnection -> UserConnection -> ExceptT ConnectionError AppIO () + unblockDirected :: UserConnection -> UserConnection -> ExceptT ConnectionError (AppIO r) () unblockDirected uconn uconnRev = do lfrom <- qualifyLocal (ucFrom uconnRev) void . lift . for (ucConvId uconn) $ Intra.unblockConv lfrom Nothing @@ -451,7 +452,7 @@ updateConnectionInternal = \case } lift $ Intra.onConnectionEvent (ucFrom uconn) Nothing connEvent - relationWithHistory :: Local UserId -> Qualified UserId -> ExceptT ConnectionError AppIO RelationWithHistory + relationWithHistory :: Local UserId -> Qualified UserId -> ExceptT ConnectionError (AppIO r) RelationWithHistory relationWithHistory self target = lift (Data.lookupRelationWithHistory self target) >>= tryJust (NotConnected (tUnqualified self) target) @@ -473,18 +474,18 @@ updateConnectionInternal = \case SentWithHistory -> SentWithHistory CancelledWithHistory -> CancelledWithHistory -createLocalConnectionUnchecked :: Local UserId -> Local UserId -> AppIO () +createLocalConnectionUnchecked :: Local UserId -> Local UserId -> (AppIO r) () createLocalConnectionUnchecked self other = do qcnv <- liftIO $ qUntagged . qualifyAs self <$> (Id <$> UUID.nextRandom) void $ Data.insertConnection self (qUntagged other) AcceptedWithHistory qcnv void $ Data.insertConnection other (qUntagged self) AcceptedWithHistory qcnv -createRemoteConnectionUnchecked :: Local UserId -> Remote UserId -> AppIO () +createRemoteConnectionUnchecked :: Local UserId -> Remote UserId -> (AppIO r) () createRemoteConnectionUnchecked self other = do qcnv <- liftIO $ qUntagged . qualifyAs self <$> (Id <$> UUID.nextRandom) void $ Data.insertConnection self (qUntagged other) AcceptedWithHistory qcnv -lookupConnections :: UserId -> Maybe UserId -> Range 1 500 Int32 -> AppIO UserConnectionList +lookupConnections :: UserId -> Maybe UserId -> Range 1 500 Int32 -> (AppIO r) UserConnectionList lookupConnections from start size = do lusr <- qualifyLocal from rs <- Data.lookupLocalConnections lusr start size diff --git a/services/brig/src/Brig/API/Connection/Remote.hs b/services/brig/src/Brig/API/Connection/Remote.hs index 1e8190f203e..a36cc48ab33 100644 --- a/services/brig/src/Brig/API/Connection/Remote.hs +++ b/services/brig/src/Brig/API/Connection/Remote.hs @@ -108,7 +108,7 @@ updateOne2OneConv :: Maybe (Qualified ConvId) -> Relation -> Actor -> - AppIO (Qualified ConvId) + (AppIO r) (Qualified ConvId) updateOne2OneConv lUsr _mbConn remoteUser mbConvId rel actor = do let request = UpsertOne2OneConversationRequest @@ -148,7 +148,7 @@ transitionTo :: Maybe UserConnection -> Maybe Relation -> Actor -> - ConnectionM (ResponseForExistedCreated UserConnection, Bool) + (ConnectionM r) (ResponseForExistedCreated UserConnection, Bool) transitionTo self _ _ Nothing Nothing _ = -- This can only happen if someone tries to ignore as a first action on a -- connection. This shouldn't be possible. @@ -181,7 +181,7 @@ transitionTo self mzcon other (Just connection) (Just rel) actor = lift $ do pure (Existed connection', True) -- | Send an event to the local user when the state of a connection changes. -pushEvent :: Local UserId -> Maybe ConnId -> UserConnection -> AppIO () +pushEvent :: Local UserId -> Maybe ConnId -> UserConnection -> (AppIO r) () pushEvent self mzcon connection = do let event = ConnectionUpdated connection Nothing Nothing Intra.onConnectionEvent (tUnqualified self) mzcon event @@ -192,7 +192,7 @@ performLocalAction :: Remote UserId -> Maybe UserConnection -> LocalConnectionAction -> - ConnectionM (ResponseForExistedCreated UserConnection, Bool) + (ConnectionM r) (ResponseForExistedCreated UserConnection, Bool) performLocalAction self mzcon other mconnection action = do let rel0 = maybe Cancelled ucStatus mconnection checkLimitForLocalAction self rel0 action @@ -238,7 +238,7 @@ performRemoteAction :: Remote UserId -> Maybe UserConnection -> RemoteConnectionAction -> - AppIO (Maybe RemoteConnectionAction) + (AppIO r) (Maybe RemoteConnectionAction) performRemoteAction self other mconnection action = do let rel0 = maybe Cancelled ucStatus mconnection let rel1 = transition (RCA action) rel0 @@ -254,7 +254,7 @@ createConnectionToRemoteUser :: Local UserId -> ConnId -> Remote UserId -> - ConnectionM (ResponseForExistedCreated UserConnection) + (ConnectionM r) (ResponseForExistedCreated UserConnection) createConnectionToRemoteUser self zcon other = do mconnection <- lift $ Data.lookupConnection self (qUntagged other) fst <$> performLocalAction self (Just zcon) other mconnection LocalConnect @@ -264,7 +264,7 @@ updateConnectionToRemoteUser :: Remote UserId -> Relation -> Maybe ConnId -> - ConnectionM (Maybe UserConnection) + (ConnectionM r) (Maybe UserConnection) updateConnectionToRemoteUser self other rel1 zcon = do mconnection <- lift $ Data.lookupConnection self (qUntagged other) action <- @@ -281,7 +281,7 @@ updateConnectionToRemoteUser self other rel1 zcon = do actionForTransition Pending = Nothing actionForTransition MissingLegalholdConsent = Nothing -checkLimitForLocalAction :: Local UserId -> Relation -> LocalConnectionAction -> ConnectionM () +checkLimitForLocalAction :: Local UserId -> Relation -> LocalConnectionAction -> (ConnectionM r) () checkLimitForLocalAction u oldRel action = when (oldRel `notElem` [Accepted, Sent] && (action == LocalConnect)) $ checkLimit u diff --git a/services/brig/src/Brig/API/Connection/Util.hs b/services/brig/src/Brig/API/Connection/Util.hs index b4730b0eaaa..49051bbd56b 100644 --- a/services/brig/src/Brig/API/Connection/Util.hs +++ b/services/brig/src/Brig/API/Connection/Util.hs @@ -33,11 +33,11 @@ import Data.Qualified (Local, tUnqualified) import Imports import Wire.API.Connection (Relation (..)) -type ConnectionM = ExceptT ConnectionError AppIO +type ConnectionM r = ExceptT ConnectionError (AppIO r) -- Helpers -checkLimit :: Local UserId -> ExceptT ConnectionError AppIO () +checkLimit :: Local UserId -> ExceptT ConnectionError (AppIO r) () checkLimit u = noteT (TooManyConnections (tUnqualified u)) $ do n <- lift $ Data.countConnections u [Accepted, Sent] l <- setUserMaxConnections <$> view settings diff --git a/services/brig/src/Brig/API/Error.hs b/services/brig/src/Brig/API/Error.hs index 58d92463152..b5830fe6657 100644 --- a/services/brig/src/Brig/API/Error.hs +++ b/services/brig/src/Brig/API/Error.hs @@ -39,6 +39,7 @@ import qualified Network.Wai.Utilities.Error as Wai import Servant.API.Status import Wire.API.ErrorDescription import Wire.API.Federation.Error +import Wire.API.User (ChangeHandleError (..), UpdateProfileError (..)) errorDescriptionToWai :: forall (code :: Nat) (lbl :: Symbol) (desc :: Symbol). @@ -110,18 +111,18 @@ connError InvalidTransition {} = StdError (errorDescriptionTypeToWai @InvalidTra connError NotConnected {} = StdError (errorDescriptionTypeToWai @NotConnected) connError InvalidUser {} = StdError (errorDescriptionTypeToWai @InvalidUser) connError ConnectNoIdentity {} = StdError (errorDescriptionToWai (noIdentity 0)) -connError (ConnectBlacklistedUserKey k) = StdError $ foldKey (const blacklistedEmail) (const blacklistedPhone) k +connError (ConnectBlacklistedUserKey k) = StdError $ foldKey (const blacklistedEmail) (const (errorDescriptionTypeToWai @BlacklistedPhone)) k connError (ConnectInvalidEmail _ _) = StdError invalidEmail -connError ConnectInvalidPhone {} = StdError invalidPhone +connError ConnectInvalidPhone {} = StdError (errorDescriptionTypeToWai @InvalidPhone) connError ConnectSameBindingTeamUsers = StdError sameBindingTeamUsers connError ConnectMissingLegalholdConsent = StdError (errorDescriptionTypeToWai @MissingLegalholdConsent) connError (ConnectFederationError e) = fedError e actError :: ActivationError -> Error -actError (UserKeyExists _) = StdError userKeyExists +actError (UserKeyExists _) = StdError (errorDescriptionTypeToWai @UserKeyExists) actError (InvalidActivationCode e) = StdError (invalidActivationCode e) actError (InvalidActivationEmail _ _) = StdError invalidEmail -actError (InvalidActivationPhone _) = StdError invalidPhone +actError (InvalidActivationPhone _) = StdError (errorDescriptionTypeToWai @InvalidPhone) pwResetError :: PasswordResetError -> Error pwResetError InvalidPasswordResetKey = StdError invalidPwResetKey @@ -138,45 +139,35 @@ newUserError :: CreateUserError -> Error newUserError InvalidInvitationCode = StdError invalidInvitationCode newUserError MissingIdentity = StdError missingIdentity newUserError (InvalidEmail _ _) = StdError invalidEmail -newUserError (InvalidPhone _) = StdError invalidPhone -newUserError (DuplicateUserKey _) = StdError userKeyExists +newUserError (InvalidPhone _) = StdError (errorDescriptionTypeToWai @InvalidPhone) +newUserError (DuplicateUserKey _) = StdError (errorDescriptionTypeToWai @UserKeyExists) newUserError (EmailActivationError e) = actError e newUserError (PhoneActivationError e) = actError e -newUserError (BlacklistedUserKey k) = StdError $ foldKey (const blacklistedEmail) (const blacklistedPhone) k +newUserError (BlacklistedUserKey k) = StdError $ foldKey (const blacklistedEmail) (const (errorDescriptionTypeToWai @BlacklistedPhone)) k newUserError TooManyTeamMembers = StdError tooManyTeamMembers newUserError UserCreationRestricted = StdError userCreationRestricted newUserError (ExternalPreconditionFailed e) = StdError e sendLoginCodeError :: SendLoginCodeError -> Error -sendLoginCodeError (SendLoginInvalidPhone _) = StdError invalidPhone +sendLoginCodeError (SendLoginInvalidPhone _) = StdError (errorDescriptionTypeToWai @InvalidPhone) sendLoginCodeError SendLoginPasswordExists = StdError passwordExists sendActCodeError :: SendActivationCodeError -> Error -sendActCodeError (InvalidRecipient k) = StdError $ foldKey (const invalidEmail) (const invalidPhone) k -sendActCodeError (UserKeyInUse _) = StdError userKeyExists -sendActCodeError (ActivationBlacklistedUserKey k) = StdError $ foldKey (const blacklistedEmail) (const blacklistedPhone) k +sendActCodeError (InvalidRecipient k) = StdError $ foldKey (const invalidEmail) (const (errorDescriptionTypeToWai @InvalidPhone)) k +sendActCodeError (UserKeyInUse _) = StdError (errorDescriptionTypeToWai @UserKeyExists) +sendActCodeError (ActivationBlacklistedUserKey k) = StdError $ foldKey (const blacklistedEmail) (const (errorDescriptionTypeToWai @BlacklistedPhone)) k changeEmailError :: ChangeEmailError -> Error changeEmailError (InvalidNewEmail _ _) = StdError invalidEmail -changeEmailError (EmailExists _) = StdError userKeyExists +changeEmailError (EmailExists _) = StdError (errorDescriptionTypeToWai @UserKeyExists) changeEmailError (ChangeBlacklistedEmail _) = StdError blacklistedEmail changeEmailError EmailManagedByScim = StdError $ propertyManagedByScim "email" -changePhoneError :: ChangePhoneError -> Error -changePhoneError (InvalidNewPhone _) = StdError invalidPhone -changePhoneError (PhoneExists _) = StdError userKeyExists -changePhoneError (BlacklistedNewPhone _) = StdError blacklistedPhone - -changePwError :: ChangePasswordError -> Error -changePwError InvalidCurrentPassword = StdError (errorDescriptionTypeToWai @BadCredentials) -changePwError ChangePasswordNoIdentity = StdError (errorDescriptionToWai (noIdentity 1)) -changePwError ChangePasswordMustDiffer = StdError changePasswordMustDiffer - changeHandleError :: ChangeHandleError -> Error changeHandleError ChangeHandleNoIdentity = StdError (errorDescriptionToWai (noIdentity 2)) -changeHandleError ChangeHandleExists = StdError handleExists -changeHandleError ChangeHandleInvalid = StdError invalidHandle -changeHandleError ChangeHandleManagedByScim = StdError $ propertyManagedByScim "handle" +changeHandleError ChangeHandleExists = StdError (errorDescriptionToWai (mkErrorDescription :: HandleExists)) +changeHandleError ChangeHandleInvalid = StdError (errorDescriptionToWai (mkErrorDescription :: InvalidHandle)) +changeHandleError ChangeHandleManagedByScim = StdError (errorDescriptionToWai (mkErrorDescription :: HandleManagedByScim)) legalHoldLoginError :: LegalHoldLoginError -> Error legalHoldLoginError LegalHoldLoginNoBindingTeam = StdError noBindingTeam @@ -230,11 +221,6 @@ clientError ClientMissingLegalholdConsent = StdError (errorDescriptionTypeToWai fedError :: FederationError -> Error fedError = StdError . federationErrorToWai -idtError :: RemoveIdentityError -> Error -idtError LastIdentity = StdError lastIdentity -idtError NoPassword = StdError noPassword -idtError NoIdentity = StdError (errorDescriptionToWai (noIdentity 3)) - propDataError :: PropertiesDataError -> Error propDataError TooManyProperties = StdError tooManyProperties @@ -256,13 +242,13 @@ accountStatusError :: AccountStatusError -> Error accountStatusError InvalidAccountStatus = StdError invalidAccountStatus phoneError :: PhoneException -> Error -phoneError PhoneNumberUnreachable = StdError invalidPhone -phoneError PhoneNumberBarred = StdError blacklistedPhone +phoneError PhoneNumberUnreachable = StdError (errorDescriptionTypeToWai @InvalidPhone) +phoneError PhoneNumberBarred = StdError (errorDescriptionTypeToWai @BlacklistedPhone) phoneError (PhoneBudgetExhausted t) = RichError phoneBudgetExhausted (PhoneBudgetTimeout t) [] updateProfileError :: UpdateProfileError -> Error updateProfileError DisplayNameManagedByScim = StdError (propertyManagedByScim "name") -updateProfileError (ProfileNotFound _) = StdError (errorDescriptionTypeToWai @UserNotFound) +updateProfileError ProfileNotFound = StdError (errorDescriptionTypeToWai @UserNotFound) -- WAI Errors ----------------------------------------------------------------- @@ -281,12 +267,6 @@ clientCapabilitiesCannotBeRemoved = Wai.mkError status409 "client-capabilities-c noEmail :: Wai.Error noEmail = Wai.mkError status403 "no-email" "This operation requires the user to have a verified email address." -lastIdentity :: Wai.Error -lastIdentity = Wai.mkError status403 "last-identity" "The last user identity (email or phone number) cannot be removed." - -noPassword :: Wai.Error -noPassword = Wai.mkError status403 "no-password" "The user has no password." - invalidEmail :: Wai.Error invalidEmail = Wai.mkError status400 "invalid-email" "Invalid e-mail address." @@ -296,12 +276,6 @@ invalidPwResetKey = Wai.mkError status400 "invalid-key" "Invalid email or mobile resetPasswordMustDiffer :: Wai.Error resetPasswordMustDiffer = Wai.mkError status409 "password-must-differ" "For password reset, new and old password must be different." -changePasswordMustDiffer :: Wai.Error -changePasswordMustDiffer = Wai.mkError status409 "password-must-differ" "For password change, new and old password must be different." - -invalidPhone :: Wai.Error -invalidPhone = Wai.mkError status400 "invalid-phone" "Invalid mobile phone number." - invalidInvitationCode :: Wai.Error invalidInvitationCode = Wai.mkError status400 "invalid-invitation-code" "Invalid invitation code." @@ -314,21 +288,12 @@ invalidPwResetCode = Wai.mkError status400 "invalid-code" "Invalid password rese duplicatePwResetCode :: Wai.Error duplicatePwResetCode = Wai.mkError status409 "code-exists" "A password reset is already in progress." -userKeyExists :: Wai.Error -userKeyExists = Wai.mkError status409 "key-exists" "The given e-mail address or phone number is in use." - emailExists :: Wai.Error emailExists = Wai.mkError status409 "email-exists" "The given e-mail address is in use." phoneExists :: Wai.Error phoneExists = Wai.mkError status409 "phone-exists" "The given phone number is in use." -handleExists :: Wai.Error -handleExists = Wai.mkError status409 "handle-exists" "The given handle is already taken." - -invalidHandle :: Wai.Error -invalidHandle = Wai.mkError status400 "invalid-handle" "The given handle is invalid." - badRequest :: LText -> Wai.Error badRequest = Wai.mkError status400 "bad-request" @@ -379,14 +344,6 @@ blacklistedEmail = "The given e-mail address has been blacklisted due to a permanent bounce \ \or a complaint." -blacklistedPhone :: Wai.Error -blacklistedPhone = - Wai.mkError - status403 - "blacklisted-phone" - "The given phone number has been blacklisted due to suspected abuse \ - \or a complaint." - passwordExists :: Wai.Error passwordExists = Wai.mkError @@ -416,9 +373,6 @@ authMissingToken = Wai.mkError status403 "invalid-credentials" "Missing token" authMissingCookieAndToken :: Wai.Error authMissingCookieAndToken = Wai.mkError status403 "invalid-credentials" "Missing cookie and token" -invalidUserToken :: Wai.Error -invalidUserToken = Wai.mkError status403 "invalid-credentials" "Invalid user token" - invalidAccessToken :: Wai.Error invalidAccessToken = Wai.mkError status403 "invalid-credentials" "Invalid access token" @@ -437,9 +391,6 @@ authTokenInvalid = Wai.mkError status403 "invalid-credentials" "Invalid token" authTokenUnsupported :: Wai.Error authTokenUnsupported = Wai.mkError status403 "invalid-credentials" "Unsupported token operation for this token type" -incorrectPermissions :: Wai.Error -incorrectPermissions = Wai.mkError status403 "invalid-permissions" "Copy permissions must be a subset of self permissions" - -- | User's relation to the team is not what we expect it to be. Examples: -- -- * Requested action requires the user to be a team member, but the user doesn't belong to diff --git a/services/brig/src/Brig/API/Federation.hs b/services/brig/src/Brig/API/Federation.hs index 5375a174217..d18f6e37ba8 100644 --- a/services/brig/src/Brig/API/Federation.hs +++ b/services/brig/src/Brig/API/Federation.hs @@ -31,6 +31,7 @@ import Brig.IO.Intra (notify) import Brig.Types (PrekeyBundle, Relation (Accepted)) import Brig.Types.User.Event import Brig.User.API.Handle +import qualified Brig.User.Search.SearchIndex as Q import Data.Domain import Data.Handle (Handle (..), parseHandle) import Data.Id (ClientId, UserId) @@ -58,7 +59,7 @@ import Wire.API.UserMap (UserMap) type FederationAPI = "federation" :> BrigApi -federationSitemap :: ServerT FederationAPI Handler +federationSitemap :: ServerT FederationAPI (Handler r) federationSitemap = Named @"get-user-by-handle" getUserByHandle :<|> Named @"get-users-by-ids" getUsersByIds @@ -70,7 +71,7 @@ federationSitemap = :<|> Named @"send-connection-action" sendConnectionAction :<|> Named @"on-user-deleted-connections" onUserDeleted -sendConnectionAction :: Domain -> NewConnectionRequest -> Handler NewConnectionResponse +sendConnectionAction :: Domain -> NewConnectionRequest -> (Handler r) NewConnectionResponse sendConnectionAction originDomain NewConnectionRequest {..} = do active <- lift $ Data.isActivated ncrTo if active @@ -82,7 +83,7 @@ sendConnectionAction originDomain NewConnectionRequest {..} = do pure $ NewConnectionResponseOk maction else pure NewConnectionResponseUserNotActivated -getUserByHandle :: Domain -> Handle -> Handler (Maybe UserProfile) +getUserByHandle :: Domain -> Handle -> (Handler r) (Maybe UserProfile) getUserByHandle _ handle = lift $ do maybeOwnerId <- API.lookupHandle handle case maybeOwnerId of @@ -91,36 +92,52 @@ getUserByHandle _ handle = lift $ do Just ownerId -> listToMaybe <$> API.lookupLocalProfiles Nothing [ownerId] -getUsersByIds :: Domain -> [UserId] -> Handler [UserProfile] +getUsersByIds :: Domain -> [UserId] -> (Handler r) [UserProfile] getUsersByIds _ uids = lift (API.lookupLocalProfiles Nothing uids) -claimPrekey :: Domain -> (UserId, ClientId) -> Handler (Maybe ClientPrekey) +claimPrekey :: Domain -> (UserId, ClientId) -> (Handler r) (Maybe ClientPrekey) claimPrekey _ (user, client) = do API.claimLocalPrekey LegalholdPlusFederationNotImplemented user client !>> clientError -claimPrekeyBundle :: Domain -> UserId -> Handler PrekeyBundle +claimPrekeyBundle :: Domain -> UserId -> (Handler r) PrekeyBundle claimPrekeyBundle _ user = API.claimLocalPrekeyBundle LegalholdPlusFederationNotImplemented user !>> clientError -claimMultiPrekeyBundle :: Domain -> UserClients -> Handler UserClientPrekeyMap +claimMultiPrekeyBundle :: Domain -> UserClients -> (Handler r) UserClientPrekeyMap claimMultiPrekeyBundle _ uc = API.claimLocalMultiPrekeyBundles LegalholdPlusFederationNotImplemented uc !>> clientError -- | Searching for federated users on a remote backend should -- only search by exact handle search, not in elasticsearch. -- (This decision may change in the future) -searchUsers :: Domain -> SearchRequest -> Handler [Contact] +searchUsers :: Domain -> SearchRequest -> (Handler r) [Contact] searchUsers _ (SearchRequest searchTerm) = do - let maybeHandle = parseHandle searchTerm - maybeOwnerId <- maybe (pure Nothing) (lift . API.lookupHandle) maybeHandle - case maybeOwnerId of - Nothing -> pure [] - Just foundUser -> lift $ contactFromProfile <$$> API.lookupLocalProfiles Nothing [foundUser] + let maxResults = 15 + + maybeExactHandleMatch <- exactHandleSearch + + let exactHandleMatchCount = length maybeExactHandleMatch + esMaxResults = maxResults - exactHandleMatchCount + + esResult <- + if esMaxResults > 0 + then Q.searchIndex Nothing Nothing searchTerm esMaxResults + else pure $ SearchResult 0 0 0 [] + + pure $ maybeToList maybeExactHandleMatch <> searchResults esResult + where + exactHandleSearch :: (Handler r) (Maybe Contact) + exactHandleSearch = do + let maybeHandle = parseHandle searchTerm + maybeOwnerId <- maybe (pure Nothing) (lift . API.lookupHandle) maybeHandle + case maybeOwnerId of + Nothing -> pure Nothing + Just foundUser -> lift $ fmap listToMaybe $ contactFromProfile <$$> API.lookupLocalProfiles Nothing [foundUser] -getUserClients :: Domain -> GetUserClients -> Handler (UserMap (Set PubClient)) +getUserClients :: Domain -> GetUserClients -> (Handler r) (UserMap (Set PubClient)) getUserClients _ (GetUserClients uids) = API.lookupLocalPubClientsBulk uids !>> clientError -onUserDeleted :: Domain -> UserDeletedConnectionsNotification -> Handler EmptyResponse +onUserDeleted :: Domain -> UserDeletedConnectionsNotification -> (Handler r) EmptyResponse onUserDeleted origDomain udcn = lift $ do let deletedUser = toRemoteUnsafe origDomain (udcnUser udcn) connections = udcnConnections udcn diff --git a/services/brig/src/Brig/API/Handler.hs b/services/brig/src/Brig/API/Handler.hs index b88d1efdf80..967e2afefb3 100644 --- a/services/brig/src/Brig/API/Handler.hs +++ b/services/brig/src/Brig/API/Handler.hs @@ -64,15 +64,15 @@ import System.Logger.Class (Logger) ------------------------------------------------------------------------------- -- HTTP Handler Monad -type Handler = ExceptT Error AppIO +type Handler r = ExceptT Error (AppIO r) -runHandler :: Env -> Request -> Handler ResponseReceived -> Continue IO -> IO ResponseReceived +runHandler :: Env -> Request -> (Handler r) ResponseReceived -> Continue IO -> IO ResponseReceived runHandler e r h k = do let e' = set requestId (maybe def RequestId (lookupRequestId r)) e a <- runAppT e' (runExceptT h) `catches` brigErrorHandlers either (onError (view applog e') r k) return a -toServantHandler :: Env -> Handler a -> Servant.Handler a +toServantHandler :: Env -> (Handler r) a -> Servant.Handler a toServantHandler env action = do a <- liftIO $ runAppT env (runExceptT action) `catches` brigErrorHandlers case a of @@ -135,11 +135,11 @@ type JSON = Media "application" "json" -- TODO: move to libs/wai-utilities? there is a parseJson' in "Network.Wai.Utilities.Request", -- but adjusting its signature to this here would require to move more code out of brig (at least -- badRequest and probably all the other errors). -parseJsonBody :: FromJSON a => JsonRequest a -> Handler a +parseJsonBody :: FromJSON a => JsonRequest a -> (Handler r) a parseJsonBody req = parseBody req !>> StdError . badRequest -- | If a whitelist is configured, consult it, otherwise a no-op. {#RefActivationWhitelist} -checkWhitelist :: Either Email Phone -> Handler () +checkWhitelist :: Either Email Phone -> (Handler r) () checkWhitelist key = do eb <- setWhitelist <$> view settings case eb of diff --git a/services/brig/src/Brig/API/Internal.hs b/services/brig/src/Brig/API/Internal.hs index 0f9e108ceb4..7e21804bf7a 100644 --- a/services/brig/src/Brig/API/Internal.hs +++ b/services/brig/src/Brig/API/Internal.hs @@ -81,7 +81,7 @@ import Wire.API.User.RichInfo --------------------------------------------------------------------------- -- Sitemap (servant) -servantSitemap :: ServerT BrigIRoutes.API Handler +servantSitemap :: ServerT BrigIRoutes.API (Handler r) servantSitemap = Brig.User.EJPD.ejpdRequest :<|> getAccountFeatureConfig @@ -91,16 +91,16 @@ servantSitemap = :<|> getConnectionsStatus -- | Responds with 'Nothing' if field is NULL in existing user or user does not exist. -getAccountFeatureConfig :: UserId -> Handler ApiFt.TeamFeatureStatusNoConfig +getAccountFeatureConfig :: UserId -> (Handler r) ApiFt.TeamFeatureStatusNoConfig getAccountFeatureConfig uid = lift (Data.lookupFeatureConferenceCalling uid) >>= maybe (view (settings . getAfcConferenceCallingDefNull)) pure -putAccountFeatureConfig :: UserId -> ApiFt.TeamFeatureStatusNoConfig -> Handler NoContent +putAccountFeatureConfig :: UserId -> ApiFt.TeamFeatureStatusNoConfig -> (Handler r) NoContent putAccountFeatureConfig uid status = lift $ Data.updateFeatureConferenceCalling uid (Just status) $> NoContent -deleteAccountFeatureConfig :: UserId -> Handler NoContent +deleteAccountFeatureConfig :: UserId -> (Handler r) NoContent deleteAccountFeatureConfig uid = lift $ Data.updateFeatureConferenceCalling uid Nothing $> NoContent @@ -110,7 +110,7 @@ swaggerDocsAPI = swaggerSchemaUIServer BrigIRoutes.swaggerDoc --------------------------------------------------------------------------- -- Sitemap (wai-route) -sitemap :: Routes a Handler () +sitemap :: Routes a (Handler r) () sitemap = do get "/i/status" (continue $ const $ return empty) true head "/i/status" (continue $ const $ return empty) true @@ -279,44 +279,44 @@ sitemap = do -- Handlers -- | Add a client without authentication checks -addClientInternalH :: UserId ::: JsonRequest NewClient ::: Maybe ConnId ::: JSON -> Handler Response +addClientInternalH :: UserId ::: JsonRequest NewClient ::: Maybe ConnId ::: JSON -> (Handler r) Response addClientInternalH (usr ::: req ::: connId ::: _) = do new <- parseJsonBody req setStatus status201 . json <$> addClientInternal usr new connId -addClientInternal :: UserId -> NewClient -> Maybe ConnId -> Handler Client +addClientInternal :: UserId -> NewClient -> Maybe ConnId -> (Handler r) Client addClientInternal usr new connId = do API.addClient usr connId Nothing new !>> clientError -legalHoldClientRequestedH :: UserId ::: JsonRequest LegalHoldClientRequest ::: JSON -> Handler Response +legalHoldClientRequestedH :: UserId ::: JsonRequest LegalHoldClientRequest ::: JSON -> (Handler r) Response legalHoldClientRequestedH (targetUser ::: req ::: _) = do clientRequest <- parseJsonBody req lift $ API.legalHoldClientRequested targetUser clientRequest return $ setStatus status200 empty -removeLegalHoldClientH :: UserId ::: JSON -> Handler Response +removeLegalHoldClientH :: UserId ::: JSON -> (Handler r) Response removeLegalHoldClientH (uid ::: _) = do lift $ API.removeLegalHoldClient uid return $ setStatus status200 empty -internalListClientsH :: JSON ::: JsonRequest UserSet -> Handler Response +internalListClientsH :: JSON ::: JsonRequest UserSet -> (Handler r) Response internalListClientsH (_ ::: req) = do json <$> (lift . internalListClients =<< parseJsonBody req) -internalListClients :: UserSet -> AppIO UserClients +internalListClients :: UserSet -> (AppIO r) UserClients internalListClients (UserSet usrs) = do UserClients . Map.fromList <$> API.lookupUsersClientIds (Set.toList usrs) -internalListFullClientsH :: JSON ::: JsonRequest UserSet -> Handler Response +internalListFullClientsH :: JSON ::: JsonRequest UserSet -> (Handler r) Response internalListFullClientsH (_ ::: req) = json <$> (lift . internalListFullClients =<< parseJsonBody req) -internalListFullClients :: UserSet -> AppIO UserClientsFull +internalListFullClients :: UserSet -> (AppIO r) UserClientsFull internalListFullClients (UserSet usrs) = UserClientsFull <$> Data.lookupClientsBulk (Set.toList usrs) -createUserNoVerifyH :: JSON ::: JsonRequest NewUser -> Handler Response +createUserNoVerifyH :: JSON ::: JsonRequest NewUser -> (Handler r) Response createUserNoVerifyH (_ ::: req) = do CreateUserNoVerifyResponse uid prof <- createUserNoVerify =<< parseJsonBody req return . setStatus status201 @@ -325,7 +325,7 @@ createUserNoVerifyH (_ ::: req) = do data CreateUserNoVerifyResponse = CreateUserNoVerifyResponse UserId SelfProfile -createUserNoVerify :: NewUser -> Handler CreateUserNoVerifyResponse +createUserNoVerify :: NewUser -> (Handler r) CreateUserNoVerifyResponse createUserNoVerify uData = do result <- API.createUser uData !>> newUserError let acc = createdAccount result @@ -339,18 +339,18 @@ createUserNoVerify uData = do in API.activate key code (Just uid) !>> actError return $ CreateUserNoVerifyResponse uid (SelfProfile usr) -deleteUserNoVerifyH :: UserId -> Handler Response +deleteUserNoVerifyH :: UserId -> (Handler r) Response deleteUserNoVerifyH uid = do setStatus status202 empty <$ deleteUserNoVerify uid -deleteUserNoVerify :: UserId -> Handler () +deleteUserNoVerify :: UserId -> (Handler r) () deleteUserNoVerify uid = do void $ lift (API.lookupAccount uid) >>= ifNothing (errorDescriptionTypeToWai @UserNotFound) lift $ API.deleteUserNoVerify uid -changeSelfEmailMaybeSendH :: UserId ::: Bool ::: JsonRequest EmailUpdate -> Handler Response +changeSelfEmailMaybeSendH :: UserId ::: Bool ::: JsonRequest EmailUpdate -> (Handler r) Response changeSelfEmailMaybeSendH (u ::: validate ::: req) = do email <- euEmail <$> parseJsonBody req changeSelfEmailMaybeSend u (if validate then ActuallySendEmail else DoNotSendEmail) email API.AllowSCIMUpdates >>= \case @@ -359,7 +359,7 @@ changeSelfEmailMaybeSendH (u ::: validate ::: req) = do data MaybeSendEmail = ActuallySendEmail | DoNotSendEmail -changeSelfEmailMaybeSend :: UserId -> MaybeSendEmail -> Email -> API.AllowSCIMUpdates -> Handler ChangeEmailResponse +changeSelfEmailMaybeSend :: UserId -> MaybeSendEmail -> Email -> API.AllowSCIMUpdates -> (Handler r) ChangeEmailResponse changeSelfEmailMaybeSend u ActuallySendEmail email allowScim = do API.changeSelfEmail u email allowScim changeSelfEmailMaybeSend u DoNotSendEmail email allowScim = do @@ -367,11 +367,11 @@ changeSelfEmailMaybeSend u DoNotSendEmail email allowScim = do ChangeEmailIdempotent -> pure ChangeEmailResponseIdempotent ChangeEmailNeedsActivation _ -> pure ChangeEmailResponseNeedsActivation -listActivatedAccountsH :: JSON ::: Either (List UserId) (List Handle) ::: Bool -> Handler Response +listActivatedAccountsH :: JSON ::: Either (List UserId) (List Handle) ::: Bool -> (Handler r) Response listActivatedAccountsH (_ ::: qry ::: includePendingInvitations) = do json <$> lift (listActivatedAccounts qry includePendingInvitations) -listActivatedAccounts :: Either (List UserId) (List Handle) -> Bool -> AppIO [UserAccount] +listActivatedAccounts :: Either (List UserId) (List Handle) -> Bool -> (AppIO r) [UserAccount] listActivatedAccounts elh includePendingInvitations = do Log.debug (Log.msg $ "listActivatedAccounts: " <> show (elh, includePendingInvitations)) case elh of @@ -380,10 +380,10 @@ listActivatedAccounts elh includePendingInvitations = do us <- mapM (API.lookupHandle) (fromList hs) byIds (catMaybes us) where - byIds :: [UserId] -> AppIO [UserAccount] + byIds :: [UserId] -> (AppIO r) [UserAccount] byIds uids = API.lookupAccounts uids >>= filterM accountValid - accountValid :: UserAccount -> AppIO Bool + accountValid :: UserAccount -> (AppIO r) Bool accountValid account = case userIdentity . accountUser $ account of Nothing -> pure False Just ident -> @@ -402,17 +402,17 @@ listActivatedAccounts elh includePendingInvitations = do (Deleted, _, _) -> pure True (Ephemeral, _, _) -> pure True -listAccountsByIdentityH :: JSON ::: Either Email Phone ::: Bool -> Handler Response +listAccountsByIdentityH :: JSON ::: Either Email Phone ::: Bool -> (Handler r) Response listAccountsByIdentityH (_ ::: emailOrPhone ::: includePendingInvitations) = lift $ json <$> API.lookupAccountsByIdentity emailOrPhone includePendingInvitations -getActivationCodeH :: JSON ::: Either Email Phone -> Handler Response +getActivationCodeH :: JSON ::: Either Email Phone -> (Handler r) Response getActivationCodeH (_ ::: emailOrPhone) = do json <$> getActivationCode emailOrPhone -getActivationCode :: Either Email Phone -> Handler GetActivationCodeResp +getActivationCode :: Either Email Phone -> (Handler r) GetActivationCodeResp getActivationCode emailOrPhone = do apair <- lift $ API.lookupActivationCode emailOrPhone maybe (throwStd activationKeyNotFound) (return . GetActivationCodeResp) apair @@ -422,11 +422,11 @@ data GetActivationCodeResp = GetActivationCodeResp (ActivationKey, ActivationCod instance ToJSON GetActivationCodeResp where toJSON (GetActivationCodeResp (k, c)) = object ["key" .= k, "code" .= c] -getPasswordResetCodeH :: JSON ::: Either Email Phone -> Handler Response +getPasswordResetCodeH :: JSON ::: Either Email Phone -> (Handler r) Response getPasswordResetCodeH (_ ::: emailOrPhone) = do maybe (throwStd invalidPwResetKey) (pure . json) =<< lift (getPasswordResetCode emailOrPhone) -getPasswordResetCode :: Either Email Phone -> AppIO (Maybe GetPasswordResetCodeResp) +getPasswordResetCode :: Either Email Phone -> (AppIO r) (Maybe GetPasswordResetCodeResp) getPasswordResetCode emailOrPhone = do GetPasswordResetCodeResp <$$> API.lookupPasswordResetCode emailOrPhone @@ -435,27 +435,27 @@ data GetPasswordResetCodeResp = GetPasswordResetCodeResp (PasswordResetKey, Pass instance ToJSON GetPasswordResetCodeResp where toJSON (GetPasswordResetCodeResp (k, c)) = object ["key" .= k, "code" .= c] -changeAccountStatusH :: UserId ::: JsonRequest AccountStatusUpdate -> Handler Response +changeAccountStatusH :: UserId ::: JsonRequest AccountStatusUpdate -> (Handler r) Response changeAccountStatusH (usr ::: req) = do status <- suStatus <$> parseJsonBody req API.changeAccountStatus (List1.singleton usr) status !>> accountStatusError return empty -getAccountStatusH :: JSON ::: UserId -> Handler Response +getAccountStatusH :: JSON ::: UserId -> (Handler r) Response getAccountStatusH (_ ::: usr) = do status <- lift $ API.lookupStatus usr return $ case status of Just s -> json $ AccountStatusResp s Nothing -> setStatus status404 empty -getConnectionsStatusUnqualified :: ConnectionsStatusRequest -> Maybe Relation -> Handler [ConnectionStatus] +getConnectionsStatusUnqualified :: ConnectionsStatusRequest -> Maybe Relation -> (Handler r) [ConnectionStatus] getConnectionsStatusUnqualified ConnectionsStatusRequest {csrFrom, csrTo} flt = lift $ do r <- maybe (API.lookupConnectionStatus' csrFrom) (API.lookupConnectionStatus csrFrom) csrTo return $ maybe r (filterByRelation r) flt where filterByRelation l rel = filter ((== rel) . csStatus) l -getConnectionsStatus :: ConnectionsStatusRequestV2 -> Handler [ConnectionStatusV2] +getConnectionsStatus :: ConnectionsStatusRequestV2 -> (Handler r) [ConnectionStatusV2] getConnectionsStatus (ConnectionsStatusRequestV2 froms mtos mrel) = do loc <- qualifyLocal () conns <- lift $ case mtos of @@ -467,35 +467,35 @@ getConnectionsStatus (ConnectionsStatusRequestV2 froms mtos mrel) = do where filterByRelation l rel = filter ((== rel) . csv2Status) l -revokeIdentityH :: Either Email Phone -> Handler Response +revokeIdentityH :: Either Email Phone -> (Handler r) Response revokeIdentityH emailOrPhone = do lift $ API.revokeIdentity emailOrPhone return $ setStatus status200 empty -updateConnectionInternalH :: JSON ::: JsonRequest UpdateConnectionsInternal -> Handler Response +updateConnectionInternalH :: JSON ::: JsonRequest UpdateConnectionsInternal -> (Handler r) Response updateConnectionInternalH (_ ::: req) = do updateConn <- parseJsonBody req API.updateConnectionInternal updateConn !>> connError return $ setStatus status200 empty -checkBlacklistH :: Either Email Phone -> Handler Response +checkBlacklistH :: Either Email Phone -> (Handler r) Response checkBlacklistH emailOrPhone = do bl <- lift $ API.isBlacklisted emailOrPhone return $ setStatus (bool status404 status200 bl) empty -deleteFromBlacklistH :: Either Email Phone -> Handler Response +deleteFromBlacklistH :: Either Email Phone -> (Handler r) Response deleteFromBlacklistH emailOrPhone = do void . lift $ API.blacklistDelete emailOrPhone return empty -addBlacklistH :: Either Email Phone -> Handler Response +addBlacklistH :: Either Email Phone -> (Handler r) Response addBlacklistH emailOrPhone = do void . lift $ API.blacklistInsert emailOrPhone return empty -- | Get any matching prefixes. Also try for shorter prefix matches, -- i.e. checking for +123456 also checks for +12345, +1234, ... -getPhonePrefixesH :: PhonePrefix -> Handler Response +getPhonePrefixesH :: PhonePrefix -> (Handler r) Response getPhonePrefixesH prefix = do results <- lift $ API.phonePrefixGet prefix return $ case results of @@ -503,18 +503,18 @@ getPhonePrefixesH prefix = do _ -> json results -- | Delete a phone prefix entry (must be an exact match) -deleteFromPhonePrefixH :: PhonePrefix -> Handler Response +deleteFromPhonePrefixH :: PhonePrefix -> (Handler r) Response deleteFromPhonePrefixH prefix = do void . lift $ API.phonePrefixDelete prefix return empty -addPhonePrefixH :: JSON ::: JsonRequest ExcludedPrefix -> Handler Response +addPhonePrefixH :: JSON ::: JsonRequest ExcludedPrefix -> (Handler r) Response addPhonePrefixH (_ ::: req) = do prefix :: ExcludedPrefix <- parseJsonBody req void . lift $ API.phonePrefixInsert prefix return empty -updateSSOIdH :: UserId ::: JSON ::: JsonRequest UserSSOId -> Handler Response +updateSSOIdH :: UserId ::: JSON ::: JsonRequest UserSSOId -> (Handler r) Response updateSSOIdH (uid ::: _ ::: req) = do ssoid :: UserSSOId <- parseJsonBody req success <- lift $ Data.updateSSOId uid (Just ssoid) @@ -524,7 +524,7 @@ updateSSOIdH (uid ::: _ ::: req) = do return empty else return . setStatus status404 $ plain "User does not exist or has no team." -deleteSSOIdH :: UserId ::: JSON -> Handler Response +deleteSSOIdH :: UserId ::: JSON -> (Handler r) Response deleteSSOIdH (uid ::: _) = do success <- lift $ Data.updateSSOId uid Nothing if success @@ -533,17 +533,17 @@ deleteSSOIdH (uid ::: _) = do return empty else return . setStatus status404 $ plain "User does not exist or has no team." -updateManagedByH :: UserId ::: JSON ::: JsonRequest ManagedByUpdate -> Handler Response +updateManagedByH :: UserId ::: JSON ::: JsonRequest ManagedByUpdate -> (Handler r) Response updateManagedByH (uid ::: _ ::: req) = do ManagedByUpdate managedBy <- parseJsonBody req lift $ Data.updateManagedBy uid managedBy return empty -updateRichInfoH :: UserId ::: JSON ::: JsonRequest RichInfoUpdate -> Handler Response +updateRichInfoH :: UserId ::: JSON ::: JsonRequest RichInfoUpdate -> (Handler r) Response updateRichInfoH (uid ::: _ ::: req) = do empty <$ (updateRichInfo uid =<< parseJsonBody req) -updateRichInfo :: UserId -> RichInfoUpdate -> Handler () +updateRichInfo :: UserId -> RichInfoUpdate -> (Handler r) () updateRichInfo uid rup = do let (unRichInfoAssocList -> richInfo) = normalizeRichInfoAssocList . riuRichInfo $ rup maxSize <- setRichInfoLimit <$> view settings @@ -552,31 +552,31 @@ updateRichInfo uid rup = do -- Intra.onUserEvent uid (Just conn) (richInfoUpdate uid ri) lift $ Data.updateRichInfo uid (mkRichInfoAssocList richInfo) -getRichInfoH :: UserId -> Handler Response +getRichInfoH :: UserId -> (Handler r) Response getRichInfoH uid = json <$> getRichInfo uid -getRichInfo :: UserId -> Handler RichInfo +getRichInfo :: UserId -> (Handler r) RichInfo getRichInfo uid = RichInfo . fromMaybe mempty <$> lift (API.lookupRichInfo uid) -getRichInfoMultiH :: List UserId -> Handler Response +getRichInfoMultiH :: List UserId -> (Handler r) Response getRichInfoMultiH uids = json <$> getRichInfoMulti (List.fromList uids) -getRichInfoMulti :: [UserId] -> Handler [(UserId, RichInfo)] +getRichInfoMulti :: [UserId] -> (Handler r) [(UserId, RichInfo)] getRichInfoMulti uids = lift (API.lookupRichInfoMultiUsers uids) -updateHandleH :: UserId ::: JSON ::: JsonRequest HandleUpdate -> Handler Response +updateHandleH :: UserId ::: JSON ::: JsonRequest HandleUpdate -> (Handler r) Response updateHandleH (uid ::: _ ::: body) = empty <$ (updateHandle uid =<< parseJsonBody body) -updateHandle :: UserId -> HandleUpdate -> Handler () +updateHandle :: UserId -> HandleUpdate -> (Handler r) () updateHandle uid (HandleUpdate handleUpd) = do handle <- validateHandle handleUpd API.changeHandle uid Nothing handle API.AllowSCIMUpdates !>> changeHandleError -updateUserNameH :: UserId ::: JSON ::: JsonRequest NameUpdate -> Handler Response +updateUserNameH :: UserId ::: JSON ::: JsonRequest NameUpdate -> (Handler r) Response updateUserNameH (uid ::: _ ::: body) = empty <$ (updateUserName uid =<< parseJsonBody body) -updateUserName :: UserId -> NameUpdate -> Handler () +updateUserName :: UserId -> NameUpdate -> (Handler r) () updateUserName uid (NameUpdate nameUpd) = do name <- either (const $ throwStd (errorDescriptionTypeToWai @InvalidUser)) pure $ mkName nameUpd let uu = @@ -590,19 +590,19 @@ updateUserName uid (NameUpdate nameUpd) = do Just _ -> API.updateUser uid Nothing uu API.AllowSCIMUpdates !>> updateProfileError Nothing -> throwStd (errorDescriptionTypeToWai @InvalidUser) -checkHandleInternalH :: Text -> Handler Response +checkHandleInternalH :: Text -> (Handler r) Response checkHandleInternalH = API.checkHandle >=> \case - API.CheckHandleInvalid -> throwE (StdError invalidHandle) + API.CheckHandleInvalid -> throwE (StdError (errorDescriptionTypeToWai @InvalidHandle)) API.CheckHandleFound -> pure $ setStatus status200 empty API.CheckHandleNotFound -> pure $ setStatus status404 empty -getContactListH :: JSON ::: UserId -> Handler Response +getContactListH :: JSON ::: UserId -> (Handler r) Response getContactListH (_ ::: uid) = do contacts <- lift $ API.lookupContactList uid return $ json $ UserIds contacts -- Utilities -ifNothing :: Utilities.Error -> Maybe a -> Handler a +ifNothing :: Utilities.Error -> Maybe a -> (Handler r) a ifNothing e = maybe (throwStd e) return diff --git a/services/brig/src/Brig/API/Properties.hs b/services/brig/src/Brig/API/Properties.hs index d6c3e00cd18..48d5af9e9c1 100644 --- a/services/brig/src/Brig/API/Properties.hs +++ b/services/brig/src/Brig/API/Properties.hs @@ -36,17 +36,17 @@ import Control.Error import Data.Id import Imports -setProperty :: UserId -> ConnId -> PropertyKey -> PropertyValue -> ExceptT PropertiesDataError AppIO () +setProperty :: UserId -> ConnId -> PropertyKey -> PropertyValue -> ExceptT PropertiesDataError (AppIO r) () setProperty u c k v = do Data.insertProperty u k v lift $ Intra.onPropertyEvent u c (PropertySet u k v) -deleteProperty :: UserId -> ConnId -> PropertyKey -> AppIO () +deleteProperty :: UserId -> ConnId -> PropertyKey -> (AppIO r) () deleteProperty u c k = do Data.deleteProperty u k Intra.onPropertyEvent u c (PropertyDeleted u k) -clearProperties :: UserId -> ConnId -> AppIO () +clearProperties :: UserId -> ConnId -> (AppIO r) () clearProperties u c = do Data.clearProperties u Intra.onPropertyEvent u c (PropertiesCleared u) diff --git a/services/brig/src/Brig/API/Public.hs b/services/brig/src/Brig/API/Public.hs index d35ad9c5e8e..afffe057767 100644 --- a/services/brig/src/Brig/API/Public.hs +++ b/services/brig/src/Brig/API/Public.hs @@ -22,7 +22,6 @@ module Brig.API.Public apiDocs, servantSitemap, swaggerDocsAPI, - ServantAPI, SwaggerDocsAPI, ) where @@ -93,7 +92,6 @@ import qualified Network.Wai.Utilities.Swagger as Doc import Network.Wai.Utilities.ZAuth (zauthConnId, zauthUserId) import Servant hiding (Handler, JSON, addHeader, respond) import qualified Servant -import Servant.Server.Generic (genericServerT) import Servant.Swagger.Internal.Orphans () import Servant.Swagger.UI import qualified System.Logger.Class as Log @@ -102,14 +100,15 @@ import qualified Wire.API.Connection as Public import Wire.API.ErrorDescription import qualified Wire.API.Properties as Public import qualified Wire.API.Routes.MultiTablePaging as Public -import Wire.API.Routes.Public.Brig (Api (updateConnectionUnqualified)) -import qualified Wire.API.Routes.Public.Brig as BrigAPI +import Wire.API.Routes.Named +import Wire.API.Routes.Public.Brig 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 import qualified Wire.API.Routes.Public.LegalHold as LegalHoldAPI import qualified Wire.API.Routes.Public.Spar as SparAPI import qualified Wire.API.Routes.Public.Util as Public +import Wire.API.Routes.Version import qualified Wire.API.Swagger as Public.Swagger (models) import qualified Wire.API.Team as Public import Wire.API.Team.LegalHold (LegalholdProtectee (..)) @@ -128,12 +127,11 @@ import qualified Wire.API.Wrapped as Public type SwaggerDocsAPI = "api" :> SwaggerSchemaUI "swagger-ui" "swagger.json" -type ServantAPI = BrigAPI.ServantAPI - swaggerDocsAPI :: Servant.Server SwaggerDocsAPI swaggerDocsAPI = swaggerSchemaUIServer $ - ( BrigAPI.swagger + ( brigSwagger + <> versionSwagger <> GalleyAPI.swaggerDoc <> LegalHoldAPI.swaggerDoc <> SparAPI.swaggerDoc @@ -162,48 +160,72 @@ swaggerDocsAPI = . (S.required %~ nubOrd) . (S.enum_ . _Just %~ nub) -servantSitemap :: ServerT ServantAPI Handler -servantSitemap = - genericServerT $ - BrigAPI.Api - { BrigAPI.getUserUnqualified = getUserUnqualifiedH, - BrigAPI.getUserQualified = getUser, - BrigAPI.getSelf = getSelf, - BrigAPI.deleteSelf = deleteUser, - BrigAPI.updateUserEmail = updateUserEmail, - BrigAPI.getHandleInfoUnqualified = getHandleInfoUnqualifiedH, - BrigAPI.getUserByHandleQualified = Handle.getHandleInfo, - BrigAPI.listUsersByUnqualifiedIdsOrHandles = listUsersByUnqualifiedIdsOrHandles, - BrigAPI.listUsersByIdsOrHandles = listUsersByIdsOrHandles, - BrigAPI.getUserClientsUnqualified = getUserClientsUnqualified, - BrigAPI.getUserClientsQualified = getUserClientsQualified, - BrigAPI.getUserClientUnqualified = getUserClientUnqualified, - BrigAPI.getUserClientQualified = getUserClientQualified, - BrigAPI.listClientsBulk = listClientsBulk, - BrigAPI.listClientsBulkV2 = listClientsBulkV2, - BrigAPI.getUsersPrekeysClientUnqualified = getPrekeyUnqualifiedH, - BrigAPI.getUsersPrekeysClientQualified = getPrekeyH, - BrigAPI.getUsersPrekeyBundleUnqualified = getPrekeyBundleUnqualifiedH, - BrigAPI.getUsersPrekeyBundleQualified = getPrekeyBundleH, - BrigAPI.getMultiUserPrekeyBundleUnqualified = getMultiUserPrekeyBundleUnqualifiedH, - BrigAPI.getMultiUserPrekeyBundleQualified = getMultiUserPrekeyBundleH, - BrigAPI.addClient = addClient, - BrigAPI.updateClient = updateClient, - BrigAPI.deleteClient = deleteClient, - BrigAPI.listClients = listClients, - BrigAPI.getClient = getClient, - BrigAPI.getClientCapabilities = getClientCapabilities, - BrigAPI.getClientPrekeys = getClientPrekeys, - BrigAPI.createConnectionUnqualified = createConnectionUnqualified, - BrigAPI.createConnection = createConnection, - BrigAPI.listLocalConnections = listLocalConnections, - BrigAPI.listConnections = listConnections, - BrigAPI.getConnectionUnqualified = getLocalConnection, - BrigAPI.getConnection = getConnection, - BrigAPI.updateConnectionUnqualified = updateLocalConnection, - BrigAPI.updateConnection = updateConnection, - BrigAPI.searchContacts = Search.search - } +servantSitemap :: ServerT BrigAPI (Handler r) +servantSitemap = userAPI :<|> selfAPI :<|> clientAPI :<|> prekeyAPI :<|> userClientAPI :<|> connectionAPI + where + userAPI :: ServerT UserAPI (Handler r) + userAPI = + Named @"get-user-unqualified" getUserUnqualifiedH + :<|> Named @"get-user-qualified" getUser + :<|> Named @"update-user-email" updateUserEmail + :<|> Named @"get-handle-info-unqualified" getHandleInfoUnqualifiedH + :<|> Named @"get-user-by-handle-qualified" Handle.getHandleInfo + :<|> Named @"list-users-by-unqualified-ids-or-handles" listUsersByUnqualifiedIdsOrHandles + :<|> Named @"list-users-by-ids-or-handles" listUsersByIdsOrHandles + :<|> Named @"send-verification-code" (const sendVerificationCode) + + selfAPI :: ServerT SelfAPI (Handler r) + selfAPI = + Named @"get-self" getSelf + :<|> Named @"delete-self" deleteUser + :<|> Named @"put-self" updateUser + :<|> Named @"change-phone" changePhone + :<|> Named @"remove-phone" removePhone + :<|> Named @"remove-email" removeEmail + :<|> Named @"check-password-exists" checkPasswordExists + :<|> Named @"change-password" changePassword + :<|> Named @"change-locale" changeLocale + :<|> Named @"change-handle" changeHandle + + clientAPI :: ServerT ClientAPI (Handler r) + clientAPI = + Named @"get-user-clients-unqualified" getUserClientsUnqualified + :<|> Named @"get-user-clients-qualified" getUserClientsQualified + :<|> Named @"get-user-client-unqualified" getUserClientUnqualified + :<|> Named @"get-user-client-qualified" getUserClientQualified + :<|> Named @"list-clients-bulk" listClientsBulk + :<|> Named @"list-clients-bulk-v2" listClientsBulkV2 + + prekeyAPI :: ServerT PrekeyAPI (Handler r) + prekeyAPI = + Named @"get-users-prekeys-client-unqualified" getPrekeyUnqualifiedH + :<|> Named @"get-users-prekeys-client-qualified" getPrekeyH + :<|> Named @"get-users-prekey-bundle-unqualified" getPrekeyBundleUnqualifiedH + :<|> Named @"get-users-prekey-bundle-qualified" getPrekeyBundleH + :<|> Named @"get-multi-user-prekey-bundle-unqualified" getMultiUserPrekeyBundleUnqualifiedH + :<|> Named @"get-multi-user-prekey-bundle-qualified" getMultiUserPrekeyBundleH + + userClientAPI :: ServerT UserClientAPI (Handler r) + userClientAPI = + Named @"add-client" addClient + :<|> Named @"update-client" updateClient + :<|> Named @"delete-client" deleteClient + :<|> Named @"list-clients" listClients + :<|> Named @"get-client" getClient + :<|> Named @"get-client-capabilities" getClientCapabilities + :<|> Named @"get-client-prekeys" getClientPrekeys + + connectionAPI :: ServerT ConnectionAPI (Handler r) + connectionAPI = + Named @"create-connection-unqualified" createConnectionUnqualified + :<|> Named @"create-connection" createConnection + :<|> Named @"list-local-connections" listLocalConnections + :<|> Named @"list-connections" listConnections + :<|> Named @"get-connection-unqualified" getLocalConnection + :<|> Named @"get-connection" getConnection + :<|> Named @"update-connection-unqualified" updateLocalConnection + :<|> Named @"update-connection" updateConnection + :<|> Named @"search-contacts" Search.search -- Note [ephemeral user sideeffect] -- If the user is ephemeral and expired, it will be removed upon calling @@ -212,7 +234,7 @@ servantSitemap = -- - UserDeleted event to contacts of the user -- - MemberLeave event to members for all conversations the user was in (via galley) -sitemap :: Routes Doc.ApiBuilder Handler () +sitemap :: Routes Doc.ApiBuilder (Handler r) () sitemap = do -- User Handle API ---------------------------------------------------- @@ -235,7 +257,7 @@ sitemap = do Doc.parameter Doc.Path "handle" Doc.bytes' $ Doc.description "Handle to check" Doc.response 200 "Handle is taken" Doc.end - Doc.errorResponse invalidHandle + Doc.errorResponse (errorDescriptionTypeToWai @InvalidHandle) Doc.errorResponse (errorDescriptionTypeToWai @HandleNotFound) -- some APIs moved to servant @@ -253,112 +275,6 @@ sitemap = do Doc.response 200 "RichInfo" Doc.end Doc.errorResponse insufficientTeamPermissions - -- User Self API ------------------------------------------------------ - - -- This endpoint can lead to the following events being sent: - -- - UserUpdated event to contacts of self - put "/self" (continue updateUserH) $ - zauthUserId - .&. zauthConnId - .&. jsonRequest @Public.UserUpdate - document "PUT" "updateSelf" $ do - Doc.summary "Update your profile" - Doc.body (Doc.ref Public.modelUserUpdate) $ - Doc.description "JSON body" - Doc.response 200 "Update successful." Doc.end - - get "/self/name" (continue getUserDisplayNameH) $ - accept "application" "json" - .&. zauthUserId - document "GET" "selfName" $ do - Doc.summary "Get your profile name" - Doc.returns (Doc.ref Public.modelUserDisplayName) - Doc.response 200 "Profile name found." Doc.end - - put "/self/phone" (continue changePhoneH) $ - zauthUserId - .&. zauthConnId - .&. jsonRequest @Public.PhoneUpdate - document "PUT" "changePhone" $ do - Doc.summary "Change your phone number" - Doc.body (Doc.ref Public.modelPhoneUpdate) $ - Doc.description "JSON body" - Doc.response 202 "Update accepted and pending activation of the new phone number." Doc.end - Doc.errorResponse userKeyExists - - head - "/self/password" - (continue checkPasswordExistsH) - zauthUserId - document "HEAD" "checkPassword" $ do - Doc.summary "Check that your password is set" - Doc.response 200 "Password is set." Doc.end - Doc.response 404 "Password is not set." Doc.end - - put "/self/password" (continue changePasswordH) $ - zauthUserId - .&. jsonRequest @Public.PasswordChange - document "PUT" "changePassword" $ do - Doc.summary "Change your password" - Doc.body (Doc.ref Public.modelChangePassword) $ - Doc.description "JSON body" - Doc.response 200 "Password changed." Doc.end - Doc.errorResponse (errorDescriptionTypeToWai @BadCredentials) - Doc.errorResponse (errorDescriptionToWai (noIdentity 4)) - - put "/self/locale" (continue changeLocaleH) $ - zauthUserId - .&. zauthConnId - .&. jsonRequest @Public.LocaleUpdate - document "PUT" "changeLocale" $ do - Doc.summary "Change your locale" - Doc.body (Doc.ref Public.modelChangeLocale) $ - Doc.description "JSON body" - Doc.response 200 "Locale changed." Doc.end - - -- This endpoint can lead to the following events being sent: - -- - UserUpdated event to contacts of self - put "/self/handle" (continue changeHandleH) $ - zauthUserId - .&. zauthConnId - .&. jsonRequest @Public.HandleUpdate - document "PUT" "changeHandle" $ do - Doc.summary "Change your handle" - Doc.body (Doc.ref Public.modelChangeHandle) $ - Doc.description "JSON body" - Doc.errorResponse handleExists - Doc.errorResponse invalidHandle - Doc.response 200 "Handle changed." Doc.end - - -- This endpoint can lead to the following events being sent: - -- - UserIdentityRemoved event to self - delete "/self/phone" (continue removePhoneH) $ - zauthUserId - .&. zauthConnId - document "DELETE" "removePhone" $ do - Doc.summary "Remove your phone number." - Doc.notes - "Your phone number can only be removed if you also have an \ - \email address and a password." - Doc.response 200 "Phone number removed." Doc.end - Doc.errorResponse lastIdentity - Doc.errorResponse noPassword - - -- This endpoint can lead to the following events being sent: - -- - UserIdentityRemoved event to self - delete "/self/email" (continue removeEmailH) $ - zauthUserId - .&. zauthConnId - document "DELETE" "removeEmail" $ do - Doc.summary "Remove your email address." - Doc.notes - "Your email address can only be removed if you also have a \ - \phone number." - Doc.response 200 "Email address removed." Doc.end - Doc.errorResponse lastIdentity - - -- TODO put where? - -- This endpoint can lead to the following events being sent: -- UserDeleted event to contacts of deleted user -- MemberLeave event to members for all conversations the user was in (via galley) @@ -466,10 +382,10 @@ sitemap = do Doc.errorResponse whitelistError Doc.errorResponse invalidInvitationCode Doc.errorResponse missingIdentity - Doc.errorResponse userKeyExists + Doc.errorResponse (errorDescriptionTypeToWai @UserKeyExists) Doc.errorResponse activationCodeNotFound Doc.errorResponse blacklistedEmail - Doc.errorResponse blacklistedPhone + Doc.errorResponse (errorDescriptionTypeToWai @BlacklistedPhone) -- This endpoint can lead to the following events being sent: -- - UserActivated event to the user, if account gets activated @@ -518,10 +434,10 @@ sitemap = do Doc.description "JSON body" Doc.response 200 "Activation code sent." Doc.end Doc.errorResponse invalidEmail - Doc.errorResponse invalidPhone - Doc.errorResponse userKeyExists + Doc.errorResponse (errorDescriptionTypeToWai @InvalidPhone) + Doc.errorResponse (errorDescriptionTypeToWai @UserKeyExists) Doc.errorResponse blacklistedEmail - Doc.errorResponse blacklistedPhone + Doc.errorResponse (errorDescriptionTypeToWai @BlacklistedPhone) Doc.errorResponse (customerExtensionBlockedDomain (either undefined id $ mkDomain "example.com")) post "/password-reset" (continue beginPasswordResetH) $ @@ -571,7 +487,7 @@ sitemap = do Team.routesPublic Calling.routesPublic -apiDocs :: Routes Doc.ApiBuilder Handler () +apiDocs :: Routes Doc.ApiBuilder (Handler r) () apiDocs = get "/users/api-docs" @@ -585,17 +501,17 @@ apiDocs = --------------------------------------------------------------------------- -- Handlers -setPropertyH :: UserId ::: ConnId ::: Public.PropertyKey ::: JsonRequest Public.PropertyValue -> Handler Response +setPropertyH :: UserId ::: ConnId ::: Public.PropertyKey ::: JsonRequest Public.PropertyValue -> (Handler r) Response setPropertyH (u ::: c ::: k ::: req) = do propkey <- safeParsePropertyKey k propval <- safeParsePropertyValue (lazyRequestBody (fromJsonRequest req)) empty <$ setProperty u c propkey propval -setProperty :: UserId -> ConnId -> Public.PropertyKey -> Public.PropertyValue -> Handler () +setProperty :: UserId -> ConnId -> Public.PropertyKey -> Public.PropertyValue -> (Handler r) () setProperty u c propkey propval = API.setProperty u c propkey propval !>> propDataError -safeParsePropertyKey :: Public.PropertyKey -> Handler Public.PropertyKey +safeParsePropertyKey :: Public.PropertyKey -> (Handler r) Public.PropertyKey safeParsePropertyKey k = do maxKeyLen <- fromMaybe defMaxKeyLen <$> view (settings . propertyMaxKeyLen) let keyText = Ascii.toText (Public.propertyKeyName k) @@ -606,7 +522,7 @@ safeParsePropertyKey k = do -- | Parse a 'PropertyValue' from a bytestring. This is different from 'FromJSON' in that -- checks the byte size of the input, and fails *without consuming all of it* if that size -- exceeds the settings. -safeParsePropertyValue :: IO Lazy.ByteString -> Handler Public.PropertyValue +safeParsePropertyValue :: IO Lazy.ByteString -> (Handler r) Public.PropertyValue safeParsePropertyValue lreqbody = do maxValueLen <- fromMaybe defMaxValueLen <$> view (settings . propertyMaxValueLen) lbs <- Lazy.take (maxValueLen + 1) <$> liftIO lreqbody @@ -614,56 +530,56 @@ safeParsePropertyValue lreqbody = do throwStd propertyValueTooLarge hoistEither $ fmapL (StdError . badRequest . pack) (eitherDecode lbs) -deletePropertyH :: UserId ::: ConnId ::: Public.PropertyKey -> Handler Response +deletePropertyH :: UserId ::: ConnId ::: Public.PropertyKey -> (Handler r) Response deletePropertyH (u ::: c ::: k) = lift (API.deleteProperty u c k) >> return empty -clearPropertiesH :: UserId ::: ConnId -> Handler Response +clearPropertiesH :: UserId ::: ConnId -> (Handler r) Response clearPropertiesH (u ::: c) = lift (API.clearProperties u c) >> return empty -getPropertyH :: UserId ::: Public.PropertyKey ::: JSON -> Handler Response +getPropertyH :: UserId ::: Public.PropertyKey ::: JSON -> (Handler r) Response getPropertyH (u ::: k ::: _) = do val <- lift $ API.lookupProperty u k return $ case val of Nothing -> setStatus status404 empty Just v -> json (v :: Public.PropertyValue) -listPropertyKeysH :: UserId ::: JSON -> Handler Response +listPropertyKeysH :: UserId ::: JSON -> (Handler r) Response listPropertyKeysH (u ::: _) = do keys <- lift (API.lookupPropertyKeys u) pure $ json (keys :: [Public.PropertyKey]) -listPropertyKeysAndValuesH :: UserId ::: JSON -> Handler Response +listPropertyKeysAndValuesH :: UserId ::: JSON -> (Handler r) Response listPropertyKeysAndValuesH (u ::: _) = do keysAndVals <- lift (API.lookupPropertyKeysAndValues u) pure $ json (keysAndVals :: Public.PropertyKeysAndValues) -getPrekeyUnqualifiedH :: UserId -> UserId -> ClientId -> Handler Public.ClientPrekey +getPrekeyUnqualifiedH :: UserId -> UserId -> ClientId -> (Handler r) Public.ClientPrekey getPrekeyUnqualifiedH zusr user client = do domain <- viewFederationDomain getPrekeyH zusr (Qualified user domain) client -getPrekeyH :: UserId -> Qualified UserId -> ClientId -> Handler Public.ClientPrekey +getPrekeyH :: UserId -> Qualified UserId -> ClientId -> (Handler r) Public.ClientPrekey getPrekeyH zusr (Qualified user domain) client = do mPrekey <- API.claimPrekey (ProtectedUser zusr) user domain client !>> clientError ifNothing (notFound "prekey not found") mPrekey -getPrekeyBundleUnqualifiedH :: UserId -> UserId -> Handler Public.PrekeyBundle +getPrekeyBundleUnqualifiedH :: UserId -> UserId -> (Handler r) Public.PrekeyBundle getPrekeyBundleUnqualifiedH zusr uid = do domain <- viewFederationDomain API.claimPrekeyBundle (ProtectedUser zusr) domain uid !>> clientError -getPrekeyBundleH :: UserId -> Qualified UserId -> Handler Public.PrekeyBundle +getPrekeyBundleH :: UserId -> Qualified UserId -> (Handler r) Public.PrekeyBundle getPrekeyBundleH zusr (Qualified uid domain) = API.claimPrekeyBundle (ProtectedUser zusr) domain uid !>> clientError -getMultiUserPrekeyBundleUnqualifiedH :: UserId -> Public.UserClients -> Handler Public.UserClientPrekeyMap +getMultiUserPrekeyBundleUnqualifiedH :: UserId -> Public.UserClients -> (Handler r) Public.UserClientPrekeyMap getMultiUserPrekeyBundleUnqualifiedH zusr userClients = do maxSize <- fromIntegral . setMaxConvSize <$> view settings when (Map.size (Public.userClients userClients) > maxSize) $ throwErrorDescriptionType @TooManyClients API.claimLocalMultiPrekeyBundles (ProtectedUser zusr) userClients !>> clientError -getMultiUserPrekeyBundleH :: UserId -> Public.QualifiedUserClients -> Handler Public.QualifiedUserClientPrekeyMap +getMultiUserPrekeyBundleH :: UserId -> Public.QualifiedUserClients -> (Handler r) Public.QualifiedUserClientPrekeyMap getMultiUserPrekeyBundleH zusr qualUserClients = do maxSize <- fromIntegral . setMaxConvSize <$> view settings let Sum (size :: Int) = @@ -674,66 +590,66 @@ getMultiUserPrekeyBundleH zusr qualUserClients = do throwErrorDescriptionType @TooManyClients API.claimMultiPrekeyBundles (ProtectedUser zusr) qualUserClients !>> clientError -addClient :: UserId -> ConnId -> Maybe IpAddr -> Public.NewClient -> Handler BrigAPI.NewClientResponse +addClient :: UserId -> ConnId -> Maybe IpAddr -> Public.NewClient -> (Handler r) NewClientResponse addClient usr con ip new = do -- Users can't add legal hold clients when (Public.newClientType new == Public.LegalHoldClientType) $ throwE (clientError ClientLegalHoldCannotBeAdded) clientResponse <$> API.addClient usr (Just con) (ipAddr <$> ip) new !>> clientError where - clientResponse :: Public.Client -> BrigAPI.NewClientResponse + clientResponse :: Public.Client -> NewClientResponse clientResponse client = Servant.addHeader (Public.clientId client) client -deleteClient :: UserId -> ConnId -> ClientId -> Public.RmClient -> Handler () +deleteClient :: UserId -> ConnId -> ClientId -> Public.RmClient -> (Handler r) () deleteClient usr con clt body = API.rmClient usr con clt (Public.rmPassword body) !>> clientError -updateClient :: UserId -> ClientId -> Public.UpdateClient -> Handler () +updateClient :: UserId -> ClientId -> Public.UpdateClient -> (Handler r) () updateClient usr clt upd = API.updateClient usr clt upd !>> clientError -listClients :: UserId -> Handler [Public.Client] +listClients :: UserId -> (Handler r) [Public.Client] listClients zusr = lift $ API.lookupLocalClients zusr -getClient :: UserId -> ClientId -> Handler (Maybe Public.Client) +getClient :: UserId -> ClientId -> (Handler r) (Maybe Public.Client) getClient zusr clientId = lift $ API.lookupLocalClient zusr clientId -getUserClientsUnqualified :: UserId -> Handler [Public.PubClient] +getUserClientsUnqualified :: UserId -> (Handler r) [Public.PubClient] getUserClientsUnqualified uid = do localdomain <- viewFederationDomain API.lookupPubClients (Qualified uid localdomain) !>> clientError -getUserClientsQualified :: Qualified UserId -> Handler [Public.PubClient] +getUserClientsQualified :: Qualified UserId -> (Handler r) [Public.PubClient] getUserClientsQualified quid = API.lookupPubClients quid !>> clientError -getUserClientUnqualified :: UserId -> ClientId -> Handler Public.PubClient +getUserClientUnqualified :: UserId -> ClientId -> (Handler r) Public.PubClient getUserClientUnqualified uid cid = do localdomain <- viewFederationDomain x <- API.lookupPubClient (Qualified uid localdomain) cid !>> clientError ifNothing (notFound "client not found") x -listClientsBulk :: UserId -> Range 1 BrigAPI.MaxUsersForListClientsBulk [Qualified UserId] -> Handler (Public.QualifiedUserMap (Set Public.PubClient)) +listClientsBulk :: UserId -> Range 1 MaxUsersForListClientsBulk [Qualified UserId] -> (Handler r) (Public.QualifiedUserMap (Set Public.PubClient)) listClientsBulk _zusr limitedUids = API.lookupPubClientsBulk (fromRange limitedUids) !>> clientError -listClientsBulkV2 :: UserId -> Public.LimitedQualifiedUserIdList BrigAPI.MaxUsersForListClientsBulk -> Handler (Public.WrappedQualifiedUserMap (Set Public.PubClient)) +listClientsBulkV2 :: UserId -> Public.LimitedQualifiedUserIdList MaxUsersForListClientsBulk -> (Handler r) (Public.WrappedQualifiedUserMap (Set Public.PubClient)) listClientsBulkV2 zusr userIds = Public.Wrapped <$> listClientsBulk zusr (Public.qualifiedUsers userIds) -getUserClientQualified :: Qualified UserId -> ClientId -> Handler Public.PubClient +getUserClientQualified :: Qualified UserId -> ClientId -> (Handler r) Public.PubClient getUserClientQualified quid cid = do x <- API.lookupPubClient quid cid !>> clientError ifNothing (notFound "client not found") x -getClientCapabilities :: UserId -> ClientId -> Handler Public.ClientCapabilityList +getClientCapabilities :: UserId -> ClientId -> (Handler r) Public.ClientCapabilityList getClientCapabilities uid cid = do mclient <- lift (API.lookupLocalClient uid cid) maybe (throwErrorDescriptionType @ClientNotFound) (pure . Public.clientCapabilities) mclient -getRichInfoH :: UserId ::: UserId ::: JSON -> Handler Response +getRichInfoH :: UserId ::: UserId ::: JSON -> (Handler r) Response getRichInfoH (self ::: user ::: _) = json <$> getRichInfo self user -getRichInfo :: UserId -> UserId -> Handler Public.RichInfoAssocList +getRichInfo :: UserId -> UserId -> (Handler r) Public.RichInfoAssocList getRichInfo self user = do -- Check that both users exist and the requesting user is allowed to see rich info of the -- other user @@ -749,11 +665,11 @@ getRichInfo self user = do -- Query rich info fromMaybe mempty <$> lift (API.lookupRichInfo user) -getClientPrekeys :: UserId -> ClientId -> Handler [Public.PrekeyId] +getClientPrekeys :: UserId -> ClientId -> (Handler r) [Public.PrekeyId] getClientPrekeys usr clt = lift (API.lookupPrekeyIds usr clt) -- docs/reference/user/registration.md {#RefRegistration} -createUserH :: JSON ::: JsonRequest Public.NewUserPublic -> Handler Response +createUserH :: JSON ::: JsonRequest Public.NewUserPublic -> (Handler r) Response createUserH (_ ::: req) = do CreateUserResponse cok loc prof <- createUser =<< parseJsonBody req lift . Auth.setResponseCookie cok @@ -764,7 +680,7 @@ createUserH (_ ::: req) = do data CreateUserResponse = CreateUserResponse (Public.Cookie (ZAuth.Token ZAuth.User)) UserId Public.SelfProfile -createUser :: Public.NewUserPublic -> Handler CreateUserResponse +createUser :: Public.NewUserPublic -> (Handler r) CreateUserResponse createUser (Public.NewUserPublic new) = do API.checkRestrictedUserCreation new !>> newUserError for_ (Public.newUserEmail new) $ checkWhitelist . Left @@ -808,7 +724,7 @@ createUser (Public.NewUserPublic new) = do UserAccount _ _ -> lift $ Auth.newCookie @ZAuth.User userId Public.PersistentCookie newUserLabel pure $ CreateUserResponse cok userId (Public.SelfProfile usr) where - sendActivationEmail :: Public.Email -> Public.Name -> ActivationPair -> Maybe Public.Locale -> Maybe Public.NewTeamUser -> AppIO () + sendActivationEmail :: Public.Email -> Public.Name -> ActivationPair -> Maybe Public.Locale -> Maybe Public.NewTeamUser -> (AppIO r) () sendActivationEmail e u p l mTeamUser | Just teamUser <- mTeamUser, Public.NewTeamCreator creator <- teamUser, @@ -817,7 +733,7 @@ createUser (Public.NewUserPublic new) = do | otherwise = sendActivationMail e u p l Nothing - sendWelcomeEmail :: Public.Email -> CreateUserTeam -> Public.NewTeamUser -> Maybe Public.Locale -> AppIO () + sendWelcomeEmail :: Public.Email -> CreateUserTeam -> Public.NewTeamUser -> Maybe Public.Locale -> (AppIO r) () -- NOTE: Welcome e-mails for the team creator are not dealt by brig anymore sendWelcomeEmail e (CreateUserTeam t n) newUser l = case newUser of Public.NewTeamCreator _ -> @@ -827,30 +743,23 @@ createUser (Public.NewUserPublic new) = do Public.NewTeamMemberSSO _ -> Team.sendMemberWelcomeMail e t n l -getSelf :: UserId -> Handler Public.SelfProfile +getSelf :: UserId -> (Handler r) Public.SelfProfile getSelf self = lift (API.lookupSelfProfile self) >>= ifNothing (errorDescriptionTypeToWai @UserNotFound) -getUserUnqualifiedH :: UserId -> UserId -> Handler (Maybe Public.UserProfile) +getUserUnqualifiedH :: UserId -> UserId -> (Handler r) (Maybe Public.UserProfile) getUserUnqualifiedH self uid = do domain <- viewFederationDomain getUser self (Qualified uid domain) -getUser :: UserId -> Qualified UserId -> Handler (Maybe Public.UserProfile) +getUser :: UserId -> Qualified UserId -> (Handler r) (Maybe Public.UserProfile) getUser self qualifiedUserId = do lself <- qualifyLocal self API.lookupProfile lself qualifiedUserId !>> fedError -getUserDisplayNameH :: JSON ::: UserId -> Handler Response -getUserDisplayNameH (_ ::: self) = do - name :: Maybe Public.Name <- lift $ API.lookupName self - return $ case name of - Just n -> json $ object ["name" .= n] - Nothing -> setStatus status404 empty - -- FUTUREWORK: Make servant understand that at least one of these is required -listUsersByUnqualifiedIdsOrHandles :: UserId -> Maybe (CommaSeparatedList UserId) -> Maybe (Range 1 4 (CommaSeparatedList Handle)) -> Handler [Public.UserProfile] +listUsersByUnqualifiedIdsOrHandles :: UserId -> Maybe (CommaSeparatedList UserId) -> Maybe (Range 1 4 (CommaSeparatedList Handle)) -> (Handler r) [Public.UserProfile] listUsersByUnqualifiedIdsOrHandles self mUids mHandles = do domain <- viewFederationDomain case (mUids, mHandles) of @@ -866,7 +775,7 @@ listUsersByUnqualifiedIdsOrHandles self mUids mHandles = do in listUsersByIdsOrHandles self (Public.ListUsersByHandles qualifiedRangedList) (Nothing, Nothing) -> throwStd $ badRequest "at least one ids or handles must be provided" -listUsersByIdsOrHandles :: UserId -> Public.ListUsersQuery -> Handler [Public.UserProfile] +listUsersByIdsOrHandles :: UserId -> Public.ListUsersQuery -> (Handler r) [Public.UserProfile] listUsersByIdsOrHandles self q = do lself <- qualifyLocal self foundUsers <- case q of @@ -880,12 +789,12 @@ listUsersByIdsOrHandles self q = do [] -> throwStd $ notFound "None of the specified ids or handles match any users" _ -> pure foundUsers where - getIds :: [Handle] -> Handler [Qualified UserId] + getIds :: [Handle] -> (Handler r) [Qualified UserId] getIds localHandles = do localUsers <- catMaybes <$> traverse (lift . API.lookupHandle) localHandles domain <- viewFederationDomain pure $ map (`Qualified` domain) localUsers - byIds :: Local UserId -> [Qualified UserId] -> Handler [Public.UserProfile] + byIds :: Local UserId -> [Qualified UserId] -> (Handler r) [Public.UserProfile] byIds lself uids = API.lookupProfiles lself uids !>> fedError newtype GetActivationCodeResp @@ -894,60 +803,45 @@ newtype GetActivationCodeResp instance ToJSON GetActivationCodeResp where toJSON (GetActivationCodeResp (k, c)) = object ["key" .= k, "code" .= c] -updateUserH :: UserId ::: ConnId ::: JsonRequest Public.UserUpdate -> Handler Response -updateUserH (uid ::: conn ::: req) = do - uu <- parseJsonBody req - API.updateUser uid (Just conn) uu API.ForbidSCIMUpdates !>> updateProfileError - return empty - -changePhoneH :: UserId ::: ConnId ::: JsonRequest Public.PhoneUpdate -> Handler Response -changePhoneH (u ::: c ::: req) = - setStatus status202 empty <$ (changePhone u c =<< parseJsonBody req) +updateUser :: UserId -> ConnId -> Public.UserUpdate -> (Handler r) (Maybe Public.UpdateProfileError) +updateUser uid conn uu = do + eithErr <- lift $ runExceptT $ API.updateUser uid (Just conn) uu API.ForbidSCIMUpdates + pure $ either Just (const Nothing) eithErr -changePhone :: UserId -> ConnId -> Public.PhoneUpdate -> Handler () -changePhone u _ (Public.puPhone -> phone) = do - (adata, pn) <- API.changePhone u phone !>> changePhoneError +changePhone :: UserId -> ConnId -> Public.PhoneUpdate -> (Handler r) (Maybe Public.ChangePhoneError) +changePhone u _ (Public.puPhone -> phone) = lift . exceptTToMaybe $ do + (adata, pn) <- API.changePhone u phone loc <- lift $ API.lookupLocale u let apair = (activationKey adata, activationCode adata) lift $ sendActivationSms pn apair loc -removePhoneH :: UserId ::: ConnId -> Handler Response -removePhoneH (self ::: conn) = do - API.removePhone self conn !>> idtError - return empty +removePhone :: UserId -> ConnId -> (Handler r) (Maybe Public.RemoveIdentityError) +removePhone self conn = + lift . exceptTToMaybe $ API.removePhone self conn -removeEmailH :: UserId ::: ConnId -> Handler Response -removeEmailH (self ::: conn) = do - API.removeEmail self conn !>> idtError - return empty +removeEmail :: UserId -> ConnId -> (Handler r) (Maybe Public.RemoveIdentityError) +removeEmail self conn = + lift . exceptTToMaybe $ API.removeEmail self conn -checkPasswordExistsH :: UserId -> Handler Response -checkPasswordExistsH self = do - exists <- lift $ isJust <$> API.lookupPassword self - return $ if exists then empty else setStatus status404 empty +checkPasswordExists :: UserId -> (Handler r) Bool +checkPasswordExists = fmap isJust . lift . API.lookupPassword -changePasswordH :: UserId ::: JsonRequest Public.PasswordChange -> Handler Response -changePasswordH (u ::: req) = do - cp <- parseJsonBody req - API.changePassword u cp !>> changePwError - return empty +changePassword :: UserId -> Public.PasswordChange -> (Handler r) (Maybe Public.ChangePasswordError) +changePassword u cp = lift . exceptTToMaybe $ API.changePassword u cp -changeLocaleH :: UserId ::: ConnId ::: JsonRequest Public.LocaleUpdate -> Handler Response -changeLocaleH (u ::: conn ::: req) = do - l <- parseJsonBody req - lift $ API.changeLocale u conn l - return empty +changeLocale :: UserId -> ConnId -> Public.LocaleUpdate -> (Handler r) () +changeLocale u conn l = lift $ API.changeLocale u conn l -- | (zusr is ignored by this handler, ie. checking handles is allowed as long as you have -- *any* account.) -checkHandleH :: UserId ::: Text -> Handler Response +checkHandleH :: UserId ::: Text -> (Handler r) Response checkHandleH (_uid ::: hndl) = API.checkHandle hndl >>= \case - API.CheckHandleInvalid -> throwE (StdError invalidHandle) + API.CheckHandleInvalid -> throwE (StdError (errorDescriptionTypeToWai @InvalidHandle)) API.CheckHandleFound -> pure $ setStatus status200 empty API.CheckHandleNotFound -> pure $ setStatus status404 empty -checkHandlesH :: JSON ::: UserId ::: JsonRequest Public.CheckHandles -> Handler Response +checkHandlesH :: JSON ::: UserId ::: JsonRequest Public.CheckHandles -> (Handler r) Response checkHandlesH (_ ::: _ ::: req) = do Public.CheckHandles hs num <- parseJsonBody req let handles = mapMaybe parseHandle (fromRange hs) @@ -958,27 +852,22 @@ checkHandlesH (_ ::: _ ::: req) = do -- compatibility, whereas the corresponding qualified endpoint (implemented by -- 'Handle.getHandleInfo') returns UserProfile to reduce traffic between backends -- in a federated scenario. -getHandleInfoUnqualifiedH :: UserId -> Handle -> Handler (Maybe Public.UserHandleInfo) +getHandleInfoUnqualifiedH :: UserId -> Handle -> (Handler r) (Maybe Public.UserHandleInfo) getHandleInfoUnqualifiedH self handle = do domain <- viewFederationDomain Public.UserHandleInfo . Public.profileQualifiedId <$$> Handle.getHandleInfo self (Qualified handle domain) -changeHandleH :: UserId ::: ConnId ::: JsonRequest Public.HandleUpdate -> Handler Response -changeHandleH (u ::: conn ::: req) = - empty <$ (changeHandle u conn =<< parseJsonBody req) +changeHandle :: UserId -> ConnId -> Public.HandleUpdate -> (Handler r) (Maybe Public.ChangeHandleError) +changeHandle u conn (Public.HandleUpdate h) = lift . exceptTToMaybe $ do + handle <- maybe (throwError Public.ChangeHandleInvalid) pure $ parseHandle h + API.changeHandle u (Just conn) handle API.ForbidSCIMUpdates -changeHandle :: UserId -> ConnId -> Public.HandleUpdate -> Handler () -changeHandle u conn (Public.HandleUpdate h) = do - handle <- API.validateHandle h - -- TODO check here - API.changeHandle u (Just conn) handle API.ForbidSCIMUpdates !>> changeHandleError - -beginPasswordResetH :: JSON ::: JsonRequest Public.NewPasswordReset -> Handler Response +beginPasswordResetH :: JSON ::: JsonRequest Public.NewPasswordReset -> (Handler r) Response beginPasswordResetH (_ ::: req) = setStatus status201 empty <$ (beginPasswordReset =<< parseJsonBody req) -beginPasswordReset :: Public.NewPasswordReset -> Handler () +beginPasswordReset :: Public.NewPasswordReset -> (Handler r) () beginPasswordReset (Public.NewPasswordReset target) = do checkWhitelist target (u, pair) <- API.beginPasswordReset target !>> pwResetError @@ -987,19 +876,19 @@ beginPasswordReset (Public.NewPasswordReset target) = do Left email -> sendPasswordResetMail email pair loc Right phone -> sendPasswordResetSms phone pair loc -completePasswordResetH :: JSON ::: JsonRequest Public.CompletePasswordReset -> Handler Response +completePasswordResetH :: JSON ::: JsonRequest Public.CompletePasswordReset -> (Handler r) Response completePasswordResetH (_ ::: req) = do Public.CompletePasswordReset {..} <- parseJsonBody req API.completePasswordReset cpwrIdent cpwrCode cpwrPassword !>> pwResetError return empty -sendActivationCodeH :: JsonRequest Public.SendActivationCode -> Handler Response +sendActivationCodeH :: JsonRequest Public.SendActivationCode -> (Handler r) Response sendActivationCodeH req = empty <$ (sendActivationCode =<< parseJsonBody req) -- docs/reference/user/activation.md {#RefActivationRequest} -- docs/reference/user/registration.md {#RefRegistration} -sendActivationCode :: Public.SendActivationCode -> Handler () +sendActivationCode :: Public.SendActivationCode -> (Handler r) () sendActivationCode Public.SendActivationCode {..} = do either customerExtensionCheckBlockedDomains (const $ pure ()) saUserKey checkWhitelist saUserKey @@ -1009,7 +898,7 @@ sendActivationCode Public.SendActivationCode {..} = do -- -- The tautological constraint in the type signature is added so that once we remove the -- feature, ghc will guide us here. -customerExtensionCheckBlockedDomains :: (DomainsBlockedForRegistration ~ DomainsBlockedForRegistration) => Public.Email -> Handler () +customerExtensionCheckBlockedDomains :: (DomainsBlockedForRegistration ~ DomainsBlockedForRegistration) => Public.Email -> (Handler r) () customerExtensionCheckBlockedDomains email = do mBlockedDomains <- asks (fmap domainsBlockedForRegistration . setCustomerExtensions . view settings) for_ mBlockedDomains $ \(DomainsBlockedForRegistration blockedDomains) -> do @@ -1020,30 +909,30 @@ customerExtensionCheckBlockedDomains email = do when (domain `elem` blockedDomains) $ throwM $ customerExtensionBlockedDomain domain -createConnectionUnqualified :: UserId -> ConnId -> Public.ConnectionRequest -> Handler (Public.ResponseForExistedCreated Public.UserConnection) +createConnectionUnqualified :: UserId -> ConnId -> Public.ConnectionRequest -> (Handler r) (Public.ResponseForExistedCreated Public.UserConnection) createConnectionUnqualified self conn cr = do lself <- qualifyLocal self target <- qualifyLocal (Public.crUser cr) API.createConnection lself conn (qUntagged target) !>> connError -createConnection :: UserId -> ConnId -> Qualified UserId -> Handler (Public.ResponseForExistedCreated Public.UserConnection) +createConnection :: UserId -> ConnId -> Qualified UserId -> (Handler r) (Public.ResponseForExistedCreated Public.UserConnection) createConnection self conn target = do lself <- qualifyLocal self API.createConnection lself conn target !>> connError -updateLocalConnection :: UserId -> ConnId -> UserId -> Public.ConnectionUpdate -> Handler (Public.UpdateResult Public.UserConnection) +updateLocalConnection :: UserId -> ConnId -> UserId -> Public.ConnectionUpdate -> (Handler r) (Public.UpdateResult Public.UserConnection) updateLocalConnection self conn other update = do lother <- qualifyLocal other updateConnection self conn (qUntagged lother) update -updateConnection :: UserId -> ConnId -> Qualified UserId -> Public.ConnectionUpdate -> Handler (Public.UpdateResult Public.UserConnection) +updateConnection :: UserId -> ConnId -> Qualified UserId -> Public.ConnectionUpdate -> (Handler r) (Public.UpdateResult Public.UserConnection) updateConnection self conn other update = do let newStatus = Public.cuStatus update lself <- qualifyLocal self mc <- API.updateConnection lself other newStatus (Just conn) !>> connError return $ maybe Public.Unchanged Public.Updated mc -listLocalConnections :: UserId -> Maybe UserId -> Maybe (Range 1 500 Int32) -> Handler Public.UserConnectionList +listLocalConnections :: UserId -> Maybe UserId -> Maybe (Range 1 500 Int32) -> (Handler r) Public.UserConnectionList listLocalConnections uid start msize = do let defaultSize = toRange (Proxy @100) lift $ API.lookupConnections uid start (fromMaybe defaultSize msize) @@ -1056,7 +945,7 @@ listLocalConnections uid start msize = do -- -- - After local connections, remote connections are listed ordered -- - lexicographically by their domain and then by their id. -listConnections :: UserId -> Public.ListConnectionsRequestPaginated -> Handler Public.ConnectionsPage +listConnections :: UserId -> Public.ListConnectionsRequestPaginated -> (Handler r) Public.ConnectionsPage listConnections uid Public.GetMultiTablePageRequest {..} = do self <- qualifyLocal uid case gmtprState of @@ -1076,7 +965,7 @@ listConnections uid Public.GetMultiTablePageRequest {..} = do mkState :: ByteString -> C.PagingState mkState = C.PagingState . LBS.fromStrict - localsAndRemotes :: Local UserId -> Maybe C.PagingState -> Range 1 500 Int32 -> Handler Public.ConnectionsPage + localsAndRemotes :: Local UserId -> Maybe C.PagingState -> Range 1 500 Int32 -> (Handler r) Public.ConnectionsPage localsAndRemotes self pagingState size = do localPage <- pageToConnectionsPage Public.PagingLocals <$> Data.lookupLocalConnectionsPage self pagingState (rcast size) let remainingSize = fromRange size - fromIntegral (length (Public.mtpResults localPage)) @@ -1086,16 +975,16 @@ listConnections uid Public.GetMultiTablePageRequest {..} = do remotePage <- remotesOnly self Nothing remainingSize pure remotePage {Public.mtpResults = Public.mtpResults localPage <> Public.mtpResults remotePage} - remotesOnly :: Local UserId -> Maybe C.PagingState -> Int32 -> Handler Public.ConnectionsPage + remotesOnly :: Local UserId -> Maybe C.PagingState -> Int32 -> (Handler r) Public.ConnectionsPage remotesOnly self pagingState size = pageToConnectionsPage Public.PagingRemotes <$> Data.lookupRemoteConnectionsPage self pagingState size -getLocalConnection :: UserId -> UserId -> Handler (Maybe Public.UserConnection) +getLocalConnection :: UserId -> UserId -> (Handler r) (Maybe Public.UserConnection) getLocalConnection self other = do lother <- qualifyLocal other getConnection self (qUntagged lother) -getConnection :: UserId -> Qualified UserId -> Handler (Maybe Public.UserConnection) +getConnection :: UserId -> Qualified UserId -> (Handler r) (Maybe Public.UserConnection) getConnection self other = do lself <- qualifyLocal self lift $ Data.lookupConnection lself other @@ -1103,17 +992,17 @@ getConnection self other = do deleteUser :: UserId -> Public.DeleteUser -> - Handler (Maybe Code.Timeout) + (Handler r) (Maybe Code.Timeout) deleteUser u body = API.deleteUser u (Public.deleteUserPassword body) !>> deleteUserError -verifyDeleteUserH :: JsonRequest Public.VerifyDeleteUser ::: JSON -> Handler Response +verifyDeleteUserH :: JsonRequest Public.VerifyDeleteUser ::: JSON -> (Handler r) Response verifyDeleteUserH (r ::: _) = do body <- parseJsonBody r API.verifyDeleteUser body !>> deleteUserError return (setStatus status200 empty) -updateUserEmail :: UserId -> UserId -> Public.EmailUpdate -> Handler () +updateUserEmail :: UserId -> UserId -> Public.EmailUpdate -> (Handler r) () updateUserEmail zuserId emailOwnerId (Public.EmailUpdate email) = do maybeZuserTeamId <- lift $ Data.lookupUserTeam zuserId whenM (not <$> assertHasPerm maybeZuserTeamId) $ throwStd insufficientTeamPermissions @@ -1121,12 +1010,12 @@ updateUserEmail zuserId emailOwnerId (Public.EmailUpdate email) = do checkSameTeam maybeZuserTeamId maybeEmailOwnerTeamId void $ API.changeSelfEmail emailOwnerId email API.AllowSCIMUpdates where - checkSameTeam :: Maybe TeamId -> Maybe TeamId -> Handler () + checkSameTeam :: Maybe TeamId -> Maybe TeamId -> (Handler r) () checkSameTeam (Just zuserTeamId) maybeEmailOwnerTeamId = when (Just zuserTeamId /= maybeEmailOwnerTeamId) $ throwStd $ notFound "user not found" checkSameTeam Nothing _ = throwStd insufficientTeamPermissions - assertHasPerm :: Maybe TeamId -> Handler Bool + assertHasPerm :: Maybe TeamId -> (Handler r) Bool assertHasPerm maybeTeamId = fromMaybe False <$> check where check = runMaybeT $ do @@ -1150,17 +1039,17 @@ respFromActivationRespWithStatus = \case ActivationRespSuccessNoIdent -> empty -- docs/reference/user/activation.md {#RefActivationSubmit} -activateKeyH :: JSON ::: JsonRequest Public.Activate -> Handler Response +activateKeyH :: JSON ::: JsonRequest Public.Activate -> (Handler r) Response activateKeyH (_ ::: req) = do activationRequest <- parseJsonBody req respFromActivationRespWithStatus <$> activate activationRequest -activateH :: Public.ActivationKey ::: Public.ActivationCode -> Handler Response +activateH :: Public.ActivationKey ::: Public.ActivationCode -> (Handler r) Response activateH (k ::: c) = do let activationRequest = Public.Activate (Public.ActivateKey k) c False respFromActivationRespWithStatus <$> activate activationRequest -activate :: Public.Activate -> Handler ActivationRespWithStatus +activate :: Public.Activate -> (Handler r) ActivationRespWithStatus activate (Public.Activate tgt code dryrun) | dryrun = do API.preverify tgt code !>> actError @@ -1174,9 +1063,16 @@ activate (Public.Activate tgt code dryrun) respond (Just ident) first = ActivationResp $ Public.ActivationResponse ident first respond Nothing _ = ActivationRespSuccessNoIdent +-- Verification + +sendVerificationCode :: (Handler r) () +sendVerificationCode = + case Public.TeamFeatureSndFPasswordChallengeNotImplemented of + _ -> pure () + -- Deprecated -deprecatedOnboardingH :: JSON ::: UserId ::: JsonRequest Value -> Handler Response +deprecatedOnboardingH :: JSON ::: UserId ::: JsonRequest Value -> (Handler r) Response deprecatedOnboardingH (_ ::: _ ::: _) = pure $ json DeprecatedMatchingResult data DeprecatedMatchingResult = DeprecatedMatchingResult @@ -1188,7 +1084,7 @@ instance ToJSON DeprecatedMatchingResult where "auto-connects" .= ([] :: [()]) ] -deprecatedCompletePasswordResetH :: JSON ::: Public.PasswordResetKey ::: JsonRequest Public.PasswordReset -> Handler Response +deprecatedCompletePasswordResetH :: JSON ::: Public.PasswordResetKey ::: JsonRequest Public.PasswordReset -> (Handler r) Response deprecatedCompletePasswordResetH (_ ::: k ::: req) = do pwr <- parseJsonBody req API.completePasswordReset @@ -1200,5 +1096,5 @@ deprecatedCompletePasswordResetH (_ ::: k ::: req) = do -- Utilities -ifNothing :: Utilities.Error -> Maybe a -> Handler a +ifNothing :: Utilities.Error -> Maybe a -> (Handler r) a ifNothing e = maybe (throwStd e) return diff --git a/services/brig/src/Brig/API/Types.hs b/services/brig/src/Brig/API/Types.hs index 8d9359d29d4..663411d7937 100644 --- a/services/brig/src/Brig/API/Types.hs +++ b/services/brig/src/Brig/API/Types.hs @@ -101,10 +101,6 @@ data CreateUserError | -- | Some precondition on another Wire service failed. We propagate this error. ExternalPreconditionFailed Wai.Error -data UpdateProfileError - = DisplayNameManagedByScim - | ProfileNotFound UserId - data InvitationError = InviteeEmailExists UserId | InviteInvalidEmail Email @@ -156,28 +152,12 @@ data LoginError | LoginThrottled RetryAfter | LoginBlocked RetryAfter -data ChangePasswordError - = InvalidCurrentPassword - | ChangePasswordNoIdentity - | ChangePasswordMustDiffer - -data ChangePhoneError - = PhoneExists !Phone - | InvalidNewPhone !Phone - | BlacklistedNewPhone !Phone - data ChangeEmailError = InvalidNewEmail !Email !String | EmailExists !Email | ChangeBlacklistedEmail !Email | EmailManagedByScim -data ChangeHandleError - = ChangeHandleNoIdentity - | ChangeHandleExists - | ChangeHandleInvalid - | ChangeHandleManagedByScim - data SendActivationCodeError = InvalidRecipient UserKey | UserKeyInUse UserKey @@ -197,11 +177,6 @@ data ClientError | ClientCapabilitiesCannotBeRemoved | ClientMissingLegalholdConsent -data RemoveIdentityError - = LastIdentity - | NoPassword - | NoIdentity - data DeleteUserError = DeleteUserInvalid | DeleteUserInvalidCode diff --git a/services/brig/src/Brig/API/User.hs b/services/brig/src/Brig/API/User.hs index 13e21692065..447cc3b3ef4 100644 --- a/services/brig/src/Brig/API/User.hs +++ b/services/brig/src/Brig/API/User.hs @@ -153,6 +153,7 @@ import UnliftIO.Async import Wire.API.Federation.Error import Wire.API.Routes.Internal.Brig.Connection import Wire.API.Team.Member (legalHoldStatus) +import Wire.API.User data AllowSCIMUpdates = AllowSCIMUpdates @@ -162,7 +163,7 @@ data AllowSCIMUpdates ------------------------------------------------------------------------------- -- Create User -verifyUniquenessAndCheckBlacklist :: UserKey -> ExceptT CreateUserError AppIO () +verifyUniquenessAndCheckBlacklist :: UserKey -> ExceptT CreateUserError (AppIO r) () verifyUniquenessAndCheckBlacklist uk = do checkKey Nothing uk blacklisted <- lift $ Blacklist.exists uk @@ -176,7 +177,7 @@ verifyUniquenessAndCheckBlacklist uk = do DuplicateUserKey k -- docs/reference/user/registration.md {#RefRegistration} -createUser :: NewUser -> ExceptT CreateUserError AppIO CreateUserResult +createUser :: NewUser -> ExceptT CreateUserError (AppIO r) CreateUserResult createUser new = do (email, phone) <- validateEmailAndPhone new @@ -275,7 +276,7 @@ createUser new = do where -- NOTE: all functions in the where block don't use any arguments of createUser - validateEmailAndPhone :: NewUser -> ExceptT CreateUserError (AppT IO) (Maybe Email, Maybe Phone) + validateEmailAndPhone :: NewUser -> ExceptT CreateUserError (AppT r IO) (Maybe Email, Maybe Phone) validateEmailAndPhone newUser = do -- Validate e-mail email <- for (newUserEmail newUser) $ \e -> @@ -296,7 +297,7 @@ createUser new = do pure (email, phone) - findTeamInvitation :: Maybe UserKey -> InvitationCode -> ExceptT CreateUserError AppIO (Maybe (Team.Invitation, Team.InvitationInfo, TeamId)) + findTeamInvitation :: Maybe UserKey -> InvitationCode -> ExceptT CreateUserError (AppIO r) (Maybe (Team.Invitation, Team.InvitationInfo, TeamId)) findTeamInvitation Nothing _ = throwE MissingIdentity findTeamInvitation (Just e) c = lift (Team.lookupInvitationInfo c) >>= \case @@ -310,7 +311,7 @@ createUser new = do _ -> throwE InvalidInvitationCode Nothing -> throwE InvalidInvitationCode - ensureMemberCanJoin :: TeamId -> ExceptT CreateUserError AppIO () + ensureMemberCanJoin :: TeamId -> ExceptT CreateUserError (AppIO r) () ensureMemberCanJoin tid = do maxSize <- fromIntegral . setMaxTeamSize <$> view settings (TeamSize teamSize) <- TeamSize.teamSize tid @@ -329,7 +330,7 @@ createUser new = do Team.InvitationInfo -> UserKey -> UserIdentity -> - ExceptT CreateUserError (AppT IO) () + ExceptT CreateUserError (AppT r IO) () acceptTeamInvitation account inv ii uk ident = do let uid = userId (accountUser account) ok <- lift $ Data.claimKey uk uid @@ -351,7 +352,7 @@ createUser new = do Data.usersPendingActivationRemove uid Team.deleteInvitation (Team.inTeam inv) (Team.inInvitation inv) - addUserToTeamSSO :: UserAccount -> TeamId -> UserIdentity -> ExceptT CreateUserError AppIO CreateUserTeam + addUserToTeamSSO :: UserAccount -> TeamId -> UserIdentity -> ExceptT CreateUserError (AppIO r) CreateUserTeam addUserToTeamSSO account tid ident = do let uid = userId (accountUser account) added <- lift $ Intra.addTeamMember uid tid (Nothing, Team.defaultRole) @@ -368,7 +369,7 @@ createUser new = do pure $ CreateUserTeam tid nm -- Handle e-mail activation (deprecated, see #RefRegistrationNoPreverification in /docs/reference/user/registration.md) - handleEmailActivation :: Maybe Email -> UserId -> Maybe BindingNewTeamUser -> ExceptT CreateUserError (AppT IO) (Maybe Activation) + handleEmailActivation :: Maybe Email -> UserId -> Maybe BindingNewTeamUser -> ExceptT CreateUserError (AppT r IO) (Maybe Activation) handleEmailActivation email uid newTeam = do fmap join . for (userEmailKey <$> email) $ \ek -> case newUserEmailCode new of Nothing -> do @@ -385,7 +386,7 @@ createUser new = do return Nothing -- Handle phone activation (deprecated, see #RefRegistrationNoPreverification in /docs/reference/user/registration.md) - handlePhoneActivation :: Maybe Phone -> UserId -> ExceptT CreateUserError (AppT IO) (Maybe Activation) + handlePhoneActivation :: Maybe Phone -> UserId -> ExceptT CreateUserError (AppT r IO) (Maybe Activation) handlePhoneActivation phone uid = do pdata <- fmap join . for (userPhoneKey <$> phone) $ \pk -> case newUserPhoneCode new of Nothing -> do @@ -402,7 +403,7 @@ createUser new = do return Nothing pure pdata -initAccountFeatureConfig :: UserId -> AppIO () +initAccountFeatureConfig :: UserId -> (AppIO r) () initAccountFeatureConfig uid = do mbCciDefNew <- view (settings . getAfcConferenceCallingDefNewMaybe) forM_ mbCciDefNew $ Data.updateFeatureConferenceCalling uid . Just @@ -410,7 +411,7 @@ initAccountFeatureConfig uid = do -- | 'createUser' is becoming hard to maintian, and instead of adding more case distinctions -- all over the place there, we add a new function that handles just the one new flow where -- users are invited to the team via scim. -createUserInviteViaScim :: UserId -> NewUserScimInvitation -> ExceptT Error.Error AppIO UserAccount +createUserInviteViaScim :: UserId -> NewUserScimInvitation -> ExceptT Error.Error (AppIO r) UserAccount createUserInviteViaScim uid (NewUserScimInvitation tid loc name rawEmail) = (`catchE` (throwE . Error.newUserError)) $ do email <- either (throwE . InvalidEmail rawEmail) pure (validateEmail rawEmail) let emKey = userEmailKey email @@ -437,7 +438,7 @@ createUserInviteViaScim uid (NewUserScimInvitation tid loc name rawEmail) = (`ca return account -- | docs/reference/user/registration.md {#RefRestrictRegistration}. -checkRestrictedUserCreation :: NewUser -> ExceptT CreateUserError AppIO () +checkRestrictedUserCreation :: NewUser -> ExceptT CreateUserError (AppIO r) () checkRestrictedUserCreation new = do restrictPlease <- lift . asks $ fromMaybe False . setRestrictUserCreation . view settings when @@ -450,11 +451,11 @@ checkRestrictedUserCreation new = do ------------------------------------------------------------------------------- -- Update Profile -updateUser :: UserId -> Maybe ConnId -> UserUpdate -> AllowSCIMUpdates -> ExceptT UpdateProfileError AppIO () +updateUser :: UserId -> Maybe ConnId -> UserUpdate -> AllowSCIMUpdates -> ExceptT UpdateProfileError (AppIO r) () updateUser uid mconn uu allowScim = do for_ (uupName uu) $ \newName -> do mbUser <- lift $ Data.lookupUser WithPendingInvitations uid - user <- maybe (throwE (ProfileNotFound uid)) pure mbUser + user <- maybe (throwE ProfileNotFound) pure mbUser unless ( userManagedBy user /= ManagedByScim || userDisplayName user == newName @@ -468,7 +469,7 @@ updateUser uid mconn uu allowScim = do ------------------------------------------------------------------------------- -- Update Locale -changeLocale :: UserId -> ConnId -> LocaleUpdate -> AppIO () +changeLocale :: UserId -> ConnId -> LocaleUpdate -> (AppIO r) () changeLocale uid conn (LocaleUpdate loc) = do Data.updateLocale uid loc Intra.onUserEvent uid (Just conn) (localeUpdate uid loc) @@ -476,7 +477,7 @@ changeLocale uid conn (LocaleUpdate loc) = do ------------------------------------------------------------------------------- -- Update ManagedBy -changeManagedBy :: UserId -> ConnId -> ManagedByUpdate -> AppIO () +changeManagedBy :: UserId -> ConnId -> ManagedByUpdate -> (AppIO r) () changeManagedBy uid conn (ManagedByUpdate mb) = do Data.updateManagedBy uid mb Intra.onUserEvent uid (Just conn) (managedByUpdate uid mb) @@ -484,7 +485,7 @@ changeManagedBy uid conn (ManagedByUpdate mb) = do -------------------------------------------------------------------------------- -- Change Handle -changeHandle :: UserId -> Maybe ConnId -> Handle -> AllowSCIMUpdates -> ExceptT ChangeHandleError AppIO () +changeHandle :: UserId -> Maybe ConnId -> Handle -> AllowSCIMUpdates -> ExceptT ChangeHandleError (AppIO r) () changeHandle uid mconn hdl allowScim = do when (isBlacklistedHandle hdl) $ throwE ChangeHandleInvalid @@ -516,7 +517,7 @@ data CheckHandleResp | CheckHandleFound | CheckHandleNotFound -checkHandle :: Text -> API.Handler CheckHandleResp +checkHandle :: Text -> API.Handler r CheckHandleResp checkHandle uhandle = do xhandle <- validateHandle uhandle owner <- lift $ lookupHandle xhandle @@ -538,7 +539,7 @@ checkHandle uhandle = do -------------------------------------------------------------------------------- -- Check Handles -checkHandles :: [Handle] -> Word -> AppIO [Handle] +checkHandles :: [Handle] -> Word -> (AppIO r) [Handle] checkHandles check num = reverse <$> collectFree [] check num where collectFree free _ 0 = return free @@ -557,7 +558,7 @@ checkHandles check num = reverse <$> collectFree [] check num -- | Call 'changeEmail' and process result: if email changes to itself, succeed, if not, send -- validation email. -changeSelfEmail :: UserId -> Email -> AllowSCIMUpdates -> ExceptT Error.Error AppIO ChangeEmailResponse +changeSelfEmail :: UserId -> Email -> AllowSCIMUpdates -> ExceptT Error.Error (AppIO r) ChangeEmailResponse changeSelfEmail u email allowScim = do changeEmail u email allowScim !>> Error.changeEmailError >>= \case ChangeEmailIdempotent -> @@ -575,7 +576,7 @@ changeSelfEmail u email allowScim = do (userIdentity usr) -- | Prepare changing the email (checking a number of invariants). -changeEmail :: UserId -> Email -> AllowSCIMUpdates -> ExceptT ChangeEmailError AppIO ChangeEmailResult +changeEmail :: UserId -> Email -> AllowSCIMUpdates -> ExceptT ChangeEmailError (AppIO r) ChangeEmailResult changeEmail u email allowScim = do em <- either @@ -607,33 +608,32 @@ changeEmail u email allowScim = do ------------------------------------------------------------------------------- -- Change Phone -changePhone :: UserId -> Phone -> ExceptT ChangePhoneError AppIO (Activation, Phone) +changePhone :: UserId -> Phone -> ExceptT ChangePhoneError (AppIO r) (Activation, Phone) changePhone u phone = do canonical <- maybe - (throwE $ InvalidNewPhone phone) + (throwE InvalidNewPhone) return =<< lift (validatePhone phone) let pk = userPhoneKey canonical available <- lift $ Data.keyAvailable pk (Just u) unless available $ - throwE $ - PhoneExists phone + throwE PhoneExists timeout <- setActivationTimeout <$> view settings blacklisted <- lift $ Blacklist.exists pk when blacklisted $ - throwE (BlacklistedNewPhone canonical) + throwE BlacklistedNewPhone -- check if any prefixes of this phone number are blocked prefixExcluded <- lift $ Blacklist.existsAnyPrefix canonical when prefixExcluded $ - throwE (BlacklistedNewPhone canonical) + throwE BlacklistedNewPhone act <- lift $ Data.newActivation pk timeout (Just u) return (act, canonical) ------------------------------------------------------------------------------- -- Remove Email -removeEmail :: UserId -> ConnId -> ExceptT RemoveIdentityError AppIO () +removeEmail :: UserId -> ConnId -> ExceptT RemoveIdentityError (AppIO r) () removeEmail uid conn = do ident <- lift $ fetchUserIdentity uid case ident of @@ -647,7 +647,7 @@ removeEmail uid conn = do ------------------------------------------------------------------------------- -- Remove Phone -removePhone :: UserId -> ConnId -> ExceptT RemoveIdentityError AppIO () +removePhone :: UserId -> ConnId -> ExceptT RemoveIdentityError (AppIO r) () removePhone uid conn = do ident <- lift $ fetchUserIdentity uid case ident of @@ -665,7 +665,7 @@ removePhone uid conn = do ------------------------------------------------------------------------------- -- Forcefully revoke a verified identity -revokeIdentity :: Either Email Phone -> AppIO () +revokeIdentity :: Either Email Phone -> (AppIO r) () revokeIdentity key = do let uk = either userEmailKey userPhoneKey key mu <- Data.lookupKey uk @@ -697,7 +697,7 @@ revokeIdentity key = do ------------------------------------------------------------------------------- -- Change Account Status -changeAccountStatus :: List1 UserId -> AccountStatus -> ExceptT AccountStatusError AppIO () +changeAccountStatus :: List1 UserId -> AccountStatus -> ExceptT AccountStatusError (AppIO r) () changeAccountStatus usrs status = do e <- ask ev <- case status of @@ -708,12 +708,12 @@ changeAccountStatus usrs status = do PendingInvitation -> throwE InvalidAccountStatus liftIO $ mapConcurrently_ (runAppT e . (update ev)) usrs where - update :: (UserId -> UserEvent) -> UserId -> AppIO () + update :: (UserId -> UserEvent) -> UserId -> (AppIO r) () update ev u = do Data.updateStatus u status Intra.onUserEvent u Nothing (ev u) -suspendAccount :: HasCallStack => List1 UserId -> AppIO () +suspendAccount :: HasCallStack => List1 UserId -> (AppIO r) () suspendAccount usrs = runExceptT (changeAccountStatus usrs Suspended) >>= \case Right _ -> pure () @@ -727,7 +727,7 @@ activate :: ActivationCode -> -- | The user for whom to activate the key. Maybe UserId -> - ExceptT ActivationError AppIO ActivationResult + ExceptT ActivationError (AppIO r) ActivationResult activate tgt code usr = activateWithCurrency tgt code usr Nothing activateWithCurrency :: @@ -738,7 +738,7 @@ activateWithCurrency :: -- | Potential currency update. -- ^ TODO: to be removed once billing supports currency changes after team creation Maybe Currency.Alpha -> - ExceptT ActivationError AppIO ActivationResult + ExceptT ActivationError (AppIO r) ActivationResult activateWithCurrency tgt code usr cur = do key <- mkActivationKey tgt Log.info $ @@ -759,12 +759,12 @@ activateWithCurrency tgt code usr cur = do tid <- Intra.getTeamId uid for_ tid $ \t -> Intra.changeTeamStatus t Team.Active cur -preverify :: ActivationTarget -> ActivationCode -> ExceptT ActivationError AppIO () +preverify :: ActivationTarget -> ActivationCode -> ExceptT ActivationError (AppIO r) () preverify tgt code = do key <- mkActivationKey tgt void $ Data.verifyCode key code -onActivated :: ActivationEvent -> AppIO (UserId, Maybe UserIdentity, Bool) +onActivated :: ActivationEvent -> (AppIO r) (UserId, Maybe UserIdentity, Bool) onActivated (AccountActivated account) = do let uid = userId (accountUser account) Log.debug $ field "user" (toByteString uid) . field "action" (Log.val "User.onActivated") @@ -779,7 +779,7 @@ onActivated (PhoneActivated uid phone) = do return (uid, Just (PhoneIdentity phone), False) -- docs/reference/user/activation.md {#RefActivationRequest} -sendActivationCode :: Either Email Phone -> Maybe Locale -> Bool -> ExceptT SendActivationCodeError AppIO () +sendActivationCode :: Either Email Phone -> Maybe Locale -> Bool -> ExceptT SendActivationCodeError (AppIO r) () sendActivationCode emailOrPhone loc call = case emailOrPhone of Left email -> do ek <- @@ -861,7 +861,7 @@ sendActivationCode emailOrPhone loc call = case emailOrPhone of _otherwise -> sendActivationMail em name p loc' ident -mkActivationKey :: ActivationTarget -> ExceptT ActivationError AppIO ActivationKey +mkActivationKey :: ActivationTarget -> ExceptT ActivationError (AppIO r) ActivationKey mkActivationKey (ActivateKey k) = return k mkActivationKey (ActivateEmail e) = do ek <- @@ -881,7 +881,7 @@ mkActivationKey (ActivatePhone p) = do ------------------------------------------------------------------------------- -- Password Management -changePassword :: UserId -> PasswordChange -> ExceptT ChangePasswordError AppIO () +changePassword :: UserId -> PasswordChange -> ExceptT ChangePasswordError (AppIO r) () changePassword uid cp = do activated <- lift $ Data.isActivated uid unless activated $ @@ -898,7 +898,7 @@ changePassword uid cp = do throwE ChangePasswordMustDiffer lift $ Data.updatePassword uid newpw >> revokeAllCookies uid -beginPasswordReset :: Either Email Phone -> ExceptT PasswordResetError AppIO (UserId, PasswordResetPair) +beginPasswordReset :: Either Email Phone -> ExceptT PasswordResetError (AppIO r) (UserId, PasswordResetPair) beginPasswordReset target = do let key = either userEmailKey userPhoneKey target user <- lift (Data.lookupKey key) >>= maybe (throwE InvalidPasswordResetKey) return @@ -911,7 +911,7 @@ beginPasswordReset target = do throwE (PasswordResetInProgress Nothing) (user,) <$> lift (Data.createPasswordResetCode user target) -completePasswordReset :: PasswordResetIdentity -> PasswordResetCode -> PlainTextPassword -> ExceptT PasswordResetError AppIO () +completePasswordReset :: PasswordResetIdentity -> PasswordResetCode -> PlainTextPassword -> ExceptT PasswordResetError (AppIO r) () completePasswordReset ident code pw = do key <- mkPasswordResetKey ident muid :: Maybe UserId <- lift $ Data.verifyPasswordResetCode (key, code) @@ -927,14 +927,14 @@ 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 AppIO () +checkNewIsDifferent :: UserId -> PlainTextPassword -> ExceptT PasswordResetError (AppIO r) () checkNewIsDifferent uid pw = do mcurrpw <- lift $ Data.lookupPassword uid case mcurrpw of Just currpw | verifyPassword pw currpw -> throwE ResetPasswordMustDiffer _ -> pure () -mkPasswordResetKey :: PasswordResetIdentity -> ExceptT PasswordResetError AppIO PasswordResetKey +mkPasswordResetKey :: PasswordResetIdentity -> ExceptT PasswordResetError (AppIO r) PasswordResetKey mkPasswordResetKey ident = case ident of PasswordResetIdentityKey k -> return k PasswordResetEmailIdentity e -> user (userEmailKey e) >>= liftIO . Data.mkPasswordResetKey @@ -953,7 +953,7 @@ mkPasswordResetKey ident = case ident of -- delete them in the team settings. This protects teams against orphanhood. -- -- TODO: communicate deletions of SSO users to SSO service. -deleteUser :: UserId -> Maybe PlainTextPassword -> ExceptT DeleteUserError AppIO (Maybe Timeout) +deleteUser :: UserId -> Maybe PlainTextPassword -> ExceptT DeleteUserError (AppIO r) (Maybe Timeout) deleteUser uid pwd = do account <- lift $ Data.lookupAccount uid case account of @@ -965,7 +965,7 @@ deleteUser uid pwd = do Ephemeral -> go a PendingInvitation -> go a where - ensureNotOwner :: UserAccount -> ExceptT DeleteUserError (AppT IO) () + ensureNotOwner :: UserAccount -> ExceptT DeleteUserError (AppT r IO) () ensureNotOwner acc = do case userTeam $ accountUser acc of Nothing -> pure () @@ -1026,7 +1026,7 @@ deleteUser uid pwd = do -- | Conclude validation and scheduling of user's deletion request that was initiated in -- 'deleteUser'. Called via @post /delete@. -verifyDeleteUser :: VerifyDeleteUser -> ExceptT DeleteUserError AppIO () +verifyDeleteUser :: VerifyDeleteUser -> ExceptT DeleteUserError (AppIO r) () verifyDeleteUser d = do let key = verifyDeleteUserKey d let code = verifyDeleteUserCode d @@ -1040,7 +1040,7 @@ verifyDeleteUser d = do -- via deleting self. -- Team owners can be deleted if the team is not orphaned, i.e. there is at least one -- other owner left. -deleteAccount :: UserAccount -> AppIO () +deleteAccount :: UserAccount -> (AppIO r) () deleteAccount account@(accountUser -> user) = do let uid = userId user Log.info $ field "user" (toByteString uid) . msg (val "Deleting account") @@ -1081,14 +1081,14 @@ deleteAccount account@(accountUser -> user) = do ------------------------------------------------------------------------------- -- Lookups -lookupActivationCode :: Either Email Phone -> AppIO (Maybe ActivationPair) +lookupActivationCode :: Either Email Phone -> (AppIO r) (Maybe ActivationPair) lookupActivationCode emailOrPhone = do let uk = either userEmailKey userPhoneKey emailOrPhone k <- liftIO $ Data.mkActivationKey uk c <- fmap snd <$> Data.lookupActivationCode uk return $ (k,) <$> c -lookupPasswordResetCode :: Either Email Phone -> AppIO (Maybe PasswordResetPair) +lookupPasswordResetCode :: Either Email Phone -> (AppIO r) (Maybe PasswordResetPair) lookupPasswordResetCode emailOrPhone = do let uk = either userEmailKey userPhoneKey emailOrPhone usr <- Data.lookupKey uk @@ -1099,12 +1099,12 @@ lookupPasswordResetCode emailOrPhone = do c <- Data.lookupPasswordResetCode u return $ (k,) <$> c -deleteUserNoVerify :: UserId -> AppIO () +deleteUserNoVerify :: UserId -> (AppIO r) () deleteUserNoVerify uid = do queue <- view internalEvents Queue.enqueue queue (Internal.DeleteUser uid) -deleteUsersNoVerify :: [UserId] -> AppIO () +deleteUsersNoVerify :: [UserId] -> (AppIO r) () deleteUsersNoVerify uids = do for_ uids deleteUserNoVerify m <- view metrics @@ -1113,7 +1113,7 @@ deleteUsersNoVerify uids = do -- | Garbage collect users if they're ephemeral and they have expired. -- Always returns the user (deletion itself is delayed) -userGC :: User -> AppIO User +userGC :: User -> (AppIO r) User userGC u = case (userExpire u) of Nothing -> return u (Just (fromUTCTimeMillis -> e)) -> do @@ -1123,7 +1123,7 @@ userGC u = case (userExpire u) of deleteUserNoVerify (userId u) return u -lookupProfile :: Local UserId -> Qualified UserId -> ExceptT FederationError AppIO (Maybe UserProfile) +lookupProfile :: Local UserId -> Qualified UserId -> ExceptT FederationError (AppIO r) (Maybe UserProfile) lookupProfile self other = listToMaybe <$> lookupProfilesFromDomain @@ -1140,7 +1140,7 @@ lookupProfiles :: Local UserId -> -- | The users ('others') for which to obtain the profiles. [Qualified UserId] -> - ExceptT FederationError AppIO [UserProfile] + ExceptT FederationError (AppIO r) [UserProfile] lookupProfiles self others = fmap concat $ traverseConcurrentlyWithErrors @@ -1148,14 +1148,14 @@ lookupProfiles self others = (bucketQualified others) lookupProfilesFromDomain :: - Local UserId -> Qualified [UserId] -> ExceptT FederationError AppIO [UserProfile] + Local UserId -> Qualified [UserId] -> ExceptT FederationError (AppIO r) [UserProfile] lookupProfilesFromDomain self = foldQualified self (lift . lookupLocalProfiles (Just (tUnqualified self)) . tUnqualified) lookupRemoteProfiles -lookupRemoteProfiles :: Remote [UserId] -> ExceptT FederationError AppIO [UserProfile] +lookupRemoteProfiles :: Remote [UserId] -> ExceptT FederationError (AppIO r) [UserProfile] lookupRemoteProfiles (qUntagged -> Qualified uids domain) = Federation.getUsersByIds domain uids @@ -1167,7 +1167,7 @@ lookupLocalProfiles :: Maybe UserId -> -- | The users ('others') for which to obtain the profiles. [UserId] -> - AppIO [UserProfile] + (AppIO r) [UserProfile] lookupLocalProfiles requestingUser others = do users <- Data.lookupUsers NoPendingInvitations others >>= mapM userGC css <- case requestingUser of @@ -1186,7 +1186,7 @@ lookupLocalProfiles requestingUser others = do toMap :: [ConnectionStatus] -> Map UserId Relation toMap = Map.fromList . map (csFrom &&& csStatus) - getSelfInfo :: UserId -> AppIO (Maybe (TeamId, Team.TeamMember)) + getSelfInfo :: UserId -> (AppIO r) (Maybe (TeamId, Team.TeamMember)) getSelfInfo selfId = do -- FUTUREWORK: it is an internal error for the two lookups (for 'User' and 'TeamMember') -- to return 'Nothing'. we could throw errors here if that happens, rather than just @@ -1206,10 +1206,10 @@ lookupLocalProfiles requestingUser others = do else publicProfile u userLegalHold in baseProfile {profileEmail = profileEmail'} -getLegalHoldStatus :: UserId -> AppIO (Maybe UserLegalHoldStatus) +getLegalHoldStatus :: UserId -> (AppIO r) (Maybe UserLegalHoldStatus) getLegalHoldStatus uid = traverse (getLegalHoldStatus' . accountUser) =<< lookupAccount uid -getLegalHoldStatus' :: User -> AppIO UserLegalHoldStatus +getLegalHoldStatus' :: User -> (AppIO r) UserLegalHoldStatus getLegalHoldStatus' user = case userTeam user of Nothing -> pure defUserLegalHoldStatus @@ -1241,7 +1241,7 @@ getEmailForProfile _ EmailVisibleToSelf' = Nothing -- | Find user accounts for a given identity, both activated and those -- currently pending activation. -lookupAccountsByIdentity :: Either Email Phone -> Bool -> AppIO [UserAccount] +lookupAccountsByIdentity :: Either Email Phone -> Bool -> (AppIO r) [UserAccount] lookupAccountsByIdentity emailOrPhone includePendingInvitations = do let uk = either userEmailKey userPhoneKey emailOrPhone activeUid <- Data.lookupKey uk @@ -1251,26 +1251,26 @@ lookupAccountsByIdentity emailOrPhone includePendingInvitations = do then pure result else pure $ filter ((/= PendingInvitation) . accountStatus) result -isBlacklisted :: Either Email Phone -> AppIO Bool +isBlacklisted :: Either Email Phone -> (AppIO r) Bool isBlacklisted emailOrPhone = do let uk = either userEmailKey userPhoneKey emailOrPhone Blacklist.exists uk -blacklistInsert :: Either Email Phone -> AppIO () +blacklistInsert :: Either Email Phone -> (AppIO r) () blacklistInsert emailOrPhone = do let uk = either userEmailKey userPhoneKey emailOrPhone Blacklist.insert uk -blacklistDelete :: Either Email Phone -> AppIO () +blacklistDelete :: Either Email Phone -> (AppIO r) () blacklistDelete emailOrPhone = do let uk = either userEmailKey userPhoneKey emailOrPhone Blacklist.delete uk -phonePrefixGet :: PhonePrefix -> AppIO [ExcludedPrefix] +phonePrefixGet :: PhonePrefix -> (AppIO r) [ExcludedPrefix] phonePrefixGet prefix = Blacklist.getAllPrefixes prefix -phonePrefixDelete :: PhonePrefix -> AppIO () +phonePrefixDelete :: PhonePrefix -> (AppIO r) () phonePrefixDelete = Blacklist.deletePrefix -phonePrefixInsert :: ExcludedPrefix -> AppIO () +phonePrefixInsert :: ExcludedPrefix -> (AppIO r) () phonePrefixInsert = Blacklist.insertPrefix diff --git a/services/brig/src/Brig/API/Util.hs b/services/brig/src/Brig/API/Util.hs index 3d4276d7f86..680b42fc6ae 100644 --- a/services/brig/src/Brig/API/Util.hs +++ b/services/brig/src/Brig/API/Util.hs @@ -23,9 +23,11 @@ module Brig.API.Util validateHandle, logEmail, traverseConcurrentlyWithErrors, + exceptTToMaybe, ) where +import Brig.API.Error import qualified Brig.API.Error as Error import Brig.API.Handler import Brig.API.Types @@ -46,15 +48,16 @@ import qualified System.Logger as Log import UnliftIO.Async import UnliftIO.Exception (throwIO, try) import Util.Logging (sha256String) +import Wire.API.ErrorDescription -lookupProfilesMaybeFilterSameTeamOnly :: UserId -> [UserProfile] -> Handler [UserProfile] +lookupProfilesMaybeFilterSameTeamOnly :: UserId -> [UserProfile] -> (Handler r) [UserProfile] lookupProfilesMaybeFilterSameTeamOnly self us = do selfTeam <- lift $ Data.lookupUserTeam self return $ case selfTeam of Just team -> filter (\x -> profileTeam x == Just team) us Nothing -> us -fetchUserIdentity :: UserId -> AppIO (Maybe UserIdentity) +fetchUserIdentity :: UserId -> (AppIO r) (Maybe UserIdentity) fetchUserIdentity uid = lookupSelfProfile uid >>= maybe @@ -62,13 +65,13 @@ fetchUserIdentity uid = (return . userIdentity . selfUser) -- | Obtain a profile for a user as he can see himself. -lookupSelfProfile :: UserId -> AppIO (Maybe SelfProfile) +lookupSelfProfile :: UserId -> (AppIO r) (Maybe SelfProfile) lookupSelfProfile = fmap (fmap mk) . Data.lookupAccount where mk a = SelfProfile (accountUser a) -validateHandle :: Text -> Handler Handle -validateHandle = maybe (throwE (Error.StdError Error.invalidHandle)) return . parseHandle +validateHandle :: Text -> (Handler r) Handle +validateHandle = maybe (throwE (Error.StdError (errorDescriptionTypeToWai @InvalidHandle))) return . parseHandle logEmail :: Email -> (Msg -> Msg) logEmail email = @@ -80,9 +83,12 @@ logInvitationCode code = Log.field "invitation_code" (toText $ fromInvitationCod -- | Traverse concurrently and fail on first error. traverseConcurrentlyWithErrors :: (Traversable t, Exception e) => - (a -> ExceptT e AppIO b) -> + (a -> ExceptT e (AppIO r) b) -> t a -> - ExceptT e AppIO (t b) + ExceptT e (AppIO r) (t b) traverseConcurrentlyWithErrors f = ExceptT . try . (traverse (either throwIO pure) =<<) . pooledMapConcurrentlyN 8 (runExceptT . f) + +exceptTToMaybe :: Monad m => ExceptT e m () -> m (Maybe e) +exceptTToMaybe = (pure . either Just (const Nothing)) <=< runExceptT diff --git a/services/brig/src/Brig/AWS/SesNotification.hs b/services/brig/src/Brig/AWS/SesNotification.hs index 1bcdccfc829..f4b10a0180f 100644 --- a/services/brig/src/Brig/AWS/SesNotification.hs +++ b/services/brig/src/Brig/AWS/SesNotification.hs @@ -29,27 +29,27 @@ import Imports import System.Logger.Class (field, msg, (~~)) import qualified System.Logger.Class as Log -onEvent :: SESNotification -> AppIO () +onEvent :: SESNotification -> (AppIO r) () onEvent (MailBounce BouncePermanent es) = onPermanentBounce es onEvent (MailBounce BounceTransient es) = onTransientBounce es onEvent (MailBounce BounceUndetermined es) = onUndeterminedBounce es onEvent (MailComplaint es) = onComplaint es -onPermanentBounce :: [Email] -> AppIO () +onPermanentBounce :: [Email] -> (AppIO r) () onPermanentBounce = mapM_ $ \e -> do logEmailEvent "Permanent bounce" e Blacklist.insert (userEmailKey e) -onTransientBounce :: [Email] -> AppIO () +onTransientBounce :: [Email] -> (AppIO r) () onTransientBounce = mapM_ (logEmailEvent "Transient bounce") -onUndeterminedBounce :: [Email] -> AppIO () +onUndeterminedBounce :: [Email] -> (AppIO r) () onUndeterminedBounce = mapM_ (logEmailEvent "Undetermined bounce") -onComplaint :: [Email] -> AppIO () +onComplaint :: [Email] -> (AppIO r) () onComplaint = mapM_ $ \e -> do logEmailEvent "Complaint" e Blacklist.insert (userEmailKey e) -logEmailEvent :: Text -> Email -> AppIO () +logEmailEvent :: Text -> Email -> (AppIO r) () logEmailEvent t e = Log.info $ field "email" (fromEmail e) ~~ msg t diff --git a/services/brig/src/Brig/App.hs b/services/brig/src/Brig/App.hs index dc4c9ecc3e2..cb48e47c77f 100644 --- a/services/brig/src/Brig/App.hs +++ b/services/brig/src/Brig/App.hs @@ -419,13 +419,13 @@ initCredentials secretFile = do dat <- loadSecret secretFile return $ either (\e -> error $ "Could not load secrets from " ++ show secretFile ++ ": " ++ e) id dat -userTemplates :: Monad m => Maybe Locale -> AppT m (Locale, UserTemplates) +userTemplates :: Monad m => Maybe Locale -> AppT r m (Locale, UserTemplates) userTemplates l = forLocale l <$> view usrTemplates -providerTemplates :: Monad m => Maybe Locale -> AppT m (Locale, ProviderTemplates) +providerTemplates :: Monad m => Maybe Locale -> AppT r m (Locale, ProviderTemplates) providerTemplates l = forLocale l <$> view provTemplates -teamTemplates :: Monad m => Maybe Locale -> AppT m (Locale, TeamTemplates) +teamTemplates :: Monad m => Maybe Locale -> AppT r m (Locale, TeamTemplates) teamTemplates l = forLocale l <$> view tmTemplates closeEnv :: Env -> IO () @@ -437,7 +437,7 @@ closeEnv e = do ------------------------------------------------------------------------------- -- App Monad -newtype AppT m a = AppT +newtype AppT r m a = AppT { unAppT :: ReaderT Env m a } deriving newtype @@ -454,58 +454,58 @@ newtype AppT m a = AppT ( Semigroup, Monoid ) - via (Ap (AppT m) a) + via (Ap (AppT r m) a) -type AppIO = AppT IO +type AppIO r = AppT r IO -instance MonadIO m => MonadLogger (AppT m) where +instance MonadIO m => MonadLogger (AppT r m) where log l m = do g <- view applog r <- view requestId Log.log g l $ field "request" (unRequestId r) ~~ m -instance MonadIO m => MonadLogger (ExceptT err (AppT m)) where +instance MonadIO m => MonadLogger (ExceptT err (AppT r m)) where log l m = lift (LC.log l m) -instance (Monad m, MonadIO m) => MonadHttp (AppT m) where +instance (Monad m, MonadIO m) => MonadHttp (AppT r m) where handleRequestWithCont req handler = do manager <- view httpManager liftIO $ withResponse req manager handler -instance MonadIO m => MonadZAuth (AppT m) where +instance MonadIO m => MonadZAuth (AppT r m) where liftZAuth za = view zauthEnv >>= \e -> runZAuth e za -instance MonadIO m => MonadZAuth (ExceptT err (AppT m)) where +instance MonadIO m => MonadZAuth (ExceptT err (AppT r m)) where liftZAuth = lift . liftZAuth -instance (MonadThrow m, MonadCatch m, MonadIO m) => MonadClient (AppT m) where +instance (MonadThrow m, MonadCatch m, MonadIO m) => MonadClient (AppT r m) where liftClient m = view casClient >>= \c -> runClient c m localState f = local (over casClient f) -instance MonadIndexIO AppIO where +instance MonadIndexIO (AppIO r) where liftIndexIO m = view indexEnv >>= \e -> runIndexIO e m -instance (MonadIndexIO (AppT m), Monad m) => MonadIndexIO (ExceptT err (AppT m)) where +instance (MonadIndexIO (AppT r m), Monad m) => MonadIndexIO (ExceptT err (AppT r m)) where liftIndexIO m = view indexEnv >>= \e -> runIndexIO e m -instance Monad m => HasRequestId (AppT m) where +instance Monad m => HasRequestId (AppT r m) where getRequestId = view requestId -instance MonadUnliftIO m => MonadUnliftIO (AppT m) where +instance MonadUnliftIO m => MonadUnliftIO (AppT r m) where withRunInIO inner = AppT . ReaderT $ \r -> withRunInIO $ \run -> inner (run . flip runReaderT r . unAppT) -runAppT :: Env -> AppT m a -> m a +runAppT :: Env -> AppT r m a -> m a runAppT e (AppT ma) = runReaderT ma e -runAppResourceT :: ResourceT AppIO a -> AppIO a +runAppResourceT :: ResourceT (AppIO r) a -> (AppIO r) a runAppResourceT ma = do e <- ask liftIO . runResourceT $ transResourceT (runAppT e) ma -forkAppIO :: Maybe UserId -> AppIO a -> AppIO () +forkAppIO :: Maybe UserId -> (AppIO r) a -> (AppIO r) () forkAppIO u ma = do a <- ask g <- view applog diff --git a/services/brig/src/Brig/Calling/API.hs b/services/brig/src/Brig/Calling/API.hs index 08ef593b8a3..561e494c953 100644 --- a/services/brig/src/Brig/Calling/API.hs +++ b/services/brig/src/Brig/Calling/API.hs @@ -32,6 +32,7 @@ import Brig.Calling import qualified Brig.Calling as Calling import Brig.Calling.Internal import Brig.Effects.SFT +import Brig.Options (ListAllSFTServers (..)) import qualified Brig.Options as Opt import Control.Error (hush) import Control.Lens @@ -61,7 +62,7 @@ import Wire.API.Call.Config (SFTServer) import qualified Wire.API.Call.Config as Public import Wire.Network.DNS.SRV (srvTarget) -routesPublic :: Routes Doc.ApiBuilder Handler () +routesPublic :: Routes Doc.ApiBuilder (Handler r) () routesPublic = do -- Deprecated endpoint, but still used by old clients. -- See https://github.com/zinfra/backend-issues/issues/1616 for context @@ -93,15 +94,16 @@ routesPublic = do Doc.returns (Doc.ref Public.modelRtcConfiguration) Doc.response 200 "RTCConfiguration" Doc.end -getCallsConfigV2H :: JSON ::: UserId ::: ConnId ::: Maybe (Range 1 10 Int) -> Handler Response +getCallsConfigV2H :: JSON ::: UserId ::: ConnId ::: Maybe (Range 1 10 Int) -> (Handler r) Response getCallsConfigV2H (_ ::: uid ::: connid ::: limit) = json <$> getCallsConfigV2 uid connid limit -- | ('UserId', 'ConnId' are required as args here to make sure this is an authenticated end-point.) -getCallsConfigV2 :: UserId -> ConnId -> Maybe (Range 1 10 Int) -> Handler Public.RTCConfiguration +getCallsConfigV2 :: UserId -> ConnId -> Maybe (Range 1 10 Int) -> (Handler r) Public.RTCConfiguration getCallsConfigV2 _ _ limit = do env <- liftIO . readIORef =<< view turnEnvV2 staticUrl <- view $ settings . Opt.sftStaticUrl + sftListAllServers <- fromMaybe Opt.HideAllSFTServers <$> view (settings . Opt.sftListAllServers) sftEnv' <- view sftEnv logger <- view applog manager <- view httpManager @@ -109,13 +111,13 @@ getCallsConfigV2 _ _ limit = do . runM @IO . runTinyLog logger . interpretSFT manager - $ newConfig env staticUrl sftEnv' limit CallsConfigV2 + $ newConfig env staticUrl sftEnv' limit sftListAllServers CallsConfigV2 -getCallsConfigH :: JSON ::: UserId ::: ConnId -> Handler Response +getCallsConfigH :: JSON ::: UserId ::: ConnId -> (Handler r) Response getCallsConfigH (_ ::: uid ::: connid) = json <$> getCallsConfig uid connid -getCallsConfig :: UserId -> ConnId -> Handler Public.RTCConfiguration +getCallsConfig :: UserId -> ConnId -> (Handler r) Public.RTCConfiguration getCallsConfig _ _ = do env <- liftIO . readIORef =<< view turnEnv logger <- view applog @@ -125,7 +127,7 @@ getCallsConfig _ _ = do . runM @IO . runTinyLog logger . interpretSFT manager - $ newConfig env Nothing Nothing Nothing CallsConfigDeprecated + $ newConfig env Nothing Nothing Nothing HideAllSFTServers CallsConfigDeprecated where -- In order to avoid being backwards incompatible, remove the `transport` query param from the URIs dropTransport :: Public.RTCConfiguration -> Public.RTCConfiguration @@ -149,9 +151,10 @@ newConfig :: Maybe HttpsUrl -> Maybe SFTEnv -> Maybe (Range 1 10 Int) -> + ListAllSFTServers -> CallsConfigVersion -> Sem r Public.RTCConfiguration -newConfig env sftStaticUrl mSftEnv limit version = do +newConfig env sftStaticUrl mSftEnv limit listAllServers version = do let (sha, secret, tTTL, cTTL, prng) = (env ^. turnSHA512, env ^. turnSecret, env ^. turnTokenTTL, env ^. turnConfigTTL, env ^. turnPrng) -- randomize list of servers (before limiting the list, to ensure not always the same servers are chosen if limit is set) randomizedUris <- liftIO $ randomize (List1.toNonEmpty $ env ^. turnServers) @@ -177,9 +180,10 @@ newConfig env sftStaticUrl mSftEnv limit version = do mSftServersAll :: Maybe [SFTServer] <- case version of CallsConfigDeprecated -> pure Nothing CallsConfigV2 -> - case sftStaticUrl of - Nothing -> pure . Just $ sftServerFromSrvTarget . srvTarget <$> maybe [] toList allSrvEntries - Just url -> hush . unSFTGetResponse <$> sftGetAllServers url + case (listAllServers, sftStaticUrl) of + (HideAllSFTServers, _) -> pure Nothing + (ListAllSFTServers, Nothing) -> pure . Just $ sftServerFromSrvTarget . srvTarget <$> maybe [] toList allSrvEntries + (ListAllSFTServers, Just url) -> hush . unSFTGetResponse <$> sftGetAllServers url let mSftServers = staticSft <|> sftServerFromSrvTarget . srvTarget <$$> srvEntries pure $ Public.rtcConfiguration srvs mSftServers cTTL mSftServersAll diff --git a/services/brig/src/Brig/Data/Activation.hs b/services/brig/src/Brig/Data/Activation.hs index 1f3b394214c..919f93df768 100644 --- a/services/brig/src/Brig/Data/Activation.hs +++ b/services/brig/src/Brig/Data/Activation.hs @@ -78,7 +78,7 @@ activateKey :: ActivationKey -> ActivationCode -> Maybe UserId -> - ExceptT ActivationError AppIO (Maybe ActivationEvent) + ExceptT ActivationError (AppIO r) (Maybe ActivationEvent) activateKey k c u = verifyCode k c >>= pickUser >>= activate where pickUser (uk, u') = maybe (throwE invalidUser) (return . (uk,)) (u <|> u') @@ -129,7 +129,7 @@ newActivation :: Timeout -> -- | The user with whom to associate the activation code. Maybe UserId -> - AppIO Activation + (AppIO r) Activation newActivation uk timeout u = do (typ, key, code) <- liftIO $ @@ -148,7 +148,7 @@ newActivation uk timeout u = do <$> randIntegerZeroToNMinusOne 1000000 -- | Lookup an activation code and it's associated owner (if any) for a 'UserKey'. -lookupActivationCode :: UserKey -> AppIO (Maybe (Maybe UserId, ActivationCode)) +lookupActivationCode :: UserKey -> (AppIO r) (Maybe (Maybe UserId, ActivationCode)) lookupActivationCode k = liftIO (mkActivationKey k) >>= retry x1 . query1 codeSelect . params LocalQuorum . Identity @@ -157,7 +157,7 @@ lookupActivationCode k = verifyCode :: ActivationKey -> ActivationCode -> - ExceptT ActivationError AppIO (UserKey, Maybe UserId) + ExceptT ActivationError (AppIO r) (UserKey, Maybe UserId) verifyCode key code = do s <- lift . retry x1 . query1 keySelect $ params LocalQuorum (Identity key) case s of @@ -185,7 +185,7 @@ mkActivationKey k = do let bs = digestBS d' (T.encodeUtf8 $ keyText k) return . ActivationKey $ Ascii.encodeBase64Url bs -deleteActivationPair :: ActivationKey -> AppIO () +deleteActivationPair :: ActivationKey -> (AppIO r) () deleteActivationPair = write keyDelete . params LocalQuorum . Identity invalidUser :: ActivationError diff --git a/services/brig/src/Brig/Data/Client.hs b/services/brig/src/Brig/Data/Client.hs index b6178a0a8d5..7b4d9ed1909 100644 --- a/services/brig/src/Brig/Data/Client.hs +++ b/services/brig/src/Brig/Data/Client.hs @@ -94,7 +94,7 @@ addClient :: Int -> Maybe Location -> Maybe (Imports.Set ClientCapability) -> - ExceptT ClientDataError AppIO (Client, [Client], Word) + ExceptT ClientDataError (AppIO r) (Client, [Client], Word) addClient u newId c maxPermClients loc cps = do clients <- lookupClients u let typed = filter ((== newClientType c) . clientType) clients @@ -120,7 +120,7 @@ addClient u newId c maxPermClients loc cps = do exists :: Client -> Bool exists = (==) newId . clientId - insert :: ExceptT ClientDataError AppIO Client + insert :: ExceptT ClientDataError (AppIO r) Client insert = do -- Is it possible to do this somewhere else? Otherwise we could use `MonadClient` instead now <- toUTCTimeMillis <$> (liftIO =<< view currentTime) @@ -184,7 +184,7 @@ lookupPrekeyIds u c = hasClient :: MonadClient m => UserId -> ClientId -> m Bool hasClient u d = isJust <$> retry x1 (query1 checkClient (params LocalQuorum (u, d))) -rmClient :: UserId -> ClientId -> AppIO () +rmClient :: UserId -> ClientId -> (AppIO r) () rmClient u c = do retry x5 $ write removeClient (params LocalQuorum (u, c)) retry x5 $ write removeClientKeys (params LocalQuorum (u, c)) @@ -212,7 +212,7 @@ updatePrekeys u c pks = do Success n -> return (CryptoBox.prekeyId n == keyId (prekeyId a)) _ -> return False -claimPrekey :: UserId -> ClientId -> AppIO (Maybe ClientPrekey) +claimPrekey :: UserId -> ClientId -> (AppIO r) (Maybe ClientPrekey) claimPrekey u c = view randomPrekeyLocalLock >>= \case -- Use random prekey selection strategy @@ -225,7 +225,7 @@ claimPrekey u c = prekey <- retry x1 $ query1 userPrekey (params LocalQuorum (u, c)) removeAndReturnPreKey prekey where - removeAndReturnPreKey :: Maybe (PrekeyId, Text) -> AppIO (Maybe ClientPrekey) + removeAndReturnPreKey :: Maybe (PrekeyId, Text) -> (AppIO r) (Maybe ClientPrekey) removeAndReturnPreKey (Just (i, k)) = do if i /= lastPrekeyId then retry x1 $ write removePrekey (params LocalQuorum (u, c, i)) @@ -237,7 +237,7 @@ claimPrekey u c = return $ Just (ClientPrekey c (Prekey i k)) removeAndReturnPreKey Nothing = return Nothing - pickRandomPrekey :: [(PrekeyId, Text)] -> AppIO (Maybe (PrekeyId, Text)) + pickRandomPrekey :: [(PrekeyId, Text)] -> (AppIO r) (Maybe (PrekeyId, Text)) pickRandomPrekey [] = return Nothing -- unless we only have one key left pickRandomPrekey [pk] = return $ Just pk @@ -330,13 +330,13 @@ ddbKey u c = AWS.attributeValue & AWS.avS ?~ UUID.toText (toUUID u) <> "." <> cl key :: UserId -> ClientId -> HashMap Text AWS.AttributeValue key u c = HashMap.singleton ddbClient (ddbKey u c) -deleteOptLock :: UserId -> ClientId -> AppIO () +deleteOptLock :: UserId -> ClientId -> (AppIO r) () deleteOptLock u c = do t <- view (awsEnv . prekeyTable) e <- view (awsEnv . amazonkaEnv) void $ exec e (AWS.deleteItem t & AWS.diKey .~ (key u c)) -withOptLock :: UserId -> ClientId -> AppIO a -> AppIO a +withOptLock :: forall effs a. UserId -> ClientId -> (AppIO effs) a -> (AppIO effs) a withOptLock u c ma = go (10 :: Int) where go !n = do @@ -372,30 +372,31 @@ withOptLock u c ma = go (10 :: Int) key u c toAttributeValue :: Word32 -> AWS.AttributeValue toAttributeValue w = AWS.attributeValue & AWS.avN ?~ AWS.toText (fromIntegral w :: Int) - reportAttemptFailure :: AppIO () + reportAttemptFailure :: (AppIO effs) () reportAttemptFailure = Metrics.counterIncr (Metrics.path "client.opt_lock.optimistic_lock_grab_attempt_failed") =<< view metrics - reportFailureAndLogError :: AppIO () + reportFailureAndLogError :: (AppIO effs) () reportFailureAndLogError = do Log.err $ Log.field "user" (toByteString' u) . Log.field "client" (toByteString' c) . msg (val "PreKeys: Optimistic lock failed") Metrics.counterIncr (Metrics.path "client.opt_lock.optimistic_lock_failed") =<< view metrics - execDyn :: (AWS.AWSRequest r) => (AWS.Rs r -> Maybe a) -> (Text -> r) -> AppIO (Maybe a) + execDyn :: forall r x. (AWS.AWSRequest r) => (AWS.Rs r -> Maybe x) -> (Text -> r) -> (AppIO effs) (Maybe x) execDyn cnv mkCmd = do cmd <- mkCmd <$> view (awsEnv . prekeyTable) e <- view (awsEnv . amazonkaEnv) m <- view metrics - execDyn' e m cnv cmd + liftIO $ execDyn' e m cnv cmd where execDyn' :: - (AWS.AWSRequest r, MonadUnliftIO m, MonadMask m, MonadIO m, Typeable m) => + forall y p. + AWS.AWSRequest p => AWS.Env -> Metrics.Metrics -> - (AWS.Rs r -> Maybe a) -> - r -> - m (Maybe a) + (AWS.Rs p -> Maybe y) -> + p -> + IO (Maybe y) execDyn' e m conv cmd = recovering policy handlers (const run) where run = execCatch e cmd >>= either handleErr (return . conv) @@ -406,6 +407,6 @@ withOptLock u c ma = go (10 :: Int) return Nothing handleErr _ = return Nothing -withLocalLock :: MVar () -> AppIO a -> AppIO a +withLocalLock :: MVar () -> (AppIO r) a -> (AppIO r) a withLocalLock l ma = do (takeMVar l *> ma) `finally` putMVar l () diff --git a/services/brig/src/Brig/Data/Connection.hs b/services/brig/src/Brig/Data/Connection.hs index 949a458f57e..fe43894b4f4 100644 --- a/services/brig/src/Brig/Data/Connection.hs +++ b/services/brig/src/Brig/Data/Connection.hs @@ -73,7 +73,7 @@ insertConnection :: Qualified UserId -> RelationWithHistory -> Qualified ConvId -> - AppIO UserConnection + (AppIO r) UserConnection insertConnection self target rel qcnv@(Qualified cnv cdomain) = do now <- toUTCTimeMillis <$> liftIO getCurrentTime let local (tUnqualified -> ltarget) = @@ -92,7 +92,7 @@ insertConnection self target rel qcnv@(Qualified cnv cdomain) = do ucConvId = Just qcnv } -updateConnection :: UserConnection -> RelationWithHistory -> AppIO UserConnection +updateConnection :: UserConnection -> RelationWithHistory -> (AppIO r) UserConnection updateConnection c status = do self <- qualifyLocal (ucFrom c) now <- updateConnectionStatus self (ucTo c) status @@ -102,7 +102,7 @@ updateConnection c status = do ucLastUpdate = now } -updateConnectionStatus :: Local UserId -> Qualified UserId -> RelationWithHistory -> AppIO UTCTimeMillis +updateConnectionStatus :: Local UserId -> Qualified UserId -> RelationWithHistory -> (AppIO r) UTCTimeMillis updateConnectionStatus self target status = do now <- toUTCTimeMillis <$> liftIO getCurrentTime let local (tUnqualified -> ltarget) = @@ -115,7 +115,7 @@ updateConnectionStatus self target status = do pure now -- | Lookup the connection from a user 'A' to a user 'B' (A -> B). -lookupConnection :: Local UserId -> Qualified UserId -> AppIO (Maybe UserConnection) +lookupConnection :: Local UserId -> Qualified UserId -> (AppIO r) (Maybe UserConnection) lookupConnection self target = runMaybeT $ do let local (tUnqualified -> ltarget) = do (_, _, rel, time, mcnv) <- @@ -143,7 +143,7 @@ lookupRelationWithHistory :: Local UserId -> -- | User 'B' Qualified UserId -> - AppIO (Maybe RelationWithHistory) + (AppIO r) (Maybe RelationWithHistory) lookupRelationWithHistory self target = do let local (tUnqualified -> ltarget) = query1 relationSelect (params LocalQuorum (tUnqualified self, ltarget)) @@ -151,14 +151,14 @@ lookupRelationWithHistory self target = do query1 remoteRelationSelect (params LocalQuorum (tUnqualified self, domain, rtarget)) runIdentity <$$> retry x1 (foldQualified self local remote target) -lookupRelation :: Local UserId -> Qualified UserId -> AppIO Relation +lookupRelation :: Local UserId -> Qualified UserId -> (AppIO r) Relation lookupRelation self target = lookupRelationWithHistory self target <&> \case Nothing -> Cancelled Just relh -> (relationDropHistory relh) -- | For a given user 'A', lookup their outgoing connections (A -> X) to other users. -lookupLocalConnections :: Local UserId -> Maybe UserId -> Range 1 500 Int32 -> AppIO (ResultPage UserConnection) +lookupLocalConnections :: Local UserId -> Maybe UserId -> Range 1 500 Int32 -> (AppIO r) (ResultPage UserConnection) lookupLocalConnections lfrom start (fromRange -> size) = toResult <$> case start of Just u -> @@ -196,48 +196,48 @@ lookupRemoteConnectionsPage self pagingState size = (paramsPagingState LocalQuorum (Identity (tUnqualified self)) size pagingState) -- | Lookup all relations between two sets of users (cartesian product). -lookupConnectionStatus :: [UserId] -> [UserId] -> AppIO [ConnectionStatus] +lookupConnectionStatus :: [UserId] -> [UserId] -> (AppIO r) [ConnectionStatus] lookupConnectionStatus from to = map toConnectionStatus <$> retry x1 (query connectionStatusSelect (params LocalQuorum (from, to))) -- | Lookup all relations between two sets of users (cartesian product). -lookupConnectionStatus' :: [UserId] -> AppIO [ConnectionStatus] +lookupConnectionStatus' :: [UserId] -> (AppIO r) [ConnectionStatus] lookupConnectionStatus' from = map toConnectionStatus <$> retry x1 (query connectionStatusSelect' (params LocalQuorum (Identity from))) -lookupLocalConnectionStatuses :: [UserId] -> Local [UserId] -> AppIO [ConnectionStatusV2] +lookupLocalConnectionStatuses :: [UserId] -> Local [UserId] -> (AppIO r) [ConnectionStatusV2] lookupLocalConnectionStatuses froms tos = do concat <$> pooledMapConcurrentlyN 16 lookupStatuses froms where - lookupStatuses :: UserId -> AppIO [ConnectionStatusV2] + lookupStatuses :: UserId -> (AppIO r) [ConnectionStatusV2] lookupStatuses from = map (uncurry $ toConnectionStatusV2 from (tDomain tos)) <$> retry x1 (query relationsSelect (params LocalQuorum (from, tUnqualified tos))) -lookupRemoteConnectionStatuses :: [UserId] -> Remote [UserId] -> AppIO [ConnectionStatusV2] +lookupRemoteConnectionStatuses :: [UserId] -> Remote [UserId] -> (AppIO r) [ConnectionStatusV2] lookupRemoteConnectionStatuses froms tos = do concat <$> pooledMapConcurrentlyN 16 lookupStatuses froms where - lookupStatuses :: UserId -> AppIO [ConnectionStatusV2] + lookupStatuses :: UserId -> (AppIO r) [ConnectionStatusV2] lookupStatuses from = map (uncurry $ toConnectionStatusV2 from (tDomain tos)) <$> retry x1 (query remoteRelationsSelect (params LocalQuorum (from, tDomain tos, tUnqualified tos))) -lookupAllStatuses :: Local [UserId] -> AppIO [ConnectionStatusV2] +lookupAllStatuses :: Local [UserId] -> (AppIO r) [ConnectionStatusV2] lookupAllStatuses lfroms = do let froms = tUnqualified lfroms concat <$> pooledMapConcurrentlyN 16 lookupAndCombine froms where - lookupAndCombine :: UserId -> AppIO [ConnectionStatusV2] + lookupAndCombine :: UserId -> (AppIO r) [ConnectionStatusV2] lookupAndCombine u = (<>) <$> lookupLocalStatuses u <*> lookupRemoteStatuses u - lookupLocalStatuses :: UserId -> AppIO [ConnectionStatusV2] + lookupLocalStatuses :: UserId -> (AppIO r) [ConnectionStatusV2] lookupLocalStatuses from = map (uncurry $ toConnectionStatusV2 from (tDomain lfroms)) <$> retry x1 (query relationsSelectAll (params LocalQuorum (Identity from))) - lookupRemoteStatuses :: UserId -> AppIO [ConnectionStatusV2] + lookupRemoteStatuses :: UserId -> (AppIO r) [ConnectionStatusV2] lookupRemoteStatuses from = map (\(d, u, r) -> toConnectionStatusV2 from d u r) <$> retry x1 (query remoteRelationsSelectAll (params LocalQuorum (Identity from))) @@ -248,20 +248,20 @@ lookupRemoteConnectedUsersC u maxResults = .| C.map (map (uncurry toRemoteUnsafe)) -- | See 'lookupContactListWithRelation'. -lookupContactList :: UserId -> AppIO [UserId] +lookupContactList :: UserId -> (AppIO r) [UserId] lookupContactList u = fst <$$> (filter ((== AcceptedWithHistory) . snd) <$> lookupContactListWithRelation u) -- | For a given user 'A', lookup the list of users that form his contact list, -- i.e. the users to whom 'A' has an outgoing 'Accepted' relation (A -> B). -lookupContactListWithRelation :: UserId -> AppIO [(UserId, RelationWithHistory)] +lookupContactListWithRelation :: UserId -> (AppIO r) [(UserId, RelationWithHistory)] lookupContactListWithRelation u = retry x1 (query contactsSelect (params LocalQuorum (Identity u))) -- | Count the number of connections a user has in a specific relation status. -- (If you want to distinguish 'RelationWithHistory', write a new function.) -- Note: The count is eventually consistent. -countConnections :: Local UserId -> [Relation] -> AppIO Int64 +countConnections :: Local UserId -> [Relation] -> (AppIO r) Int64 countConnections u r = do rels <- retry x1 . query selectStatus $ params One (Identity (tUnqualified u)) relsRemote <- retry x1 . query selectStatusRemote $ params One (Identity (tUnqualified u)) @@ -277,7 +277,7 @@ countConnections u r = do count n (Identity s) | (relationDropHistory s) `elem` r = n + 1 count n _ = n -deleteConnections :: UserId -> AppIO () +deleteConnections :: UserId -> (AppIO r) () deleteConnections u = do runConduit $ paginateC contactsSelect (paramsP LocalQuorum (Identity u) 100) x1 @@ -287,7 +287,7 @@ deleteConnections u = do where delete (other, _status) = write connectionDelete $ params LocalQuorum (other, u) -deleteRemoteConnections :: Remote UserId -> Range 1 1000 [UserId] -> AppIO () +deleteRemoteConnections :: Remote UserId -> Range 1 1000 [UserId] -> (AppIO r) () deleteRemoteConnections (qUntagged -> Qualified remoteUser remoteDomain) (fromRange -> locals) = pooledForConcurrentlyN_ 16 locals $ \u -> write remoteConnectionDelete $ params LocalQuorum (u, remoteDomain, remoteUser) diff --git a/services/brig/src/Brig/Data/LoginCode.hs b/services/brig/src/Brig/Data/LoginCode.hs index c4665d92324..d5627e7e81b 100644 --- a/services/brig/src/Brig/Data/LoginCode.hs +++ b/services/brig/src/Brig/Data/LoginCode.hs @@ -48,7 +48,7 @@ maxAttempts = 3 ttl :: NominalDiffTime ttl = 600 -createLoginCode :: UserId -> AppIO PendingLoginCode +createLoginCode :: UserId -> (AppIO r) PendingLoginCode createLoginCode u = do now <- liftIO =<< view currentTime code <- liftIO genCode @@ -57,7 +57,7 @@ createLoginCode u = do where genCode = LoginCode . T.pack . printf "%06d" <$> randIntegerZeroToNMinusOne 1000000 -verifyLoginCode :: UserId -> LoginCode -> AppIO Bool +verifyLoginCode :: UserId -> LoginCode -> (AppIO r) Bool verifyLoginCode u c = do code <- retry x1 (query1 codeSelect (params LocalQuorum (Identity u))) now <- liftIO =<< view currentTime @@ -67,7 +67,7 @@ verifyLoginCode u c = do Just (_, _, _) -> deleteLoginCode u >> return False Nothing -> return False -lookupLoginCode :: UserId -> AppIO (Maybe PendingLoginCode) +lookupLoginCode :: UserId -> (AppIO r) (Maybe PendingLoginCode) lookupLoginCode u = do now <- liftIO =<< view currentTime validate now =<< retry x1 (query1 codeSelect (params LocalQuorum (Identity u))) @@ -77,10 +77,10 @@ lookupLoginCode u = do pending c now t = PendingLoginCode c (timeout now t) timeout now t = Timeout (t `diffUTCTime` now) -deleteLoginCode :: UserId -> AppIO () +deleteLoginCode :: UserId -> (AppIO r) () deleteLoginCode u = retry x5 . write codeDelete $ params LocalQuorum (Identity u) -insertLoginCode :: UserId -> LoginCode -> Int32 -> UTCTime -> AppIO () +insertLoginCode :: UserId -> LoginCode -> Int32 -> UTCTime -> (AppIO r) () insertLoginCode u c n t = retry x5 . write codeInsert $ params LocalQuorum (u, c, n, t, round ttl) -- Queries diff --git a/services/brig/src/Brig/Data/PasswordReset.hs b/services/brig/src/Brig/Data/PasswordReset.hs index 98295204b17..942d14f4065 100644 --- a/services/brig/src/Brig/Data/PasswordReset.hs +++ b/services/brig/src/Brig/Data/PasswordReset.hs @@ -48,7 +48,7 @@ maxAttempts = 3 ttl :: NominalDiffTime ttl = 3600 -- 60 minutes -createPasswordResetCode :: UserId -> Either Email Phone -> AppIO PasswordResetPair +createPasswordResetCode :: UserId -> Either Email Phone -> (AppIO r) PasswordResetPair createPasswordResetCode u target = do key <- liftIO $ mkPasswordResetKey u now <- liftIO =<< view currentTime @@ -61,7 +61,7 @@ createPasswordResetCode u target = do PasswordResetCode . Ascii.unsafeFromText . pack . printf "%06d" <$> randIntegerZeroToNMinusOne 1000000 -lookupPasswordResetCode :: UserId -> AppIO (Maybe PasswordResetCode) +lookupPasswordResetCode :: UserId -> (AppIO r) (Maybe PasswordResetCode) lookupPasswordResetCode u = do key <- liftIO $ mkPasswordResetKey u now <- liftIO =<< view currentTime @@ -70,7 +70,7 @@ lookupPasswordResetCode u = do validate now (Just (c, _, _, Just t)) | t > now = return $ Just c validate _ _ = return Nothing -verifyPasswordResetCode :: PasswordResetPair -> AppIO (Maybe UserId) +verifyPasswordResetCode :: PasswordResetPair -> (AppIO r) (Maybe UserId) verifyPasswordResetCode (k, c) = do now <- liftIO =<< view currentTime code <- retry x1 (query1 codeSelect (params LocalQuorum (Identity k))) @@ -84,7 +84,7 @@ verifyPasswordResetCode (k, c) = do where countdown = retry x5 . write codeInsert . params LocalQuorum -deletePasswordResetCode :: PasswordResetKey -> AppIO () +deletePasswordResetCode :: PasswordResetKey -> (AppIO r) () deletePasswordResetCode k = retry x5 . write codeDelete $ params LocalQuorum (Identity k) mkPasswordResetKey :: (MonadIO m) => UserId -> m PasswordResetKey diff --git a/services/brig/src/Brig/Data/Properties.hs b/services/brig/src/Brig/Data/Properties.hs index 822df183aa2..5ec00292621 100644 --- a/services/brig/src/Brig/Data/Properties.hs +++ b/services/brig/src/Brig/Data/Properties.hs @@ -40,30 +40,30 @@ maxProperties = 16 data PropertiesDataError = TooManyProperties -insertProperty :: UserId -> PropertyKey -> PropertyValue -> ExceptT PropertiesDataError AppIO () +insertProperty :: UserId -> PropertyKey -> PropertyValue -> ExceptT PropertiesDataError (AppIO r) () insertProperty u k v = do n <- lift . fmap (maybe 0 runIdentity) . retry x1 $ query1 propertyCount (params LocalQuorum (Identity u)) unless (n < maxProperties) $ throwE TooManyProperties lift . retry x5 $ write propertyInsert (params LocalQuorum (u, k, v)) -deleteProperty :: UserId -> PropertyKey -> AppIO () +deleteProperty :: UserId -> PropertyKey -> (AppIO r) () deleteProperty u k = retry x5 $ write propertyDelete (params LocalQuorum (u, k)) -clearProperties :: UserId -> AppIO () +clearProperties :: UserId -> (AppIO r) () clearProperties u = retry x5 $ write propertyReset (params LocalQuorum (Identity u)) -lookupProperty :: UserId -> PropertyKey -> AppIO (Maybe PropertyValue) +lookupProperty :: UserId -> PropertyKey -> (AppIO r) (Maybe PropertyValue) lookupProperty u k = fmap runIdentity <$> retry x1 (query1 propertySelect (params LocalQuorum (u, k))) -lookupPropertyKeys :: UserId -> AppIO [PropertyKey] +lookupPropertyKeys :: UserId -> (AppIO r) [PropertyKey] lookupPropertyKeys u = map runIdentity <$> retry x1 (query propertyKeysSelect (params LocalQuorum (Identity u))) -lookupPropertyKeysAndValues :: UserId -> AppIO PropertyKeysAndValues +lookupPropertyKeysAndValues :: UserId -> (AppIO r) PropertyKeysAndValues lookupPropertyKeysAndValues u = PropertyKeysAndValues <$> retry x1 (query propertyKeysValuesSelect (params LocalQuorum (Identity u))) diff --git a/services/brig/src/Brig/Data/User.hs b/services/brig/src/Brig/Data/User.hs index 55e4c7fd00e..3959dfc3fb0 100644 --- a/services/brig/src/Brig/Data/User.hs +++ b/services/brig/src/Brig/Data/User.hs @@ -116,7 +116,7 @@ data ReAuthError -- Condition (2.) is essential for maintaining handle uniqueness. It is guaranteed by the -- fact that we're setting getting @mbHandle@ from table @"user"@, and when/if it was added -- there, it was claimed properly. -newAccount :: NewUser -> Maybe InvitationId -> Maybe TeamId -> Maybe Handle -> AppIO (UserAccount, Maybe Password) +newAccount :: NewUser -> Maybe InvitationId -> Maybe TeamId -> Maybe Handle -> (AppIO r) (UserAccount, Maybe Password) newAccount u inv tid mbHandle = do defLoc <- setDefaultUserLocale <$> view settings domain <- viewFederationDomain @@ -152,7 +152,7 @@ newAccount u inv tid mbHandle = do managedBy = fromMaybe defaultManagedBy (newUserManagedBy u) user uid domain l e = User uid (Qualified uid domain) ident name pict assets colour False l Nothing mbHandle e tid managedBy -newAccountInviteViaScim :: UserId -> TeamId -> Maybe Locale -> Name -> Email -> AppIO UserAccount +newAccountInviteViaScim :: UserId -> TeamId -> Maybe Locale -> Name -> Email -> (AppIO r) UserAccount newAccountInviteViaScim uid tid locale name email = do defLoc <- setDefaultUserLocale <$> view settings domain <- viewFederationDomain @@ -176,7 +176,7 @@ newAccountInviteViaScim uid tid locale name email = do ManagedByScim -- | Mandatory password authentication. -authenticate :: UserId -> PlainTextPassword -> ExceptT AuthError AppIO () +authenticate :: UserId -> PlainTextPassword -> ExceptT AuthError (AppIO r) () authenticate u pw = lift (lookupAuth u) >>= \case Nothing -> throwE AuthInvalidUser @@ -217,7 +217,7 @@ insertAccount :: Maybe Password -> -- | Whether the user is activated Bool -> - AppIO () + (AppIO r) () insertAccount (UserAccount u status) mbConv password activated = retry x5 . batch $ do setType BatchLogged setConsistency LocalQuorum @@ -260,10 +260,10 @@ insertAccount (UserAccount u status) mbConv password activated = retry x5 . batc "INSERT INTO service_team (provider, service, user, conv, team) \ \VALUES (?, ?, ?, ?, ?)" -updateLocale :: UserId -> Locale -> AppIO () +updateLocale :: UserId -> Locale -> (AppIO r) () updateLocale u (Locale l c) = write userLocaleUpdate (params LocalQuorum (l, c, u)) -updateUser :: UserId -> UserUpdate -> AppIO () +updateUser :: UserId -> UserUpdate -> (AppIO r) () updateUser u UserUpdate {..} = retry x5 . batch $ do setType BatchLogged setConsistency LocalQuorum @@ -272,13 +272,13 @@ updateUser u UserUpdate {..} = retry x5 . batch $ do for_ uupAssets $ \a -> addPrepQuery userAssetsUpdate (a, u) for_ uupAccentId $ \c -> addPrepQuery userAccentIdUpdate (c, u) -updateEmail :: UserId -> Email -> AppIO () +updateEmail :: UserId -> Email -> (AppIO r) () updateEmail u e = retry x5 $ write userEmailUpdate (params LocalQuorum (e, u)) -updatePhone :: UserId -> Phone -> AppIO () +updatePhone :: UserId -> Phone -> (AppIO r) () updatePhone u p = retry x5 $ write userPhoneUpdate (params LocalQuorum (p, u)) -updateSSOId :: UserId -> Maybe UserSSOId -> AppIO Bool +updateSSOId :: UserId -> Maybe UserSSOId -> (AppIO r) Bool updateSSOId u ssoid = do mteamid <- lookupUserTeam u case mteamid of @@ -287,21 +287,21 @@ updateSSOId u ssoid = do pure True Nothing -> pure False -updateManagedBy :: UserId -> ManagedBy -> AppIO () +updateManagedBy :: UserId -> ManagedBy -> (AppIO r) () updateManagedBy u h = retry x5 $ write userManagedByUpdate (params LocalQuorum (h, u)) -updateHandle :: UserId -> Handle -> AppIO () +updateHandle :: UserId -> Handle -> (AppIO r) () updateHandle u h = retry x5 $ write userHandleUpdate (params LocalQuorum (h, u)) -updatePassword :: UserId -> PlainTextPassword -> AppIO () +updatePassword :: UserId -> PlainTextPassword -> (AppIO r) () updatePassword u t = do p <- liftIO $ mkSafePassword t retry x5 $ write userPasswordUpdate (params LocalQuorum (p, u)) -updateRichInfo :: UserId -> RichInfoAssocList -> AppIO () +updateRichInfo :: UserId -> RichInfoAssocList -> (AppIO r) () updateRichInfo u ri = retry x5 $ write userRichInfoUpdate (params LocalQuorum (ri, u)) -updateFeatureConferenceCalling :: UserId -> Maybe ApiFt.TeamFeatureStatusNoConfig -> AppIO (Maybe ApiFt.TeamFeatureStatusNoConfig) +updateFeatureConferenceCalling :: UserId -> Maybe ApiFt.TeamFeatureStatusNoConfig -> (AppIO r) (Maybe ApiFt.TeamFeatureStatusNoConfig) updateFeatureConferenceCalling uid mbStatus = do let flag = ApiFt.tfwoStatus <$> mbStatus retry x5 $ write update (params LocalQuorum (flag, uid)) @@ -310,13 +310,13 @@ updateFeatureConferenceCalling uid mbStatus = do update :: PrepQuery W (Maybe ApiFt.TeamFeatureStatusValue, UserId) () update = fromString $ "update user set feature_conference_calling = ? where id = ?" -deleteEmail :: UserId -> AppIO () +deleteEmail :: UserId -> (AppIO r) () deleteEmail u = retry x5 $ write userEmailDelete (params LocalQuorum (Identity u)) -deletePhone :: UserId -> AppIO () +deletePhone :: UserId -> (AppIO r) () deletePhone u = retry x5 $ write userPhoneDelete (params LocalQuorum (Identity u)) -deleteServiceUser :: ProviderId -> ServiceId -> BotId -> AppIO () +deleteServiceUser :: ProviderId -> ServiceId -> BotId -> (AppIO r) () deleteServiceUser pid sid bid = do lookupServiceUser pid sid bid >>= \case Nothing -> pure () @@ -336,17 +336,17 @@ deleteServiceUser pid sid bid = do "DELETE FROM service_team \ \WHERE provider = ? AND service = ? AND team = ? AND user = ?" -updateStatus :: UserId -> AccountStatus -> AppIO () +updateStatus :: UserId -> AccountStatus -> (AppIO r) () updateStatus u s = retry x5 $ write userStatusUpdate (params LocalQuorum (s, u)) -- | Whether the account has been activated by verifying -- an email address or phone number. -isActivated :: UserId -> AppIO Bool +isActivated :: UserId -> (AppIO r) Bool isActivated u = (== Just (Identity True)) <$> retry x1 (query1 activatedSelect (params LocalQuorum (Identity u))) -filterActive :: [UserId] -> AppIO [UserId] +filterActive :: [UserId] -> (AppIO r) [UserId] filterActive us = map (view _1) . filter isActiveUser <$> retry x1 (query accountStateSelectAll (params LocalQuorum (Identity us))) @@ -355,46 +355,46 @@ filterActive us = isActiveUser (_, True, Just Active) = True isActiveUser _ = False -lookupUser :: HavePendingInvitations -> UserId -> AppIO (Maybe User) +lookupUser :: HavePendingInvitations -> UserId -> (AppIO r) (Maybe User) lookupUser hpi u = listToMaybe <$> lookupUsers hpi [u] -activateUser :: UserId -> UserIdentity -> AppIO () +activateUser :: UserId -> UserIdentity -> (AppIO r) () activateUser u ident = do let email = emailIdentity ident let phone = phoneIdentity ident retry x5 $ write userActivatedUpdate (params LocalQuorum (email, phone, u)) -deactivateUser :: UserId -> AppIO () +deactivateUser :: UserId -> (AppIO r) () deactivateUser u = retry x5 $ write userDeactivatedUpdate (params LocalQuorum (Identity u)) -lookupLocale :: UserId -> AppIO (Maybe Locale) +lookupLocale :: UserId -> (AppIO r) (Maybe Locale) lookupLocale u = do defLoc <- setDefaultUserLocale <$> view settings fmap (toLocale defLoc) <$> retry x1 (query1 localeSelect (params LocalQuorum (Identity u))) -lookupName :: UserId -> AppIO (Maybe Name) +lookupName :: UserId -> (AppIO r) (Maybe Name) lookupName u = fmap runIdentity <$> retry x1 (query1 nameSelect (params LocalQuorum (Identity u))) -lookupPassword :: UserId -> AppIO (Maybe Password) +lookupPassword :: UserId -> (AppIO r) (Maybe Password) lookupPassword u = join . fmap runIdentity <$> retry x1 (query1 passwordSelect (params LocalQuorum (Identity u))) -lookupStatus :: UserId -> AppIO (Maybe AccountStatus) +lookupStatus :: UserId -> (AppIO r) (Maybe AccountStatus) lookupStatus u = join . fmap runIdentity <$> retry x1 (query1 statusSelect (params LocalQuorum (Identity u))) -lookupRichInfo :: UserId -> AppIO (Maybe RichInfoAssocList) +lookupRichInfo :: UserId -> (AppIO r) (Maybe RichInfoAssocList) lookupRichInfo u = fmap runIdentity <$> retry x1 (query1 richInfoSelect (params LocalQuorum (Identity u))) -- | Returned rich infos are in the same order as users -lookupRichInfoMultiUsers :: [UserId] -> AppIO [(UserId, RichInfo)] +lookupRichInfoMultiUsers :: [UserId] -> (AppIO r) [(UserId, RichInfo)] lookupRichInfoMultiUsers users = do mapMaybe (\(uid, mbRi) -> (uid,) . RichInfo <$> mbRi) <$> retry x1 (query richInfoSelectMulti (params LocalQuorum (Identity users))) @@ -402,7 +402,7 @@ lookupRichInfoMultiUsers users = do -- | Lookup user (no matter what status) and return 'TeamId'. Safe to use for authorization: -- suspended / deleted / ... users can't login, so no harm done if we authorize them *after* -- successful login. -lookupUserTeam :: UserId -> AppIO (Maybe TeamId) +lookupUserTeam :: UserId -> (AppIO r) (Maybe TeamId) lookupUserTeam u = (runIdentity =<<) <$> retry x1 (query1 teamSelect (params LocalQuorum (Identity u))) @@ -415,22 +415,22 @@ lookupAuth u = fmap f <$> retry x1 (query1 authSelect (params LocalQuorum (Ident -- | Return users with given IDs. -- -- Skips nonexistent users. /Does not/ skip users who have been deleted. -lookupUsers :: HavePendingInvitations -> [UserId] -> AppIO [User] +lookupUsers :: HavePendingInvitations -> [UserId] -> (AppIO r) [User] lookupUsers hpi usrs = do loc <- setDefaultUserLocale <$> view settings domain <- viewFederationDomain toUsers domain loc hpi <$> retry x1 (query usersSelect (params LocalQuorum (Identity usrs))) -lookupAccount :: UserId -> AppIO (Maybe UserAccount) +lookupAccount :: UserId -> (AppIO r) (Maybe UserAccount) lookupAccount u = listToMaybe <$> lookupAccounts [u] -lookupAccounts :: [UserId] -> AppIO [UserAccount] +lookupAccounts :: [UserId] -> (AppIO r) [UserAccount] lookupAccounts usrs = do loc <- setDefaultUserLocale <$> view settings domain <- viewFederationDomain fmap (toUserAccount domain loc) <$> retry x1 (query accountsSelect (params LocalQuorum (Identity usrs))) -lookupServiceUser :: ProviderId -> ServiceId -> BotId -> AppIO (Maybe (ConvId, Maybe TeamId)) +lookupServiceUser :: ProviderId -> ServiceId -> BotId -> (AppIO r) (Maybe (ConvId, Maybe TeamId)) lookupServiceUser pid sid bid = retry x1 (query1 cql (params LocalQuorum (pid, sid, bid))) where cql :: PrepQuery R (ProviderId, ServiceId, BotId) (ConvId, Maybe TeamId) @@ -442,7 +442,7 @@ lookupServiceUser pid sid bid = retry x1 (query1 cql (params LocalQuorum (pid, s lookupServiceUsers :: ProviderId -> ServiceId -> - ConduitM () [(BotId, ConvId, Maybe TeamId)] AppIO () + ConduitM () [(BotId, ConvId, Maybe TeamId)] (AppIO r) () lookupServiceUsers pid sid = paginateC cql (paramsP LocalQuorum (pid, sid) 100) x1 where @@ -455,7 +455,7 @@ lookupServiceUsersForTeam :: ProviderId -> ServiceId -> TeamId -> - ConduitM () [(BotId, ConvId)] AppIO () + ConduitM () [(BotId, ConvId)] (AppIO r) () lookupServiceUsersForTeam pid sid tid = paginateC cql (paramsP LocalQuorum (pid, sid, tid) 100) x1 where diff --git a/services/brig/src/Brig/Data/UserKey.hs b/services/brig/src/Brig/Data/UserKey.hs index c8e08659216..f4483c09b9b 100644 --- a/services/brig/src/Brig/Data/UserKey.hs +++ b/services/brig/src/Brig/Data/UserKey.hs @@ -124,7 +124,7 @@ claimKey :: UserKey -> -- | The user claiming the key. UserId -> - AppIO Bool + (AppIO r) Bool claimKey k u = do free <- keyAvailable k (Just u) when free (insertKey u k) @@ -138,7 +138,7 @@ keyAvailable :: UserKey -> -- | The user looking to claim the key, if any. Maybe UserId -> - AppIO Bool + (AppIO r) Bool keyAvailable k u = do o <- lookupKey k case (o, u) of @@ -146,32 +146,32 @@ keyAvailable k u = do (Just x, Just y) | x == y -> return True (Just x, _) -> not <$> User.isActivated x -lookupKey :: UserKey -> AppIO (Maybe UserId) +lookupKey :: UserKey -> (AppIO r) (Maybe UserId) lookupKey k = fmap runIdentity <$> retry x1 (query1 keySelect (params LocalQuorum (Identity $ keyText k))) -insertKey :: UserId -> UserKey -> AppIO () +insertKey :: UserId -> UserKey -> (AppIO r) () insertKey u k = do hk <- hashKey k let kt = foldKey (\(_ :: Email) -> UKHashEmail) (\(_ :: Phone) -> UKHashPhone) k retry x5 $ write insertHashed (params LocalQuorum (hk, kt, u)) retry x5 $ write keyInsert (params LocalQuorum (keyText k, u)) -deleteKey :: UserKey -> AppIO () +deleteKey :: UserKey -> (AppIO r) () deleteKey k = do hk <- hashKey k retry x5 $ write deleteHashed (params LocalQuorum (Identity hk)) retry x5 $ write keyDelete (params LocalQuorum (Identity $ keyText k)) -hashKey :: UserKey -> AppIO UserKeyHash +hashKey :: UserKey -> (AppIO r) UserKeyHash hashKey uk = do d <- view digestSHA256 let d' = digestBS d $ T.encodeUtf8 (keyText uk) return . UserKeyHash $ MH.MultihashDigest MH.SHA256 (B.length d') d' -lookupPhoneHashes :: [ByteString] -> AppIO [(ByteString, UserId)] +lookupPhoneHashes :: [ByteString] -> (AppIO r) [(ByteString, UserId)] lookupPhoneHashes hp = mapMaybe mk <$> retry x1 (query selectHashed (params One (Identity hashed))) where diff --git a/services/brig/src/Brig/Data/UserPendingActivation.hs b/services/brig/src/Brig/Data/UserPendingActivation.hs index 4a11f83c1d6..2f1222194d7 100644 --- a/services/brig/src/Brig/Data/UserPendingActivation.hs +++ b/services/brig/src/Brig/Data/UserPendingActivation.hs @@ -38,14 +38,14 @@ data UserPendingActivation = UserPendingActivation } deriving stock (Eq, Show, Ord) -usersPendingActivationAdd :: UserPendingActivation -> AppIO () +usersPendingActivationAdd :: UserPendingActivation -> (AppIO r) () usersPendingActivationAdd (UserPendingActivation uid expiresAt) = do retry x5 . write insertExpiration . params LocalQuorum $ (uid, expiresAt) where insertExpiration :: PrepQuery W (UserId, UTCTime) () insertExpiration = "INSERT INTO users_pending_activation (user, expires_at) VALUES (?, ?)" -usersPendingActivationList :: AppIO (Page UserPendingActivation) +usersPendingActivationList :: (AppIO r) (Page UserPendingActivation) usersPendingActivationList = do uncurry UserPendingActivation <$$> retry x1 (paginate selectExpired (params LocalQuorum ())) where @@ -53,10 +53,10 @@ usersPendingActivationList = do selectExpired = "SELECT user, expires_at FROM users_pending_activation" -usersPendingActivationRemove :: UserId -> AppIO () +usersPendingActivationRemove :: UserId -> (AppIO r) () usersPendingActivationRemove uid = usersPendingActivationRemoveMultiple [uid] -usersPendingActivationRemoveMultiple :: [UserId] -> AppIO () +usersPendingActivationRemoveMultiple :: [UserId] -> (AppIO r) () usersPendingActivationRemoveMultiple uids = retry x5 . write deleteExpired . params LocalQuorum $ (Identity uids) where diff --git a/services/brig/src/Brig/Email.hs b/services/brig/src/Brig/Email.hs index fa6253c1eb6..a411875b493 100644 --- a/services/brig/src/Brig/Email.hs +++ b/services/brig/src/Brig/Email.hs @@ -49,7 +49,7 @@ import Imports import Network.Mail.Mime ------------------------------------------------------------------------------- -sendMail :: Mail -> AppIO () +sendMail :: Mail -> (AppIO r) () sendMail m = view smtpEnv >>= \case Just smtp -> SMTP.sendMail smtp m diff --git a/services/brig/src/Brig/Federation/Client.hs b/services/brig/src/Brig/Federation/Client.hs index 0e0a8cf91bd..ade024d4442 100644 --- a/services/brig/src/Brig/Federation/Client.hs +++ b/services/brig/src/Brig/Federation/Client.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} @@ -50,24 +51,24 @@ import Wire.API.User.Client (UserClientPrekeyMap) import Wire.API.User.Client.Prekey (ClientPrekey) import Wire.API.UserMap (UserMap) -type FederationAppIO = ExceptT FederationError AppIO +type FederationAppIO r = ExceptT FederationError (AppIO r) -getUserHandleInfo :: Remote Handle -> FederationAppIO (Maybe UserProfile) +getUserHandleInfo :: Remote Handle -> (FederationAppIO r) (Maybe UserProfile) getUserHandleInfo (qUntagged -> Qualified handle domain) = do Log.info $ Log.msg $ T.pack "Brig-federation: handle lookup call on remote backend" executeFederated @"get-user-by-handle" domain handle -getUsersByIds :: Domain -> [UserId] -> FederationAppIO [UserProfile] +getUsersByIds :: Domain -> [UserId] -> (FederationAppIO r) [UserProfile] getUsersByIds domain uids = do Log.info $ Log.msg ("Brig-federation: get users by ids on remote backends" :: ByteString) executeFederated @"get-users-by-ids" domain uids -claimPrekey :: Qualified UserId -> ClientId -> FederationAppIO (Maybe ClientPrekey) +claimPrekey :: Qualified UserId -> ClientId -> (FederationAppIO r) (Maybe ClientPrekey) claimPrekey (Qualified user domain) client = do Log.info $ Log.msg @Text "Brig-federation: claiming remote prekey" executeFederated @"claim-prekey" domain (user, client) -claimPrekeyBundle :: Qualified UserId -> FederationAppIO PrekeyBundle +claimPrekeyBundle :: Qualified UserId -> (FederationAppIO r) PrekeyBundle claimPrekeyBundle (Qualified user domain) = do Log.info $ Log.msg @Text "Brig-federation: claiming remote prekey bundle" executeFederated @"claim-prekey-bundle" domain user @@ -75,17 +76,17 @@ claimPrekeyBundle (Qualified user domain) = do claimMultiPrekeyBundle :: Domain -> UserClients -> - FederationAppIO UserClientPrekeyMap + (FederationAppIO r) UserClientPrekeyMap claimMultiPrekeyBundle domain uc = do Log.info $ Log.msg @Text "Brig-federation: claiming remote multi-user prekey bundle" executeFederated @"claim-multi-prekey-bundle" domain uc -searchUsers :: Domain -> SearchRequest -> FederationAppIO [Public.Contact] +searchUsers :: Domain -> SearchRequest -> (FederationAppIO r) [Public.Contact] searchUsers domain searchTerm = do - Log.warn $ Log.msg $ T.pack "Brig-federation: search call on remote backend" + Log.info $ Log.msg $ T.pack "Brig-federation: search call on remote backend" executeFederated @"search-users" domain searchTerm -getUserClients :: Domain -> GetUserClients -> FederationAppIO (UserMap (Set PubClient)) +getUserClients :: Domain -> GetUserClients -> (FederationAppIO r) (UserMap (Set PubClient)) getUserClients domain guc = do Log.info $ Log.msg @Text "Brig-federation: get users' clients from remote backend" executeFederated @"get-user-clients" domain guc @@ -94,7 +95,7 @@ sendConnectionAction :: Local UserId -> Remote UserId -> RemoteConnectionAction -> - FederationAppIO NewConnectionResponse + (FederationAppIO r) NewConnectionResponse sendConnectionAction self (qUntagged -> other) action = do let req = NewConnectionRequest (tUnqualified self) (qUnqualified other) action Log.info $ Log.msg @Text "Brig-federation: sending connection action to remote backend" @@ -103,14 +104,14 @@ sendConnectionAction self (qUntagged -> other) action = do notifyUserDeleted :: Local UserId -> Remote (Range 1 1000 [UserId]) -> - FederationAppIO () + (FederationAppIO r) () notifyUserDeleted self remotes = do let remoteConnections = tUnqualified remotes void $ executeFederated @"on-user-deleted-connections" (tDomain remotes) $ UserDeletedConnectionsNotification (tUnqualified self) remoteConnections -runBrigFederatorClient :: Domain -> FederatorClient 'Brig a -> FederationAppIO a +runBrigFederatorClient :: Domain -> FederatorClient 'Brig a -> (FederationAppIO r) a runBrigFederatorClient targetDomain action = do ownDomain <- viewFederationDomain endpoint <- view federator >>= maybe (throwE FederationNotConfigured) pure @@ -124,13 +125,13 @@ runBrigFederatorClient targetDomain action = do >>= either (throwE . FederationCallFailure) pure executeFederated :: - forall (name :: Symbol) api. + forall (name :: Symbol) api r. ( HasFedEndpoint 'Brig api name, HasClient ClientM api, HasClient (FederatorClient 'Brig) api ) => Domain -> - Client FederationAppIO api + Client (FederationAppIO r) api executeFederated domain = - hoistClient (Proxy @api) (runBrigFederatorClient domain) $ + hoistClient (Proxy @api) (runBrigFederatorClient @_ @r domain) $ clientIn (Proxy @api) (Proxy @(FederatorClient 'Brig)) diff --git a/services/brig/src/Brig/IO/Intra.hs b/services/brig/src/Brig/IO/Intra.hs index 5568bb9171a..2aa0c9cdd88 100644 --- a/services/brig/src/Brig/IO/Intra.hs +++ b/services/brig/src/Brig/IO/Intra.hs @@ -127,7 +127,7 @@ import qualified Wire.API.Team.Member as Member ----------------------------------------------------------------------------- -- Event Handlers -onUserEvent :: UserId -> Maybe ConnId -> UserEvent -> AppIO () +onUserEvent :: UserId -> Maybe ConnId -> UserEvent -> (AppIO r) () onUserEvent orig conn e = updateSearchIndex orig e *> dispatchNotifications orig conn e @@ -140,7 +140,7 @@ onConnectionEvent :: Maybe ConnId -> -- | The event. ConnectionEvent -> - AppIO () + (AppIO r) () onConnectionEvent orig conn evt = do let from = ucFrom (ucConn evt) notify @@ -156,7 +156,7 @@ onPropertyEvent :: -- | Client connection ID. ConnId -> PropertyEvent -> - AppIO () + (AppIO r) () onPropertyEvent orig conn e = notify (singleton $ PropertyEvent e) @@ -172,7 +172,7 @@ onClientEvent :: Maybe ConnId -> -- | The event. ClientEvent -> - AppIO () + (AppIO r) () onClientEvent orig conn e = do let events = singleton (ClientEvent e) let rcps = list1 orig [] @@ -181,7 +181,7 @@ onClientEvent orig conn e = do -- in the stream. push events rcps orig Push.RouteAny conn -updateSearchIndex :: UserId -> UserEvent -> AppIO () +updateSearchIndex :: UserId -> UserEvent -> (AppIO r) () updateSearchIndex orig e = case e of -- no-ops UserCreated {} -> return () @@ -206,7 +206,7 @@ updateSearchIndex orig e = case e of ] when interesting $ Search.reindex orig -journalEvent :: UserId -> UserEvent -> AppIO () +journalEvent :: UserId -> UserEvent -> (AppIO r) () journalEvent orig e = case e of UserActivated acc -> Journal.userActivate acc @@ -229,7 +229,7 @@ journalEvent orig e = case e of -- | Notify the origin user's contact list (first-level contacts), -- as well as his other clients about a change to his user account -- or profile. -dispatchNotifications :: UserId -> Maybe ConnId -> UserEvent -> AppIO () +dispatchNotifications :: UserId -> Maybe ConnId -> UserEvent -> (AppIO r) () dispatchNotifications orig conn e = case e of UserCreated {} -> return () UserSuspended {} -> return () @@ -252,21 +252,21 @@ dispatchNotifications orig conn e = case e of where event = singleton $ UserEvent e -notifyUserDeletionLocals :: UserId -> Maybe ConnId -> List1 Event -> AppIO () +notifyUserDeletionLocals :: UserId -> Maybe ConnId -> List1 Event -> (AppIO r) () notifyUserDeletionLocals deleted conn event = do recipients <- list1 deleted <$> lookupContactList deleted notify event deleted Push.RouteDirect conn (pure recipients) -notifyUserDeletionRemotes :: UserId -> AppIO () +notifyUserDeletionRemotes :: UserId -> (AppIO r) () notifyUserDeletionRemotes deleted = do runConduit $ Data.lookupRemoteConnectedUsersC deleted (fromInteger (natVal (Proxy @UserDeletedNotificationMaxConnections))) .| C.mapM_ fanoutNotifications where - fanoutNotifications :: [Remote UserId] -> AppIO () + fanoutNotifications :: [Remote UserId] -> (AppIO r) () fanoutNotifications = mapM_ notifyBackend . bucketRemote - notifyBackend :: Remote [UserId] -> AppIO () + notifyBackend :: Remote [UserId] -> (AppIO r) () notifyBackend uids = do case tUnqualified (checked <$> uids) of Nothing -> @@ -279,7 +279,7 @@ notifyUserDeletionRemotes deleted = do whenLeft eitherFErr $ logFederationError (tDomain uids) - logFederationError :: Domain -> FederationError -> AppT IO () + logFederationError :: Domain -> FederationError -> AppT r IO () logFederationError domain fErr = Log.err $ Log.msg ("Federation error while notifying remote backends of a user deletion." :: ByteString) @@ -299,7 +299,7 @@ push :: Push.Route -> -- | The originating device connection. Maybe ConnId -> - AppIO () + (AppIO r) () push (toList -> events) usrs orig route conn = case mapMaybe toPushData events of [] -> pure () @@ -323,7 +323,7 @@ rawPush :: Push.Route -> -- | The originating device connection. Maybe ConnId -> - AppIO () + (AppIO r) () -- TODO: if we decide to have service whitelist events in Brig instead of -- Galley, let's merge 'push' and 'rawPush' back. See Note [whitelist events]. rawPush (toList -> events) usrs orig route conn = do @@ -368,7 +368,7 @@ notify :: Maybe ConnId -> -- | Users to notify. IO (List1 UserId) -> - AppIO () + (AppIO r) () notify events orig route conn recipients = forkAppIO (Just orig) $ do rs <- liftIO recipients push events rs orig route conn @@ -381,7 +381,7 @@ notifySelf :: Push.Route -> -- | Origin device connection, if any. Maybe ConnId -> - AppIO () + (AppIO r) () notifySelf events orig route conn = notify events orig route conn (pure (singleton orig)) @@ -393,19 +393,19 @@ notifyContacts :: Push.Route -> -- | Origin device connection, if any. Maybe ConnId -> - AppIO () + (AppIO r) () notifyContacts events orig route conn = do env <- ask notify events orig route conn $ runAppT env $ list1 orig <$> liftA2 (++) contacts teamContacts where - contacts :: AppIO [UserId] + contacts :: (AppIO r) [UserId] contacts = lookupContactList orig - teamContacts :: AppIO [UserId] + teamContacts :: (AppIO r) [UserId] teamContacts = screenMemberList =<< getTeamContacts orig -- If we have a truncated team, we just ignore it all together to avoid very large fanouts - screenMemberList :: Maybe Team.TeamMemberList -> AppIO [UserId] + screenMemberList :: Maybe Team.TeamMemberList -> (AppIO r) [UserId] screenMemberList (Just mems) | mems ^. Team.teamMemberListType == Team.ListComplete = return $ fmap (view Team.userId) (mems ^. Team.teamMembers) @@ -572,7 +572,7 @@ toApsData _ = Nothing -- Conversation Management -- | Calls 'Galley.API.createSelfConversationH'. -createSelfConv :: UserId -> AppIO () +createSelfConv :: UserId -> (AppIO r) () createSelfConv u = do debug $ remote "galley" @@ -590,7 +590,7 @@ createLocalConnectConv :: Local UserId -> Maybe Text -> Maybe ConnId -> - AppIO ConvId + (AppIO r) ConvId createLocalConnectConv from to cname conn = do debug $ logConnection (tUnqualified from) (qUntagged to) @@ -613,20 +613,20 @@ createConnectConv :: Qualified UserId -> Maybe Text -> Maybe ConnId -> - AppIO (Qualified ConvId) + (AppIO r) (Qualified ConvId) createConnectConv from to cname conn = do lfrom <- ensureLocal from lto <- ensureLocal to qUntagged . qualifyAs lfrom <$> createLocalConnectConv lfrom lto cname conn where - ensureLocal :: Qualified a -> AppIO (Local a) + ensureLocal :: Qualified a -> (AppIO r) (Local a) ensureLocal x = do loc <- qualifyLocal () foldQualified loc pure (\_ -> throwM federationNotImplemented) x -- | Calls 'Galley.API.acceptConvH'. -acceptLocalConnectConv :: Local UserId -> Maybe ConnId -> ConvId -> AppIO Conversation +acceptLocalConnectConv :: Local UserId -> Maybe ConnId -> ConvId -> (AppIO r) Conversation acceptLocalConnectConv from conn cnv = do debug $ remote "galley" @@ -640,7 +640,7 @@ acceptLocalConnectConv from conn cnv = do . maybe id (header "Z-Connection" . fromConnId) conn . expect2xx -acceptConnectConv :: Local UserId -> Maybe ConnId -> Qualified ConvId -> AppIO Conversation +acceptConnectConv :: Local UserId -> Maybe ConnId -> Qualified ConvId -> (AppIO r) Conversation acceptConnectConv from conn = foldQualified from @@ -648,7 +648,7 @@ acceptConnectConv from conn = (const (throwM federationNotImplemented)) -- | Calls 'Galley.API.blockConvH'. -blockLocalConv :: Local UserId -> Maybe ConnId -> ConvId -> AppIO () +blockLocalConv :: Local UserId -> Maybe ConnId -> ConvId -> (AppIO r) () blockLocalConv lusr conn cnv = do debug $ remote "galley" @@ -662,7 +662,7 @@ blockLocalConv lusr conn cnv = do . maybe id (header "Z-Connection" . fromConnId) conn . expect2xx -blockConv :: Local UserId -> Maybe ConnId -> Qualified ConvId -> AppIO () +blockConv :: Local UserId -> Maybe ConnId -> Qualified ConvId -> (AppIO r) () blockConv lusr conn = foldQualified lusr @@ -670,7 +670,7 @@ blockConv lusr conn = (const (throwM federationNotImplemented)) -- | Calls 'Galley.API.unblockConvH'. -unblockLocalConv :: Local UserId -> Maybe ConnId -> ConvId -> AppIO Conversation +unblockLocalConv :: Local UserId -> Maybe ConnId -> ConvId -> (AppIO r) Conversation unblockLocalConv lusr conn cnv = do debug $ remote "galley" @@ -684,7 +684,7 @@ unblockLocalConv lusr conn cnv = do . maybe id (header "Z-Connection" . fromConnId) conn . expect2xx -unblockConv :: Local UserId -> Maybe ConnId -> Qualified ConvId -> AppIO Conversation +unblockConv :: Local UserId -> Maybe ConnId -> Qualified ConvId -> (AppIO r) Conversation unblockConv luid conn = foldQualified luid @@ -692,7 +692,7 @@ unblockConv luid conn = (const (throwM federationNotImplemented)) -- | Calls 'Galley.API.getConversationH'. -getConv :: UserId -> ConvId -> AppIO (Maybe Conversation) +getConv :: UserId -> ConvId -> (AppIO r) (Maybe Conversation) getConv usr cnv = do debug $ remote "galley" @@ -708,7 +708,7 @@ getConv usr cnv = do . zUser usr . expect [status200, status404] -upsertOne2OneConversation :: UpsertOne2OneConversationRequest -> AppIO UpsertOne2OneConversationResponse +upsertOne2OneConversation :: UpsertOne2OneConversationRequest -> (AppIO r) UpsertOne2OneConversationResponse upsertOne2OneConversation urequest = do response <- galleyRequest POST req case Bilge.statusCode response of @@ -721,7 +721,7 @@ upsertOne2OneConversation urequest = do . lbytes (encode urequest) -- | Calls 'Galley.API.getTeamConversationH'. -getTeamConv :: UserId -> TeamId -> ConvId -> AppIO (Maybe Team.TeamConversation) +getTeamConv :: UserId -> TeamId -> ConvId -> (AppIO r) (Maybe Team.TeamConversation) getTeamConv usr tid cnv = do debug $ remote "galley" @@ -741,7 +741,7 @@ getTeamConv usr tid cnv = do -- User management -- | Calls 'Galley.API.rmUserH', as well as gundeck and cargohold. -rmUser :: UserId -> [Asset] -> AppIO () +rmUser :: UserId -> [Asset] -> (AppIO r) () rmUser usr asts = do debug $ remote "gundeck" @@ -767,7 +767,7 @@ rmUser usr asts = do -- Client management -- | Calls 'Galley.API.addClientH'. -newClient :: UserId -> ClientId -> AppIO () +newClient :: UserId -> ClientId -> (AppIO r) () newClient u c = do debug $ remote "galley" @@ -778,7 +778,7 @@ newClient u c = do void $ galleyRequest POST (p . zUser u . expect2xx) -- | Calls 'Galley.API.rmClientH', as well as gundeck. -rmClient :: UserId -> ClientId -> AppIO () +rmClient :: UserId -> ClientId -> (AppIO r) () rmClient u c = do let cid = toByteString' c debug $ @@ -808,7 +808,7 @@ rmClient u c = do where expected = [status200, status204, status404] -lookupPushToken :: UserId -> AppIO [Push.PushToken] +lookupPushToken :: UserId -> (AppIO r) [Push.PushToken] lookupPushToken uid = do g <- view gundeck rsp <- @@ -826,7 +826,7 @@ lookupPushToken uid = do -- Team Management -- | Calls 'Galley.API.canUserJoinTeamH'. -checkUserCanJoinTeam :: TeamId -> AppIO (Maybe Wai.Error) +checkUserCanJoinTeam :: TeamId -> (AppIO r) (Maybe Wai.Error) checkUserCanJoinTeam tid = do debug $ remote "galley" @@ -843,7 +843,7 @@ checkUserCanJoinTeam tid = do . header "Content-Type" "application/json" -- | Calls 'Galley.API.uncheckedAddTeamMemberH'. -addTeamMember :: UserId -> TeamId -> (Maybe (UserId, UTCTimeMillis), Team.Role) -> AppIO Bool +addTeamMember :: UserId -> TeamId -> (Maybe (UserId, UTCTimeMillis), Team.Role) -> (AppIO r) Bool addTeamMember u tid (minvmeta, role) = do debug $ remote "galley" @@ -863,7 +863,7 @@ addTeamMember u tid (minvmeta, role) = do . lbytes (encode bdy) -- | Calls 'Galley.API.createBindingTeamH'. -createTeam :: UserId -> Team.BindingNewTeam -> TeamId -> AppIO CreateUserTeam +createTeam :: UserId -> Team.BindingNewTeam -> TeamId -> (AppIO r) CreateUserTeam createTeam u t@(Team.BindingNewTeam bt) teamid = do debug $ remote "galley" @@ -883,7 +883,7 @@ createTeam u t@(Team.BindingNewTeam bt) teamid = do . lbytes (encode t) -- | Calls 'Galley.API.uncheckedGetTeamMemberH'. -getTeamMember :: UserId -> TeamId -> AppIO (Maybe Team.TeamMember) +getTeamMember :: UserId -> TeamId -> (AppIO r) (Maybe Team.TeamMember) getTeamMember u tid = do debug $ remote "galley" @@ -903,7 +903,7 @@ getTeamMember u tid = do -- | TODO: is now truncated. this is (only) used for team suspension / unsuspension, which -- means that only the first 2000 members of a team (according to some arbitrary order) will -- be suspended, and the rest will remain active. -getTeamMembers :: TeamId -> AppIO Team.TeamMemberList +getTeamMembers :: TeamId -> (AppIO r) Team.TeamMemberList getTeamMembers tid = do debug $ remote "galley" . msg (val "Get team members") galleyRequest GET req >>= decodeBody "galley" @@ -912,7 +912,7 @@ getTeamMembers tid = do paths ["i", "teams", toByteString' tid, "members"] . expect2xx -memberIsTeamOwner :: TeamId -> UserId -> AppIO Bool +memberIsTeamOwner :: TeamId -> UserId -> (AppIO r) Bool memberIsTeamOwner tid uid = do r <- galleyRequest GET $ @@ -922,7 +922,7 @@ memberIsTeamOwner tid uid = do -- | Only works on 'BindingTeam's! The list of members returned is potentially truncated. -- -- Calls 'Galley.API.getBindingTeamMembersH'. -getTeamContacts :: UserId -> AppIO (Maybe Team.TeamMemberList) +getTeamContacts :: UserId -> (AppIO r) (Maybe Team.TeamMemberList) getTeamContacts u = do debug $ remote "galley" . msg (val "Get team contacts") rs <- galleyRequest GET req @@ -935,7 +935,7 @@ getTeamContacts u = do . expect [status200, status404] -- | Calls 'Galley.API.getBindingTeamIdH'. -getTeamId :: UserId -> AppIO (Maybe TeamId) +getTeamId :: UserId -> (AppIO r) (Maybe TeamId) getTeamId u = do debug $ remote "galley" . msg (val "Get team from user") rs <- galleyRequest GET req @@ -948,7 +948,7 @@ getTeamId u = do . expect [status200, status404] -- | Calls 'Galley.API.getTeamInternalH'. -getTeam :: TeamId -> AppIO Team.TeamData +getTeam :: TeamId -> (AppIO r) Team.TeamData getTeam tid = do debug $ remote "galley" . msg (val "Get team info") galleyRequest GET req >>= decodeBody "galley" @@ -958,7 +958,7 @@ getTeam tid = do . expect2xx -- | Calls 'Galley.API.getTeamInternalH'. -getTeamName :: TeamId -> AppIO Team.TeamName +getTeamName :: TeamId -> (AppIO r) Team.TeamName getTeamName tid = do debug $ remote "galley" . msg (val "Get team info") galleyRequest GET req >>= decodeBody "galley" @@ -968,7 +968,7 @@ getTeamName tid = do . expect2xx -- | Calls 'Galley.API.getTeamFeatureStatusH'. -getTeamLegalHoldStatus :: TeamId -> AppIO (TeamFeatureStatus 'WithoutLockStatus 'TeamFeatureLegalHold) +getTeamLegalHoldStatus :: TeamId -> (AppIO r) (TeamFeatureStatus 'WithoutLockStatus 'TeamFeatureLegalHold) getTeamLegalHoldStatus tid = do debug $ remote "galley" . msg (val "Get legalhold settings") galleyRequest GET req >>= decodeBody "galley" @@ -978,7 +978,7 @@ getTeamLegalHoldStatus tid = do . expect2xx -- | Calls 'Galley.API.getSearchVisibilityInternalH'. -getTeamSearchVisibility :: TeamId -> AppIO Team.TeamSearchVisibility +getTeamSearchVisibility :: TeamId -> (AppIO r) Team.TeamSearchVisibility getTeamSearchVisibility tid = coerce @Team.TeamSearchVisibilityView @Team.TeamSearchVisibility <$> do debug $ remote "galley" . msg (val "Get search visibility settings") @@ -989,7 +989,7 @@ getTeamSearchVisibility tid = . expect2xx -- | Calls 'Galley.API.updateTeamStatusH'. -changeTeamStatus :: TeamId -> Team.TeamStatus -> Maybe Currency.Alpha -> AppIO () +changeTeamStatus :: TeamId -> Team.TeamStatus -> Maybe Currency.Alpha -> (AppIO r) () changeTeamStatus tid s cur = do debug $ remote "galley" . msg (val "Change Team status") void $ galleyRequest PUT req @@ -1000,7 +1000,7 @@ changeTeamStatus tid s cur = do . expect2xx . lbytes (encode $ Team.TeamStatusUpdate s cur) -guardLegalhold :: LegalholdProtectee -> UserClients -> ExceptT ClientError AppIO () +guardLegalhold :: LegalholdProtectee -> UserClients -> ExceptT ClientError (AppIO r) () guardLegalhold protectee userClients = do res <- lift $ galleyRequest PUT req case Bilge.statusCode res of diff --git a/services/brig/src/Brig/IO/Journal.hs b/services/brig/src/Brig/IO/Journal.hs index 3a2aaf1d37b..7703eb03e36 100644 --- a/services/brig/src/Brig/IO/Journal.hs +++ b/services/brig/src/Brig/IO/Journal.hs @@ -47,19 +47,19 @@ import qualified Proto.UserEvents_Fields as U -- User journal operations to SQS are a no-op when the service is started -- without journaling arguments for user updates -userActivate :: User -> AppIO () +userActivate :: User -> (AppIO r) () userActivate u@User {..} = journalEvent UserEvent'USER_ACTIVATE userId (userEmail u) (Just userLocale) userTeam (Just userDisplayName) -userUpdate :: UserId -> Maybe Email -> Maybe Locale -> Maybe Name -> AppIO () +userUpdate :: UserId -> Maybe Email -> Maybe Locale -> Maybe Name -> (AppIO r) () userUpdate uid em loc nm = journalEvent UserEvent'USER_UPDATE uid em loc Nothing nm -userEmailRemove :: UserId -> Email -> AppIO () +userEmailRemove :: UserId -> Email -> (AppIO r) () userEmailRemove uid em = journalEvent UserEvent'USER_EMAIL_REMOVE uid (Just em) Nothing Nothing Nothing -userDelete :: UserId -> AppIO () +userDelete :: UserId -> (AppIO r) () userDelete uid = journalEvent UserEvent'USER_DELETE uid Nothing Nothing Nothing Nothing -journalEvent :: UserEvent'EventType -> UserId -> Maybe Email -> Maybe Locale -> Maybe TeamId -> Maybe Name -> AppIO () +journalEvent :: UserEvent'EventType -> UserId -> Maybe Email -> Maybe Locale -> Maybe TeamId -> Maybe Name -> (AppIO r) () journalEvent typ uid em loc tid nm = view awsEnv >>= \env -> for_ (view AWS.userJournalQueue env) $ \queue -> do ts <- now diff --git a/services/brig/src/Brig/InternalEvent/Process.hs b/services/brig/src/Brig/InternalEvent/Process.hs index c2b3b8109e6..60b3b8d92f1 100644 --- a/services/brig/src/Brig/InternalEvent/Process.hs +++ b/services/brig/src/Brig/InternalEvent/Process.hs @@ -36,7 +36,7 @@ import UnliftIO (timeout) -- | Handle an internal event. -- -- Has a one-minute timeout that should be enough for anything that it does. -onEvent :: InternalNotification -> AppIO () +onEvent :: InternalNotification -> (AppIO r) () onEvent n = handleTimeout $ case n of DeleteUser uid -> do Log.info $ diff --git a/services/brig/src/Brig/Options.hs b/services/brig/src/Brig/Options.hs index 17244eeec3b..5c0f617e64f 100644 --- a/services/brig/src/Brig/Options.hs +++ b/services/brig/src/Brig/Options.hs @@ -37,6 +37,7 @@ import Data.Id import Data.LanguageCodes (ISO639_1 (EN)) import Data.Misc (HttpsUrl) import Data.Range +import Data.Schema (Schema (Schema), ToSchema (schema), element, enum) import Data.Scientific (toBoundedInteger) import qualified Data.Text as Text import qualified Data.Text.Encoding as Text @@ -341,6 +342,20 @@ instance ToJSON EmailVisibility where toJSON EmailVisibleIfOnSameTeam = "visible_if_on_same_team" toJSON EmailVisibleToSelf = "visible_to_self" +data ListAllSFTServers + = ListAllSFTServers + | HideAllSFTServers + deriving (Show, Eq, Ord) + deriving (FromJSON) via Schema ListAllSFTServers + +instance ToSchema ListAllSFTServers where + schema = + enum @Text "ListSFTServers" $ + mconcat + [ element "enabled" ListAllSFTServers, + element "disabled" HideAllSFTServers + ] + -- | Options that are consumed on startup data Opts = Opts -- services @@ -505,7 +520,12 @@ data Settings = Settings -- config will always return this entry. This is useful in Kubernetes -- where SFTs are deployed behind a load-balancer. In the long-run the SRV -- fetching logic can go away completely - setSftStaticUrl :: !(Maybe HttpsUrl) + setSftStaticUrl :: !(Maybe HttpsUrl), + -- | When set the /calls/config/v2 endpoint will include all the + -- loadbalanced servers of `setSftStaticUrl` under the @sft_servers_all@ + -- field. The default setting is to exclude and omit the field from the + -- response. + setSftListAllServers :: Maybe ListAllSFTServers } deriving (Show, Generic) @@ -715,7 +735,8 @@ Lens.makeLensesFor ("setUserMaxPermClients", "userMaxPermClients"), ("setFederationDomain", "federationDomain"), ("setSqsThrottleMillis", "sqsThrottleMillis"), - ("setSftStaticUrl", "sftStaticUrl") + ("setSftStaticUrl", "sftStaticUrl"), + ("setSftListAllServers", "sftListAllServers") ] ''Settings diff --git a/services/brig/src/Brig/Phone.hs b/services/brig/src/Brig/Phone.hs index c3de7fe9508..81df86b45e1 100644 --- a/services/brig/src/Brig/Phone.hs +++ b/services/brig/src/Brig/Phone.hs @@ -73,7 +73,7 @@ data PhoneException instance Exception PhoneException -sendCall :: Nexmo.Call -> AppIO () +sendCall :: forall r. Nexmo.Call -> AppIO r () sendCall call = unless (isTestPhone $ Nexmo.callTo call) $ do m <- view httpManager cred <- view nexmoCreds @@ -99,9 +99,9 @@ sendCall call = unless (isTestPhone $ Nexmo.callTo call) $ do Nexmo.CallInternal -> True _ -> False ] - unreachable :: Nexmo.CallErrorResponse -> AppT IO () + unreachable :: Nexmo.CallErrorResponse -> AppT r IO () unreachable ex = warn (toException ex) >> throwM PhoneNumberUnreachable - barred :: Nexmo.CallErrorResponse -> AppT IO () + barred :: Nexmo.CallErrorResponse -> AppT r IO () barred ex = warn (toException ex) >> throwM PhoneNumberBarred warn ex = Log.warn $ @@ -109,7 +109,7 @@ sendCall call = unless (isTestPhone $ Nexmo.callTo call) $ do ~~ field "error" (show ex) ~~ field "phone" (Nexmo.callTo call) -sendSms :: Locale -> SMSMessage -> AppIO () +sendSms :: forall r. Locale -> SMSMessage -> (AppIO r) () sendSms loc SMSMessage {..} = unless (isTestPhone smsTo) $ do m <- view httpManager withSmsBudget smsTo $ do @@ -132,7 +132,7 @@ sendSms loc SMSMessage {..} = unless (isTestPhone smsTo) $ do _ -> throwM ex' Right () -> return () where - sendNexmoSms :: Manager -> AppIO () + sendNexmoSms :: Manager -> (AppIO r) () sendNexmoSms mgr = do crd <- view nexmoCreds void . liftIO . recovering x3 nexmoHandlers $ @@ -149,7 +149,7 @@ sendSms loc SMSMessage {..} = unless (isTestPhone smsTo) $ do ES -> Nexmo.UCS2 ZH -> Nexmo.UCS2 _ -> Nexmo.GSM7 - sendTwilioSms :: Manager -> AppIO () + sendTwilioSms :: Manager -> (AppIO r) () sendTwilioSms mgr = do crd <- view twilioCreds void . liftIO . recovering x3 twilioHandlers $ @@ -179,9 +179,9 @@ sendSms loc SMSMessage {..} = unless (isTestPhone smsTo) $ do 20503 -> True -- Temporarily Unavailable _ -> False ] - unreachable :: Twilio.ErrorResponse -> AppT IO () + unreachable :: Twilio.ErrorResponse -> AppT r IO () unreachable ex = warn (toException ex) >> throwM PhoneNumberUnreachable - barred :: Twilio.ErrorResponse -> AppT IO () + barred :: Twilio.ErrorResponse -> AppT r IO () barred ex = warn (toException ex) >> throwM PhoneNumberBarred warn ex = Log.warn $ @@ -194,7 +194,7 @@ sendSms loc SMSMessage {..} = unless (isTestPhone smsTo) $ do -- | Validate a phone number. Returns the canonical -- E.164 format of the given phone number on success. -validatePhone :: Phone -> AppIO (Maybe Phone) +validatePhone :: Phone -> (AppIO r) (Maybe Phone) validatePhone (Phone p) | isTestPhone p = return (Just (Phone p)) | otherwise = do @@ -223,7 +223,7 @@ smsBudget = budgetValue = 5 -- # of SMS within timeout } -withSmsBudget :: Text -> AppIO a -> AppIO a +withSmsBudget :: Text -> (AppIO r) a -> (AppIO r) a withSmsBudget phone go = do let k = BudgetKey ("sms#" <> phone) r <- withBudget k smsBudget go @@ -251,7 +251,7 @@ callBudget = budgetValue = 2 -- # of voice calls within timeout } -withCallBudget :: Text -> AppIO a -> AppIO a +withCallBudget :: Text -> (AppIO r) a -> (AppIO r) a withCallBudget phone go = do let k = BudgetKey ("call#" <> phone) r <- withBudget k callBudget go diff --git a/services/brig/src/Brig/Provider/API.hs b/services/brig/src/Brig/Provider/API.hs index 27a45dbcf9a..8c506996626 100644 --- a/services/brig/src/Brig/Provider/API.hs +++ b/services/brig/src/Brig/Provider/API.hs @@ -108,7 +108,7 @@ import qualified Wire.API.User.Client as Public (Client, ClientCapability (Clien import qualified Wire.API.User.Client.Prekey as Public (PrekeyId) import qualified Wire.API.User.Identity as Public (Email) -routesPublic :: Routes Doc.ApiBuilder Handler () +routesPublic :: Routes Doc.ApiBuilder (Handler r) () routesPublic = do -- Public API (Unauthenticated) -------------------------------------------- @@ -307,7 +307,7 @@ routesPublic = do .&> zauth ZAuthBot .&> capture "uid" -routesInternal :: Routes a Handler () +routesInternal :: Routes a (Handler r) () routesInternal = do get "/i/provider/activation-code" (continue getActivationCodeH) $ accept "application" "json" @@ -316,11 +316,11 @@ routesInternal = do -------------------------------------------------------------------------------- -- Public API (Unauthenticated) -newAccountH :: JsonRequest Public.NewProvider -> Handler Response +newAccountH :: JsonRequest Public.NewProvider -> (Handler r) Response newAccountH req = do setStatus status201 . json <$> (newAccount =<< parseJsonBody req) -newAccount :: Public.NewProvider -> Handler Public.NewProviderResponse +newAccount :: Public.NewProvider -> (Handler r) Public.NewProviderResponse newAccount new = do email <- case validateEmail (Public.newProviderEmail new) of Right em -> return em @@ -352,11 +352,11 @@ newAccount new = do lift $ sendActivationMail name email key val False return $ Public.NewProviderResponse pid newPass -activateAccountKeyH :: Code.Key ::: Code.Value -> Handler Response +activateAccountKeyH :: Code.Key ::: Code.Value -> (Handler r) Response activateAccountKeyH (key ::: val) = do maybe (setStatus status204 empty) json <$> activateAccountKey key val -activateAccountKey :: Code.Key -> Code.Value -> Handler (Maybe Public.ProviderActivationResponse) +activateAccountKey :: Code.Key -> Code.Value -> (Handler r) (Maybe Public.ProviderActivationResponse) activateAccountKey key val = do c <- Code.verify key Code.IdentityVerification val >>= maybeInvalidCode (pid, email) <- case (Code.codeAccount c, Code.codeForEmail c) of @@ -378,11 +378,11 @@ activateAccountKey key val = do lift $ sendApprovalConfirmMail name email return . Just $ Public.ProviderActivationResponse email -getActivationCodeH :: Public.Email -> Handler Response +getActivationCodeH :: Public.Email -> (Handler r) Response getActivationCodeH e = do json <$> getActivationCode e -getActivationCode :: Public.Email -> Handler FoundActivationCode +getActivationCode :: Public.Email -> (Handler r) FoundActivationCode getActivationCode e = do email <- case validateEmail e of Right em -> return em @@ -398,11 +398,11 @@ instance ToJSON FoundActivationCode where toJSON $ Code.KeyValuePair (Code.codeKey vcode) (Code.codeValue vcode) -approveAccountKeyH :: Code.Key ::: Code.Value -> Handler Response +approveAccountKeyH :: Code.Key ::: Code.Value -> (Handler r) Response approveAccountKeyH (key ::: val) = do empty <$ approveAccountKey key val -approveAccountKey :: Code.Key -> Code.Value -> Handler () +approveAccountKey :: Code.Key -> Code.Value -> (Handler r) () approveAccountKey key val = do c <- Code.verify key Code.AccountApproval val >>= maybeInvalidCode case (Code.codeAccount c, Code.codeForEmail c) of @@ -412,12 +412,12 @@ approveAccountKey key val = do lift $ sendApprovalConfirmMail name email _ -> throwErrorDescriptionType @InvalidCode -loginH :: JsonRequest Public.ProviderLogin -> Handler Response +loginH :: JsonRequest Public.ProviderLogin -> (Handler r) Response loginH req = do tok <- login =<< parseJsonBody req setProviderCookie tok empty -login :: Public.ProviderLogin -> Handler ZAuth.ProviderToken +login :: Public.ProviderLogin -> (Handler r) ZAuth.ProviderToken login l = do pid <- DB.lookupKey (mkEmailKey (providerLoginEmail l)) >>= maybeBadCredentials pass <- DB.lookupPassword pid >>= maybeBadCredentials @@ -425,11 +425,11 @@ login l = do throwErrorDescriptionType @BadCredentials ZAuth.newProviderToken pid -beginPasswordResetH :: JsonRequest Public.PasswordReset -> Handler Response +beginPasswordResetH :: JsonRequest Public.PasswordReset -> (Handler r) Response beginPasswordResetH req = do setStatus status201 empty <$ (beginPasswordReset =<< parseJsonBody req) -beginPasswordReset :: Public.PasswordReset -> Handler () +beginPasswordReset :: Public.PasswordReset -> (Handler r) () beginPasswordReset (Public.PasswordReset target) = do pid <- DB.lookupKey (mkEmailKey target) >>= maybeBadCredentials gen <- Code.mkGen (Code.ForEmail target) @@ -446,11 +446,11 @@ beginPasswordReset (Public.PasswordReset target) = do Code.insert code lift $ sendPasswordResetMail target (Code.codeKey code) (Code.codeValue code) -completePasswordResetH :: JsonRequest Public.CompletePasswordReset -> Handler Response +completePasswordResetH :: JsonRequest Public.CompletePasswordReset -> (Handler r) Response completePasswordResetH req = do empty <$ (completePasswordReset =<< parseJsonBody req) -completePasswordReset :: Public.CompletePasswordReset -> Handler () +completePasswordReset :: Public.CompletePasswordReset -> (Handler r) () completePasswordReset (Public.CompletePasswordReset key val newpwd) = do code <- Code.verify key Code.PasswordReset val >>= maybeInvalidCode case Id <$> Code.codeAccount code of @@ -465,21 +465,21 @@ completePasswordReset (Public.CompletePasswordReset key val newpwd) = do -------------------------------------------------------------------------------- -- Provider API -getAccountH :: ProviderId -> Handler Response +getAccountH :: ProviderId -> (Handler r) Response getAccountH pid = do getAccount pid <&> \case Just p -> json p Nothing -> setStatus status404 empty -getAccount :: ProviderId -> Handler (Maybe Public.Provider) +getAccount :: ProviderId -> (Handler r) (Maybe Public.Provider) getAccount pid = do DB.lookupAccount pid -updateAccountProfileH :: ProviderId ::: JsonRequest Public.UpdateProvider -> Handler Response +updateAccountProfileH :: ProviderId ::: JsonRequest Public.UpdateProvider -> (Handler r) Response updateAccountProfileH (pid ::: req) = do empty <$ (updateAccountProfile pid =<< parseJsonBody req) -updateAccountProfile :: ProviderId -> Public.UpdateProvider -> Handler () +updateAccountProfile :: ProviderId -> Public.UpdateProvider -> (Handler r) () updateAccountProfile pid upd = do _ <- DB.lookupAccount pid >>= maybeInvalidProvider DB.updateAccountProfile @@ -488,11 +488,11 @@ updateAccountProfile pid upd = do (updateProviderUrl upd) (updateProviderDescr upd) -updateAccountEmailH :: ProviderId ::: JsonRequest Public.EmailUpdate -> Handler Response +updateAccountEmailH :: ProviderId ::: JsonRequest Public.EmailUpdate -> (Handler r) Response updateAccountEmailH (pid ::: req) = do setStatus status202 empty <$ (updateAccountEmail pid =<< parseJsonBody req) -updateAccountEmail :: ProviderId -> Public.EmailUpdate -> Handler () +updateAccountEmail :: ProviderId -> Public.EmailUpdate -> (Handler r) () updateAccountEmail pid (Public.EmailUpdate new) = do email <- case validateEmail new of Right em -> return em @@ -510,11 +510,11 @@ updateAccountEmail pid (Public.EmailUpdate new) = do Code.insert code lift $ sendActivationMail (Name "name") email (Code.codeKey code) (Code.codeValue code) True -updateAccountPasswordH :: ProviderId ::: JsonRequest Public.PasswordChange -> Handler Response +updateAccountPasswordH :: ProviderId ::: JsonRequest Public.PasswordChange -> (Handler r) Response updateAccountPasswordH (pid ::: req) = do empty <$ (updateAccountPassword pid =<< parseJsonBody req) -updateAccountPassword :: ProviderId -> Public.PasswordChange -> Handler () +updateAccountPassword :: ProviderId -> Public.PasswordChange -> (Handler r) () updateAccountPassword pid upd = do pass <- DB.lookupPassword pid >>= maybeBadCredentials unless (verifyPassword (cpOldPassword upd) pass) $ @@ -523,11 +523,11 @@ updateAccountPassword pid upd = do throwStd newPasswordMustDiffer DB.updateAccountPassword pid (cpNewPassword upd) -addServiceH :: ProviderId ::: JsonRequest Public.NewService -> Handler Response +addServiceH :: ProviderId ::: JsonRequest Public.NewService -> (Handler r) Response addServiceH (pid ::: req) = do setStatus status201 . json <$> (addService pid =<< parseJsonBody req) -addService :: ProviderId -> Public.NewService -> Handler Public.NewServiceResponse +addService :: ProviderId -> Public.NewService -> (Handler r) Public.NewServiceResponse addService pid new = do _ <- DB.lookupAccount pid >>= maybeInvalidProvider let name = newServiceName new @@ -543,25 +543,25 @@ addService pid new = do let rstoken = maybe (Just token) (const Nothing) (newServiceToken new) return $ Public.NewServiceResponse sid rstoken -listServicesH :: ProviderId -> Handler Response +listServicesH :: ProviderId -> (Handler r) Response listServicesH pid = json <$> listServices pid -listServices :: ProviderId -> Handler [Public.Service] +listServices :: ProviderId -> (Handler r) [Public.Service] listServices = DB.listServices -getServiceH :: ProviderId ::: ServiceId -> Handler Response +getServiceH :: ProviderId ::: ServiceId -> (Handler r) Response getServiceH (pid ::: sid) = do json <$> getService pid sid -getService :: ProviderId -> ServiceId -> Handler Public.Service +getService :: ProviderId -> ServiceId -> (Handler r) Public.Service getService pid sid = do DB.lookupService pid sid >>= maybeServiceNotFound -updateServiceH :: ProviderId ::: ServiceId ::: JsonRequest Public.UpdateService -> Handler Response +updateServiceH :: ProviderId ::: ServiceId ::: JsonRequest Public.UpdateService -> (Handler r) Response updateServiceH (pid ::: sid ::: req) = do empty <$ (updateService pid sid =<< parseJsonBody req) -updateService :: ProviderId -> ServiceId -> Public.UpdateService -> Handler () +updateService :: ProviderId -> ServiceId -> Public.UpdateService -> (Handler r) () updateService pid sid upd = do _ <- DB.lookupAccount pid >>= maybeInvalidProvider -- Update service profile @@ -578,11 +578,11 @@ updateService pid sid upd = do -- Update service, tags/prefix index if the service is enabled DB.updateService pid sid name tags nameChange newSummary newDescr newAssets tagsChange (serviceEnabled svc) -updateServiceConnH :: ProviderId ::: ServiceId ::: JsonRequest Public.UpdateServiceConn -> Handler Response +updateServiceConnH :: ProviderId ::: ServiceId ::: JsonRequest Public.UpdateServiceConn -> (Handler r) Response updateServiceConnH (pid ::: sid ::: req) = do empty <$ (updateServiceConn pid sid =<< parseJsonBody req) -updateServiceConn :: ProviderId -> ServiceId -> Public.UpdateServiceConn -> Handler () +updateServiceConn :: ProviderId -> ServiceId -> Public.UpdateServiceConn -> (Handler r) () updateServiceConn pid sid upd = do pass <- DB.lookupPassword pid >>= maybeBadCredentials unless (verifyPassword (updateServiceConnPassword upd) pass) $ @@ -622,7 +622,7 @@ updateServiceConn pid sid upd = do -- Since deleting a service can be costly, it just marks the service as -- disabled and then creates an event that will, when processed, actually -- delete the service. See 'finishDeleteService'. -deleteServiceH :: ProviderId ::: ServiceId ::: JsonRequest Public.DeleteService -> Handler Response +deleteServiceH :: ProviderId ::: ServiceId ::: JsonRequest Public.DeleteService -> (Handler r) Response deleteServiceH (pid ::: sid ::: req) = do setStatus status202 empty <$ (deleteService pid sid =<< parseJsonBody req) @@ -631,7 +631,7 @@ deleteServiceH (pid ::: sid ::: req) = do -- Since deleting a service can be costly, it just marks the service as -- disabled and then creates an event that will, when processed, actually -- delete the service. See 'finishDeleteService'. -deleteService :: ProviderId -> ServiceId -> Public.DeleteService -> Handler () +deleteService :: ProviderId -> ServiceId -> Public.DeleteService -> (Handler r) () deleteService pid sid del = do pass <- DB.lookupPassword pid >>= maybeBadCredentials unless (verifyPassword (deleteServicePassword del) pass) $ @@ -643,7 +643,7 @@ deleteService pid sid del = do queue <- view internalEvents lift $ Queue.enqueue queue (Internal.DeleteService pid sid) -finishDeleteService :: ProviderId -> ServiceId -> AppIO () +finishDeleteService :: ProviderId -> ServiceId -> (AppIO r) () finishDeleteService pid sid = do mbSvc <- DB.lookupService pid sid for_ mbSvc $ \svc -> do @@ -657,11 +657,11 @@ finishDeleteService pid sid = do where kick (bid, cid, _) = deleteBot (botUserId bid) Nothing bid cid -deleteAccountH :: ProviderId ::: JsonRequest Public.DeleteProvider -> Handler Response +deleteAccountH :: ProviderId ::: JsonRequest Public.DeleteProvider -> (Handler r) Response deleteAccountH (pid ::: req) = do empty <$ (deleteAccount pid =<< parseJsonBody req) -deleteAccount :: ProviderId -> Public.DeleteProvider -> Handler () +deleteAccount :: ProviderId -> Public.DeleteProvider -> (Handler r) () deleteAccount pid del = do prov <- DB.lookupAccount pid >>= maybeInvalidProvider pass <- DB.lookupPassword pid >>= maybeBadCredentials @@ -680,31 +680,31 @@ deleteAccount pid del = do -------------------------------------------------------------------------------- -- User API -getProviderProfileH :: ProviderId -> Handler Response +getProviderProfileH :: ProviderId -> (Handler r) Response getProviderProfileH pid = do json <$> getProviderProfile pid -getProviderProfile :: ProviderId -> Handler Public.ProviderProfile +getProviderProfile :: ProviderId -> (Handler r) Public.ProviderProfile getProviderProfile pid = do DB.lookupAccountProfile pid >>= maybeProviderNotFound -listServiceProfilesH :: ProviderId -> Handler Response +listServiceProfilesH :: ProviderId -> (Handler r) Response listServiceProfilesH pid = do json <$> listServiceProfiles pid -listServiceProfiles :: ProviderId -> Handler [Public.ServiceProfile] +listServiceProfiles :: ProviderId -> (Handler r) [Public.ServiceProfile] listServiceProfiles pid = do DB.listServiceProfiles pid -getServiceProfileH :: ProviderId ::: ServiceId -> Handler Response +getServiceProfileH :: ProviderId ::: ServiceId -> (Handler r) Response getServiceProfileH (pid ::: sid) = do json <$> getServiceProfile pid sid -getServiceProfile :: ProviderId -> ServiceId -> Handler Public.ServiceProfile +getServiceProfile :: ProviderId -> ServiceId -> (Handler r) Public.ServiceProfile getServiceProfile pid sid = do DB.lookupServiceProfile pid sid >>= maybeServiceNotFound -searchServiceProfilesH :: Maybe (Public.QueryAnyTags 1 3) ::: Maybe Text ::: Range 10 100 Int32 -> Handler Response +searchServiceProfilesH :: Maybe (Public.QueryAnyTags 1 3) ::: Maybe Text ::: Range 10 100 Int32 -> (Handler r) Response searchServiceProfilesH (qt ::: start ::: size) = do json <$> searchServiceProfiles qt start size @@ -712,7 +712,7 @@ searchServiceProfilesH (qt ::: start ::: size) = do -- pagination here, we need both 'start' and 'prefix'. -- -- Also see Note [buggy pagination]. -searchServiceProfiles :: Maybe (Public.QueryAnyTags 1 3) -> Maybe Text -> Range 10 100 Int32 -> Handler Public.ServiceProfilePage +searchServiceProfiles :: Maybe (Public.QueryAnyTags 1 3) -> Maybe Text -> Range 10 100 Int32 -> (Handler r) Public.ServiceProfilePage searchServiceProfiles Nothing (Just start) size = do prefix :: Range 1 128 Text <- rangeChecked start DB.paginateServiceNames (Just prefix) (fromRange size) =<< setProviderSearchFilter <$> view settings @@ -723,7 +723,7 @@ searchServiceProfiles Nothing Nothing _ = do searchTeamServiceProfilesH :: UserId ::: TeamId ::: Maybe (Range 1 128 Text) ::: Bool ::: Range 10 100 Int32 -> - Handler Response + (Handler r) Response searchTeamServiceProfilesH (uid ::: tid ::: prefix ::: filterDisabled ::: size) = do json <$> searchTeamServiceProfiles uid tid prefix filterDisabled size @@ -734,7 +734,7 @@ searchTeamServiceProfiles :: Maybe (Range 1 128 Text) -> Bool -> Range 10 100 Int32 -> - Handler Public.ServiceProfilePage + (Handler r) Public.ServiceProfilePage searchTeamServiceProfiles uid tid prefix filterDisabled size = do -- Check that the user actually belong to the team they claim they -- belong to. (Note: the 'tid' team might not even exist but we'll throw @@ -745,7 +745,7 @@ searchTeamServiceProfiles uid tid prefix filterDisabled size = do -- Get search results DB.paginateServiceWhitelist tid prefix filterDisabled (fromRange size) -getServiceTagListH :: () -> Handler Response +getServiceTagListH :: () -> (Handler r) Response getServiceTagListH () = json <$> getServiceTagList () getServiceTagList :: () -> Monad m => m Public.ServiceTagList @@ -753,7 +753,7 @@ getServiceTagList () = return (Public.ServiceTagList allTags) where allTags = [(minBound :: Public.ServiceTag) ..] -updateServiceWhitelistH :: UserId ::: ConnId ::: TeamId ::: JsonRequest Public.UpdateServiceWhitelist -> Handler Response +updateServiceWhitelistH :: UserId ::: ConnId ::: TeamId ::: JsonRequest Public.UpdateServiceWhitelist -> (Handler r) Response updateServiceWhitelistH (uid ::: con ::: tid ::: req) = do resp <- updateServiceWhitelist uid con tid =<< parseJsonBody req let status = case resp of @@ -765,7 +765,7 @@ data UpdateServiceWhitelistResp = UpdateServiceWhitelistRespChanged | UpdateServiceWhitelistRespUnchanged -updateServiceWhitelist :: UserId -> ConnId -> TeamId -> Public.UpdateServiceWhitelist -> Handler UpdateServiceWhitelistResp +updateServiceWhitelist :: UserId -> ConnId -> TeamId -> Public.UpdateServiceWhitelist -> (Handler r) UpdateServiceWhitelistResp updateServiceWhitelist uid con tid upd = do let pid = updateServiceWhitelistProvider upd sid = updateServiceWhitelistService upd @@ -797,11 +797,11 @@ updateServiceWhitelist uid con tid upd = do DB.deleteServiceWhitelist (Just tid) pid sid return UpdateServiceWhitelistRespChanged -addBotH :: UserId ::: ConnId ::: ConvId ::: JsonRequest Public.AddBot -> Handler Response +addBotH :: UserId ::: ConnId ::: ConvId ::: JsonRequest Public.AddBot -> (Handler r) Response addBotH (zuid ::: zcon ::: cid ::: req) = do setStatus status201 . json <$> (addBot zuid zcon cid =<< parseJsonBody req) -addBot :: UserId -> ConnId -> ConvId -> Public.AddBot -> Handler Public.AddBotResponse +addBot :: UserId -> ConnId -> ConvId -> Public.AddBot -> (Handler r) Public.AddBotResponse addBot zuid zcon cid add = do zusr <- lift (User.lookupUser NoPendingInvitations zuid) >>= maybeInvalidUser let pid = addBotProvider add @@ -814,14 +814,10 @@ addBot zuid zcon cid add = do maxSize <- fromIntegral . setMaxConvSize <$> view settings unless (length (cmOthers mems) < maxSize - 1) $ throwStd tooManyMembers - -- For team conversations: bots are not allowed in managed and in + -- For team conversations: bots are not allowed in -- team-only conversations unless (Set.member ServiceAccessRole (cnvAccessRoles cnv)) $ throwStd invalidConv - for_ (cnvTeam cnv) $ \tid -> do - tc <- lift (RPC.getTeamConv zuid tid cid) >>= maybeConvNotFound - when (view Teams.managedConversation tc) $ - throwStd invalidConv -- Lookup the relevant service data scon <- DB.lookupServiceConn pid sid >>= maybeServiceNotFound unless (sconEnabled scon) $ @@ -879,11 +875,11 @@ addBot zuid zcon cid add = do Public.rsAddBotEvent = ev } -removeBotH :: UserId ::: ConnId ::: ConvId ::: BotId -> Handler Response +removeBotH :: UserId ::: ConnId ::: ConvId ::: BotId -> (Handler r) Response removeBotH (zusr ::: zcon ::: cid ::: bid) = do maybe (setStatus status204 empty) json <$> removeBot zusr zcon cid bid -removeBot :: UserId -> ConnId -> ConvId -> BotId -> Handler (Maybe Public.RemoveBotResponse) +removeBot :: UserId -> ConnId -> ConvId -> BotId -> (Handler r) (Maybe Public.RemoveBotResponse) removeBot zusr zcon cid bid = do -- Get the conversation and check preconditions cnv <- lift (RPC.getConv zusr cid) >>= maybeConvNotFound @@ -901,38 +897,38 @@ removeBot zusr zcon cid bid = do -------------------------------------------------------------------------------- -- Bot API -botGetSelfH :: BotId -> Handler Response +botGetSelfH :: BotId -> (Handler r) Response botGetSelfH bot = json <$> botGetSelf bot -botGetSelf :: BotId -> Handler Public.UserProfile +botGetSelf :: BotId -> (Handler r) Public.UserProfile botGetSelf bot = do p <- lift $ User.lookupUser NoPendingInvitations (botUserId bot) maybe (throwErrorDescriptionType @UserNotFound) (return . (`Public.publicProfile` UserLegalHoldNoConsent)) p -botGetClientH :: BotId -> Handler Response +botGetClientH :: BotId -> (Handler r) Response botGetClientH bot = do maybe (throwErrorDescriptionType @ClientNotFound) (pure . json) =<< lift (botGetClient bot) -botGetClient :: BotId -> AppIO (Maybe Public.Client) +botGetClient :: BotId -> (AppIO r) (Maybe Public.Client) botGetClient bot = do listToMaybe <$> User.lookupClients (botUserId bot) -botListPrekeysH :: BotId -> Handler Response +botListPrekeysH :: BotId -> (Handler r) Response botListPrekeysH bot = do json <$> botListPrekeys bot -botListPrekeys :: BotId -> Handler [Public.PrekeyId] +botListPrekeys :: BotId -> (Handler r) [Public.PrekeyId] botListPrekeys bot = do clt <- lift $ listToMaybe <$> User.lookupClients (botUserId bot) case clientId <$> clt of Nothing -> return [] Just ci -> lift (User.lookupPrekeyIds (botUserId bot) ci) -botUpdatePrekeysH :: BotId ::: JsonRequest Public.UpdateBotPrekeys -> Handler Response +botUpdatePrekeysH :: BotId ::: JsonRequest Public.UpdateBotPrekeys -> (Handler r) Response botUpdatePrekeysH (bot ::: req) = do empty <$ (botUpdatePrekeys bot =<< parseJsonBody req) -botUpdatePrekeys :: BotId -> Public.UpdateBotPrekeys -> Handler () +botUpdatePrekeys :: BotId -> Public.UpdateBotPrekeys -> (Handler r) () botUpdatePrekeys bot upd = do clt <- lift $ listToMaybe <$> User.lookupClients (botUserId bot) case clt of @@ -941,41 +937,41 @@ botUpdatePrekeys bot upd = do let pks = updateBotPrekeyList upd User.updatePrekeys (botUserId bot) (clientId c) pks !>> clientDataError -botClaimUsersPrekeysH :: JsonRequest Public.UserClients -> Handler Response +botClaimUsersPrekeysH :: JsonRequest Public.UserClients -> (Handler r) Response botClaimUsersPrekeysH req = do json <$> (botClaimUsersPrekeys =<< parseJsonBody req) -botClaimUsersPrekeys :: Public.UserClients -> Handler Public.UserClientPrekeyMap +botClaimUsersPrekeys :: Public.UserClients -> (Handler r) Public.UserClientPrekeyMap botClaimUsersPrekeys body = do maxSize <- fromIntegral . setMaxConvSize <$> view settings when (Map.size (Public.userClients body) > maxSize) $ throwErrorDescriptionType @TooManyClients Client.claimLocalMultiPrekeyBundles UnprotectedBot body !>> clientError -botListUserProfilesH :: List UserId -> Handler Response +botListUserProfilesH :: List UserId -> (Handler r) Response botListUserProfilesH uids = do json <$> botListUserProfiles uids -botListUserProfiles :: List UserId -> Handler [Public.BotUserView] +botListUserProfiles :: List UserId -> (Handler r) [Public.BotUserView] botListUserProfiles uids = do us <- lift $ User.lookupUsers NoPendingInvitations (fromList uids) return (map mkBotUserView us) -botGetUserClientsH :: UserId -> Handler Response +botGetUserClientsH :: UserId -> (Handler r) Response botGetUserClientsH uid = do json <$> lift (botGetUserClients uid) -botGetUserClients :: UserId -> AppIO [Public.PubClient] +botGetUserClients :: UserId -> (AppIO r) [Public.PubClient] botGetUserClients uid = do pubClient <$$> User.lookupClients uid where pubClient c = Public.PubClient (clientId c) (clientClass c) -botDeleteSelfH :: BotId ::: ConvId -> Handler Response +botDeleteSelfH :: BotId ::: ConvId -> (Handler r) Response botDeleteSelfH (bid ::: cid) = do empty <$ botDeleteSelf bid cid -botDeleteSelf :: BotId -> ConvId -> Handler () +botDeleteSelf :: BotId -> ConvId -> (Handler r) () botDeleteSelf bid cid = do bot <- lift $ User.lookupUser NoPendingInvitations (botUserId bid) _ <- maybeInvalidBot (userService =<< bot) @@ -988,7 +984,7 @@ botDeleteSelf bid cid = do minRsaKeySize :: Int minRsaKeySize = 256 -- Bytes (= 2048 bits) -activate :: ProviderId -> Maybe Public.Email -> Public.Email -> Handler () +activate :: ProviderId -> Maybe Public.Email -> Public.Email -> (Handler r) () activate pid old new = do let emailKey = mkEmailKey new taken <- maybe False (/= pid) <$> DB.lookupKey emailKey @@ -996,7 +992,7 @@ activate pid old new = do throwStd emailExists DB.insertKey pid (mkEmailKey <$> old) emailKey -deleteBot :: UserId -> Maybe ConnId -> BotId -> ConvId -> AppIO (Maybe Public.Event) +deleteBot :: UserId -> Maybe ConnId -> BotId -> ConvId -> (AppIO r) (Maybe Public.Event) deleteBot zusr zcon bid cid = do -- Remove the bot from the conversation ev <- RPC.removeBotMember zusr zcon cid bid @@ -1045,7 +1041,7 @@ mkBotUserView u = Ext.botUserViewTeam = userTeam u } -setProviderCookie :: ZAuth.ProviderToken -> Response -> Handler Response +setProviderCookie :: ZAuth.ProviderToken -> Response -> (Handler r) Response setProviderCookie t r = do s <- view settings let hdr = toByteString' (Cookie.renderSetCookie (cookie s)) @@ -1061,34 +1057,34 @@ setProviderCookie t r = do Cookie.setCookieHttpOnly = True } -maybeInvalidProvider :: Maybe a -> Handler a +maybeInvalidProvider :: Maybe a -> (Handler r) a maybeInvalidProvider = maybe (throwStd invalidProvider) return -maybeInvalidCode :: Maybe a -> Handler a +maybeInvalidCode :: Maybe a -> (Handler r) a maybeInvalidCode = maybe (throwErrorDescriptionType @InvalidCode) return -maybeServiceNotFound :: Maybe a -> Handler a +maybeServiceNotFound :: Maybe a -> (Handler r) a maybeServiceNotFound = maybe (throwStd (notFound "Service not found")) return -maybeProviderNotFound :: Maybe a -> Handler a +maybeProviderNotFound :: Maybe a -> (Handler r) a maybeProviderNotFound = maybe (throwStd (notFound "Provider not found")) return -maybeConvNotFound :: Maybe a -> Handler a +maybeConvNotFound :: Maybe a -> (Handler r) a maybeConvNotFound = maybe (throwStd (notFound "Conversation not found")) return -maybeBadCredentials :: Maybe a -> Handler a +maybeBadCredentials :: Maybe a -> (Handler r) a maybeBadCredentials = maybe (throwErrorDescriptionType @BadCredentials) return -maybeInvalidServiceKey :: Maybe a -> Handler a +maybeInvalidServiceKey :: Maybe a -> (Handler r) a maybeInvalidServiceKey = maybe (throwStd invalidServiceKey) return -maybeInvalidBot :: Maybe a -> Handler a +maybeInvalidBot :: Maybe a -> (Handler r) a maybeInvalidBot = maybe (throwStd invalidBot) return -maybeInvalidUser :: Maybe a -> Handler a +maybeInvalidUser :: Maybe a -> (Handler r) a maybeInvalidUser = maybe (throwStd (errorDescriptionTypeToWai @InvalidUser)) return -rangeChecked :: Within a n m => a -> Handler (Range n m a) +rangeChecked :: Within a n m => a -> (Handler r) (Range n m a) rangeChecked = either (throwStd . invalidRange . fromString) return . checkedEither invalidServiceKey :: Wai.Error diff --git a/services/brig/src/Brig/Provider/Email.hs b/services/brig/src/Brig/Provider/Email.hs index f0b88831a8d..b0d5f76b44f 100644 --- a/services/brig/src/Brig/Provider/Email.hs +++ b/services/brig/src/Brig/Provider/Email.hs @@ -44,7 +44,7 @@ import Imports ------------------------------------------------------------------------------- -- Activation Email -sendActivationMail :: Name -> Email -> Code.Key -> Code.Value -> Bool -> AppIO () +sendActivationMail :: Name -> Email -> Code.Key -> Code.Value -> Bool -> (AppIO r) () sendActivationMail name email key code update = do tpl <- selectTemplate update . snd <$> providerTemplates Nothing branding <- view templateBranding @@ -96,7 +96,7 @@ renderActivationUrl t (Code.Key k) (Code.Value v) branding = -------------------------------------------------------------------------------- -- Approval Request Email -sendApprovalRequestMail :: Name -> Email -> HttpsUrl -> Text -> Code.Key -> Code.Value -> AppIO () +sendApprovalRequestMail :: Name -> Email -> HttpsUrl -> Text -> Code.Key -> Code.Value -> (AppIO r) () sendApprovalRequestMail name email url descr key val = do tpl <- approvalRequestEmail . snd <$> providerTemplates Nothing branding <- view templateBranding @@ -147,7 +147,7 @@ renderApprovalUrl t (Code.Key k) (Code.Value v) branding = -------------------------------------------------------------------------------- -- Approval Confirmation Email -sendApprovalConfirmMail :: Name -> Email -> AppIO () +sendApprovalConfirmMail :: Name -> Email -> (AppIO r) () sendApprovalConfirmMail name email = do tpl <- approvalConfirmEmail . snd <$> providerTemplates Nothing branding <- view templateBranding @@ -183,7 +183,7 @@ renderApprovalConfirmMail ApprovalConfirmEmail {..} ApprovalConfirmEmailTemplate -------------------------------------------------------------------------------- -- Password Reset Email -sendPasswordResetMail :: Email -> Code.Key -> Code.Value -> AppIO () +sendPasswordResetMail :: Email -> Code.Key -> Code.Value -> (AppIO r) () sendPasswordResetMail to key code = do tpl <- passwordResetEmail . snd <$> providerTemplates Nothing branding <- view templateBranding diff --git a/services/brig/src/Brig/Provider/RPC.hs b/services/brig/src/Brig/Provider/RPC.hs index 03ce6c2f9a6..d2dca30b6c2 100644 --- a/services/brig/src/Brig/Provider/RPC.hs +++ b/services/brig/src/Brig/Provider/RPC.hs @@ -65,7 +65,7 @@ data ServiceError -- -- If the external service is unavailable, returns a specific error -- or the response body cannot be parsed, a 'ServiceError' is returned. -createBot :: ServiceConn -> NewBotRequest -> ExceptT ServiceError AppIO NewBotResponse +createBot :: ServiceConn -> NewBotRequest -> ExceptT ServiceError (AppIO r) NewBotResponse createBot scon new = do let fprs = toList (sconFingerprints scon) (man, verifyFingerprints) <- view extGetManager @@ -130,7 +130,7 @@ extLogError scon e = -- Internal RPC -- | Set service connection information in galley. -setServiceConn :: ServiceConn -> AppIO () +setServiceConn :: ServiceConn -> (AppIO r) () setServiceConn scon = do Log.debug $ remote "galley" @@ -155,7 +155,7 @@ setServiceConn scon = do & set Galley.serviceEnabled (sconEnabled scon) -- | Remove service connection information from galley. -removeServiceConn :: ProviderId -> ServiceId -> AppIO () +removeServiceConn :: ProviderId -> ServiceId -> (AppIO r) () removeServiceConn pid sid = do Log.debug $ remote "galley" @@ -179,7 +179,7 @@ addBotMember :: ClientId -> ProviderId -> ServiceId -> - AppIO Event + (AppIO r) Event addBotMember zusr zcon conv bot clt pid sid = do Log.debug $ remote "galley" @@ -205,7 +205,7 @@ removeBotMember :: Maybe ConnId -> ConvId -> BotId -> - AppIO (Maybe Event) + (AppIO r) (Maybe Event) removeBotMember zusr zcon conv bot = do Log.debug $ remote "galley" diff --git a/services/brig/src/Brig/Queue.hs b/services/brig/src/Brig/Queue.hs index 9e03c45cf65..c4c811853e3 100644 --- a/services/brig/src/Brig/Queue.hs +++ b/services/brig/src/Brig/Queue.hs @@ -63,7 +63,7 @@ import System.Logger.Class as Log hiding (settings) -- | Enqueue a message. -- -- Throws an error in case of failure. -enqueue :: ToJSON a => Queue -> a -> AppIO () +enqueue :: ToJSON a => Queue -> a -> (AppIO r) () enqueue (StompQueue queue) message = view stompEnv >>= \case Just env -> Stomp.enqueue (Stomp.broker env) queue message @@ -93,7 +93,7 @@ enqueue (SqsQueue queue) message = -- -- See documentation of underlying functions (e.g. 'Stomp.listen') for -- extra details. -listen :: (Show a, FromJSON a) => Queue -> (a -> AppIO ()) -> AppIO () +listen :: (Show a, FromJSON a) => Queue -> (a -> (AppIO r) ()) -> (AppIO r) () listen (StompQueue queue) callback = view stompEnv >>= \case Just env -> Stomp.listen (Stomp.broker env) queue callback diff --git a/services/brig/src/Brig/RPC.hs b/services/brig/src/Brig/RPC.hs index 645abc530ed..a9268ca9b9f 100644 --- a/services/brig/src/Brig/RPC.hs +++ b/services/brig/src/Brig/RPC.hs @@ -62,19 +62,19 @@ expect ss rq = rq {checkResponse = check} cargoholdRequest :: StdMethod -> (Request -> Request) -> - AppIO (Response (Maybe BL.ByteString)) + (AppIO r) (Response (Maybe BL.ByteString)) cargoholdRequest = serviceRequest "cargohold" cargohold galleyRequest :: StdMethod -> (Request -> Request) -> - AppIO (Response (Maybe BL.ByteString)) + (AppIO r) (Response (Maybe BL.ByteString)) galleyRequest = serviceRequest "galley" galley gundeckRequest :: StdMethod -> (Request -> Request) -> - AppIO (Response (Maybe BL.ByteString)) + (AppIO r) (Response (Maybe BL.ByteString)) gundeckRequest = serviceRequest "gundeck" gundeck serviceRequest :: @@ -82,7 +82,7 @@ serviceRequest :: Control.Lens.Getting Request Env Request -> StdMethod -> (Request -> Request) -> - AppIO (Response (Maybe BL.ByteString)) + (AppIO r) (Response (Maybe BL.ByteString)) serviceRequest nm svc m r = do service <- view svc recovering x3 rpcHandlers $ diff --git a/services/brig/src/Brig/Run.hs b/services/brig/src/Brig/Run.hs index 579b5746b47..ed77926081e 100644 --- a/services/brig/src/Brig/Run.hs +++ b/services/brig/src/Brig/Run.hs @@ -27,7 +27,7 @@ import Brig.API (sitemap) import Brig.API.Federation import Brig.API.Handler import qualified Brig.API.Internal as IAPI -import Brig.API.Public (ServantAPI, SwaggerDocsAPI, servantSitemap, swaggerDocsAPI) +import Brig.API.Public (SwaggerDocsAPI, servantSitemap, swaggerDocsAPI) import qualified Brig.API.User as API import Brig.AWS (sesQueue) import qualified Brig.AWS as AWS @@ -68,6 +68,9 @@ import qualified Servant import System.Logger (msg, val, (.=), (~~)) import System.Logger.Class (MonadLogger, err) import Util.Options +import Wire.API.Routes.Public.Brig +import Wire.API.Routes.Version +import Wire.API.Routes.Version.Wai -- FUTUREWORK: If any of these async threads die, we will have no clue about it -- and brig could start misbehaving. We should ensure that brig dies whenever a @@ -104,7 +107,7 @@ mkApp o = do e <- newEnv o return (middleware e $ \reqId -> servantApp (e & requestId .~ reqId), e) where - rtree :: Tree (App Handler) + rtree :: Tree (App (Handler r)) rtree = compile sitemap middleware :: Env -> (RequestId -> Wai.Application) -> Wai.Application @@ -113,6 +116,7 @@ mkApp o = do . GZip.gunzip . GZip.gzip GZip.def . catchErrors (e ^. applog) [Right $ e ^. metrics] + . versionMiddleware . lookupRequestIdMiddleware app e r k = runHandler e r (Server.route rtree r k) k @@ -123,17 +127,19 @@ mkApp o = do (Proxy @ServantCombinedAPI) (customFormatters :. Servant.EmptyContext) ( swaggerDocsAPI - :<|> Servant.hoistServer (Proxy @ServantAPI) (toServantHandler e) servantSitemap + :<|> Servant.hoistServer (Proxy @BrigAPI) (toServantHandler e) servantSitemap :<|> Servant.hoistServer (Proxy @IAPI.API) (toServantHandler e) IAPI.servantSitemap :<|> Servant.hoistServer (Proxy @FederationAPI) (toServantHandler e) federationSitemap + :<|> versionAPI :<|> Servant.Tagged (app e) ) type ServantCombinedAPI = ( SwaggerDocsAPI - :<|> ServantAPI + :<|> BrigAPI :<|> IAPI.API :<|> FederationAPI + :<|> VersionAPI :<|> Servant.Raw ) @@ -163,7 +169,7 @@ bodyParserErrorFormatter _ _ errMsg = Servant.errHeaders = [(HTTP.hContentType, HTTPMedia.renderHeader (Servant.contentType (Proxy @Servant.JSON)))] } -pendingActivationCleanup :: AppIO () +pendingActivationCleanup :: forall r. (AppIO r) () pendingActivationCleanup = do safeForever "pendingActivationCleanup" $ do now <- liftIO =<< view currentTime @@ -200,17 +206,17 @@ pendingActivationCleanup = do -- pause to keep worst-case noise in logs manageable threadDelay 60_000_000 - forExpirationsPaged :: ([UserPendingActivation] -> AppIO ()) -> AppIO () + forExpirationsPaged :: ([UserPendingActivation] -> (AppIO r) ()) -> (AppIO r) () forExpirationsPaged f = do go =<< usersPendingActivationList where - go :: (Page UserPendingActivation) -> AppIO () + go :: (Page UserPendingActivation) -> (AppIO r) () go (Page hasMore result nextPage) = do f result when hasMore $ go =<< liftClient nextPage - threadDelayRandom :: AppIO () + threadDelayRandom :: (AppIO r) () threadDelayRandom = do cleanupTimeout <- fromMaybe (hours 24) . setExpiredUserCleanupTimeout <$> view settings let d = realToFrac cleanupTimeout diff --git a/services/brig/src/Brig/Team/API.hs b/services/brig/src/Brig/Team/API.hs index a55b9ffb59c..4ebdb9c2f9c 100644 --- a/services/brig/src/Brig/Team/API.hs +++ b/services/brig/src/Brig/Team/API.hs @@ -70,7 +70,7 @@ import qualified Wire.API.Team.Role as Public import qualified Wire.API.Team.Size as Public import qualified Wire.API.User as Public -routesPublic :: Routes Doc.ApiBuilder Handler () +routesPublic :: Routes Doc.ApiBuilder (Handler r) () routesPublic = do post "/teams/:tid/invitations" (continue createInvitationPublicH) $ accept "application" "json" @@ -180,7 +180,7 @@ routesPublic = do Doc.response 200 "Invitation successful." Doc.end Doc.response 403 "No permission (not admin or owner of this team)." Doc.end -routesInternal :: Routes a Handler () +routesInternal :: Routes a (Handler r) () routesInternal = do get "/i/teams/invitations/by-email" (continue getInvitationByEmailH) $ accept "application" "json" @@ -207,25 +207,25 @@ routesInternal = do accept "application" "json" .&. jsonRequest @NewUserScimInvitation -teamSizePublicH :: JSON ::: UserId ::: TeamId -> Handler Response +teamSizePublicH :: JSON ::: UserId ::: TeamId -> (Handler r) Response teamSizePublicH (_ ::: uid ::: tid) = json <$> teamSizePublic uid tid -teamSizePublic :: UserId -> TeamId -> Handler TeamSize +teamSizePublic :: UserId -> TeamId -> (Handler r) TeamSize teamSizePublic uid tid = do ensurePermissions uid tid [Team.AddTeamMember] -- limit this to team admins to reduce risk of involuntary DOS attacks teamSize tid -teamSizeH :: JSON ::: TeamId -> Handler Response +teamSizeH :: JSON ::: TeamId -> (Handler r) Response teamSizeH (_ ::: t) = json <$> teamSize t -teamSize :: TeamId -> Handler TeamSize +teamSize :: TeamId -> (Handler r) TeamSize teamSize t = lift $ TeamSize.teamSize t -getInvitationCodeH :: JSON ::: TeamId ::: InvitationId -> Handler Response +getInvitationCodeH :: JSON ::: TeamId ::: InvitationId -> (Handler r) Response getInvitationCodeH (_ ::: t ::: r) = do json <$> getInvitationCode t r -getInvitationCode :: TeamId -> InvitationId -> Handler FoundInvitationCode +getInvitationCode :: TeamId -> InvitationId -> (Handler r) FoundInvitationCode getInvitationCode t r = do code <- lift $ DB.lookupInvitationCode t r maybe (throwStd invalidInvitationCode) (return . FoundInvitationCode) code @@ -236,7 +236,7 @@ data FoundInvitationCode = FoundInvitationCode InvitationCode instance ToJSON FoundInvitationCode where toJSON (FoundInvitationCode c) = object ["code" .= c] -createInvitationPublicH :: JSON ::: UserId ::: TeamId ::: JsonRequest Public.InvitationRequest -> Handler Response +createInvitationPublicH :: JSON ::: UserId ::: TeamId ::: JsonRequest Public.InvitationRequest -> (Handler r) Response createInvitationPublicH (_ ::: uid ::: tid ::: req) = do body <- parseJsonBody req newInv <- createInvitationPublic uid tid body @@ -252,7 +252,7 @@ data CreateInvitationInviter = CreateInvitationInviter } deriving (Eq, Show) -createInvitationPublic :: UserId -> TeamId -> Public.InvitationRequest -> Handler Public.Invitation +createInvitationPublic :: UserId -> TeamId -> Public.InvitationRequest -> (Handler r) Public.Invitation createInvitationPublic uid tid body = do let inviteeRole = fromMaybe Team.defaultRole . irRole $ body inviter <- do @@ -272,12 +272,12 @@ createInvitationPublic uid tid body = do context (createInvitation' tid inviteeRole (Just (inviterUid inviter)) (inviterEmail inviter) body) -createInvitationViaScimH :: JSON ::: JsonRequest NewUserScimInvitation -> Handler Response +createInvitationViaScimH :: JSON ::: JsonRequest NewUserScimInvitation -> (Handler r) Response createInvitationViaScimH (_ ::: req) = do body <- parseJsonBody req setStatus status201 . json <$> createInvitationViaScim body -createInvitationViaScim :: NewUserScimInvitation -> Handler UserAccount +createInvitationViaScim :: NewUserScimInvitation -> (Handler r) UserAccount createInvitationViaScim newUser@(NewUserScimInvitation tid loc name email) = do env <- ask let inviteeRole = Team.defaultRole @@ -303,7 +303,7 @@ createInvitationViaScim newUser@(NewUserScimInvitation tid loc name email) = do createUserInviteViaScim uid newUser -logInvitationRequest :: (Msg -> Msg) -> Handler (Invitation, InvitationCode) -> Handler (Invitation, InvitationCode) +logInvitationRequest :: (Msg -> Msg) -> (Handler r) (Invitation, InvitationCode) -> (Handler r) (Invitation, InvitationCode) logInvitationRequest context action = flip mapExceptT action $ \action' -> do eith <- action' @@ -315,7 +315,7 @@ logInvitationRequest context action = Log.info $ (context . logInvitationCode code) . Log.msg @Text "Successfully created invitation" pure (Right result) -createInvitation' :: TeamId -> Public.Role -> Maybe UserId -> Email -> Public.InvitationRequest -> Handler (Public.Invitation, Public.InvitationCode) +createInvitation' :: TeamId -> Public.Role -> Maybe UserId -> Email -> Public.InvitationRequest -> (Handler r) (Public.Invitation, Public.InvitationCode) createInvitation' tid inviteeRole mbInviterUid fromEmail body = do -- FUTUREWORK: These validations are nearly copy+paste from accountCreation and -- sendActivationCode. Refactor this to a single place @@ -332,11 +332,11 @@ createInvitation' tid inviteeRole mbInviterUid fromEmail body = do -- Validate phone inviteePhone <- for (irInviteePhone body) $ \p -> do - validatedPhone <- maybe (throwStd invalidPhone) return =<< lift (Phone.validatePhone p) + validatedPhone <- maybe (throwStd (errorDescriptionTypeToWai @InvalidPhone)) return =<< lift (Phone.validatePhone p) let ukp = userPhoneKey validatedPhone blacklistedPh <- lift $ Blacklist.exists ukp when blacklistedPh $ - throwStd blacklistedPhone + throwStd (errorDescriptionTypeToWai @BlacklistedPhone) phoneTaken <- lift $ isJust <$> Data.lookupKey ukp when phoneTaken $ throwStd phoneExists @@ -366,47 +366,47 @@ createInvitation' tid inviteeRole mbInviterUid fromEmail body = do timeout (newInv, code) <$ sendInvitationMail inviteeEmail tid fromEmail code locale -deleteInvitationH :: JSON ::: UserId ::: TeamId ::: InvitationId -> Handler Response +deleteInvitationH :: JSON ::: UserId ::: TeamId ::: InvitationId -> (Handler r) Response deleteInvitationH (_ ::: uid ::: tid ::: iid) = do empty <$ deleteInvitation uid tid iid -deleteInvitation :: UserId -> TeamId -> InvitationId -> Handler () +deleteInvitation :: UserId -> TeamId -> InvitationId -> (Handler r) () deleteInvitation uid tid iid = do ensurePermissions uid tid [Team.AddTeamMember] DB.deleteInvitation tid iid -listInvitationsH :: JSON ::: UserId ::: TeamId ::: Maybe InvitationId ::: Range 1 500 Int32 -> Handler Response +listInvitationsH :: JSON ::: UserId ::: TeamId ::: Maybe InvitationId ::: Range 1 500 Int32 -> (Handler r) Response listInvitationsH (_ ::: uid ::: tid ::: start ::: size) = do json <$> listInvitations uid tid start size -listInvitations :: UserId -> TeamId -> Maybe InvitationId -> Range 1 500 Int32 -> Handler Public.InvitationList +listInvitations :: UserId -> TeamId -> Maybe InvitationId -> Range 1 500 Int32 -> (Handler r) Public.InvitationList listInvitations uid tid start size = do ensurePermissions uid tid [Team.AddTeamMember] rs <- lift $ DB.lookupInvitations tid start size return $! Public.InvitationList (DB.resultList rs) (DB.resultHasMore rs) -getInvitationH :: JSON ::: UserId ::: TeamId ::: InvitationId -> Handler Response +getInvitationH :: JSON ::: UserId ::: TeamId ::: InvitationId -> (Handler r) Response getInvitationH (_ ::: uid ::: tid ::: iid) = do inv <- getInvitation uid tid iid return $ case inv of Just i -> json i Nothing -> setStatus status404 empty -getInvitation :: UserId -> TeamId -> InvitationId -> Handler (Maybe Public.Invitation) +getInvitation :: UserId -> TeamId -> InvitationId -> (Handler r) (Maybe Public.Invitation) getInvitation uid tid iid = do ensurePermissions uid tid [Team.AddTeamMember] lift $ DB.lookupInvitation tid iid -getInvitationByCodeH :: JSON ::: Public.InvitationCode -> Handler Response +getInvitationByCodeH :: JSON ::: Public.InvitationCode -> (Handler r) Response getInvitationByCodeH (_ ::: c) = do json <$> getInvitationByCode c -getInvitationByCode :: Public.InvitationCode -> Handler Public.Invitation +getInvitationByCode :: Public.InvitationCode -> (Handler r) Public.Invitation getInvitationByCode c = do inv <- lift $ DB.lookupInvitationByCode c maybe (throwStd invalidInvitationCode) return inv -headInvitationByEmailH :: JSON ::: Email -> Handler Response +headInvitationByEmailH :: JSON ::: Email -> (Handler r) Response headInvitationByEmailH (_ ::: e) = do inv <- lift $ DB.lookupInvitationInfoByEmail e return $ case inv of @@ -417,30 +417,30 @@ headInvitationByEmailH (_ ::: e) = do -- | FUTUREWORK: This should also respond with status 409 in case of -- @DB.InvitationByEmailMoreThanOne@. Refactor so that 'headInvitationByEmailH' and -- 'getInvitationByEmailH' are almost the same thing. -getInvitationByEmailH :: JSON ::: Email -> Handler Response +getInvitationByEmailH :: JSON ::: Email -> (Handler r) Response getInvitationByEmailH (_ ::: email) = json <$> getInvitationByEmail email -getInvitationByEmail :: Email -> Handler Public.Invitation +getInvitationByEmail :: Email -> (Handler r) Public.Invitation getInvitationByEmail email = do inv <- lift $ DB.lookupInvitationByEmail email maybe (throwStd (notFound "Invitation not found")) return inv -suspendTeamH :: JSON ::: TeamId -> Handler Response +suspendTeamH :: JSON ::: TeamId -> (Handler r) Response suspendTeamH (_ ::: tid) = do empty <$ suspendTeam tid -suspendTeam :: TeamId -> Handler () +suspendTeam :: TeamId -> (Handler r) () suspendTeam tid = do changeTeamAccountStatuses tid Suspended lift $ DB.deleteInvitations tid lift $ Intra.changeTeamStatus tid Team.Suspended Nothing -unsuspendTeamH :: JSON ::: TeamId -> Handler Response +unsuspendTeamH :: JSON ::: TeamId -> (Handler r) Response unsuspendTeamH (_ ::: tid) = do empty <$ unsuspendTeam tid -unsuspendTeam :: TeamId -> Handler () +unsuspendTeam :: TeamId -> (Handler r) () unsuspendTeam tid = do changeTeamAccountStatuses tid Active lift $ Intra.changeTeamStatus tid Team.Active Nothing @@ -448,7 +448,7 @@ unsuspendTeam tid = do ------------------------------------------------------------------------------- -- Internal -changeTeamAccountStatuses :: TeamId -> AccountStatus -> Handler () +changeTeamAccountStatuses :: TeamId -> AccountStatus -> (Handler r) () changeTeamAccountStatuses tid s = do team <- Team.tdTeam <$> (lift $ Intra.getTeam tid) unless (team ^. Team.teamBinding == Team.Binding) $ diff --git a/services/brig/src/Brig/Team/Email.hs b/services/brig/src/Brig/Team/Email.hs index 6b44547527a..20189b98fab 100644 --- a/services/brig/src/Brig/Team/Email.hs +++ b/services/brig/src/Brig/Team/Email.hs @@ -42,21 +42,21 @@ import Imports ------------------------------------------------------------------------------- -- Invitation Email -sendInvitationMail :: Email -> TeamId -> Email -> InvitationCode -> Maybe Locale -> AppIO () +sendInvitationMail :: Email -> TeamId -> Email -> InvitationCode -> Maybe Locale -> (AppIO r) () sendInvitationMail to tid from code loc = do tpl <- invitationEmail . snd <$> teamTemplates loc branding <- view templateBranding let mail = InvitationEmail to tid code from Email.sendMail $ renderInvitationEmail mail tpl branding -sendCreatorWelcomeMail :: Email -> TeamId -> Text -> Maybe Locale -> AppIO () +sendCreatorWelcomeMail :: Email -> TeamId -> Text -> Maybe Locale -> (AppIO r) () sendCreatorWelcomeMail to tid teamName loc = do tpl <- creatorWelcomeEmail . snd <$> teamTemplates loc branding <- view templateBranding let mail = CreatorWelcomeEmail to tid teamName Email.sendMail $ renderCreatorWelcomeMail mail tpl branding -sendMemberWelcomeMail :: Email -> TeamId -> Text -> Maybe Locale -> AppIO () +sendMemberWelcomeMail :: Email -> TeamId -> Text -> Maybe Locale -> (AppIO r) () sendMemberWelcomeMail to tid teamName loc = do tpl <- memberWelcomeEmail . snd <$> teamTemplates loc branding <- view templateBranding diff --git a/services/brig/src/Brig/Team/Util.hs b/services/brig/src/Brig/Team/Util.hs index 64279ec00c5..bd530c489c4 100644 --- a/services/brig/src/Brig/Team/Util.hs +++ b/services/brig/src/Brig/Team/Util.hs @@ -27,7 +27,7 @@ import qualified Data.Set as Set import Galley.Types.Teams import Imports -ensurePermissions :: UserId -> TeamId -> [Perm] -> ExceptT Error AppIO () +ensurePermissions :: UserId -> TeamId -> [Perm] -> ExceptT Error (AppIO r) () ensurePermissions u t perms = do m <- lift $ Intra.getTeamMember u t unless (check m) $ @@ -40,7 +40,7 @@ ensurePermissions u t perms = do -- | Privilege escalation detection (make sure no `RoleMember` user creates a `RoleOwner`). -- -- There is some code duplication with 'Galley.API.Teams.ensureNotElevated'. -ensurePermissionToAddUser :: UserId -> TeamId -> Permissions -> ExceptT Error AppIO () +ensurePermissionToAddUser :: UserId -> TeamId -> Permissions -> ExceptT Error (AppIO r) () ensurePermissionToAddUser u t inviteePerms = do minviter <- lift $ Intra.getTeamMember u t unless (check minviter) $ diff --git a/services/brig/src/Brig/User/API/Auth.hs b/services/brig/src/Brig/User/API/Auth.hs index bd81bc54cf8..fdd67fd5fd2 100644 --- a/services/brig/src/Brig/User/API/Auth.hs +++ b/services/brig/src/Brig/User/API/Auth.hs @@ -59,7 +59,7 @@ import qualified Wire.API.User as Public import Wire.API.User.Auth as Public import Wire.Swagger as Doc (pendingLoginError) -routesPublic :: Routes Doc.ApiBuilder Handler () +routesPublic :: Routes Doc.ApiBuilder (Handler r) () routesPublic = do post "/access" (continue renewH) $ accept "application" "json" @@ -88,14 +88,15 @@ routesPublic = do document "POST" "sendLoginCode" $ do Doc.summary "Send a login code to a verified phone number." Doc.notes - "This operation generates and sends a login code. \ + "This operation generates and sends a login code via sms for phone login. \ \A login code can be used only once and times out after \ - \10 minutes. Only one login code may be pending at a time." + \10 minutes. Only one login code may be pending at a time.\ + \For 2nd factor authentication login with email and password, use the `/verification-code/send` endpoint." Doc.body (Doc.ref Public.modelSendLoginCode) $ Doc.description "JSON body" Doc.returns (Doc.ref Public.modelLoginCodeResponse) Doc.response 200 "Login code sent." Doc.end - Doc.errorResponse invalidPhone + Doc.errorResponse (errorDescriptionTypeToWai @InvalidPhone) Doc.errorResponse passwordExists Doc.errorResponse' loginCodePending Doc.pendingLoginError @@ -155,9 +156,9 @@ routesPublic = do Doc.response 202 "Update accepted and pending activation of the new email." Doc.end Doc.response 204 "No update, current and new email address are the same." Doc.end Doc.errorResponse invalidEmail - Doc.errorResponse userKeyExists + Doc.errorResponse (errorDescriptionTypeToWai @UserKeyExists) Doc.errorResponse blacklistedEmail - Doc.errorResponse blacklistedPhone + Doc.errorResponse (errorDescriptionTypeToWai @BlacklistedPhone) Doc.errorResponse missingAccessToken Doc.errorResponse invalidAccessToken Doc.errorResponse (errorDescriptionTypeToWai @BadCredentials) @@ -181,7 +182,7 @@ routesPublic = do Doc.body (Doc.ref Public.modelRemoveCookies) Doc.end Doc.errorResponse (errorDescriptionTypeToWai @BadCredentials) -routesInternal :: Routes a Handler () +routesInternal :: Routes a (Handler r) () routesInternal = do -- galley can query this endpoint at the right moment in the LegalHold flow post "/i/legalhold-login" (continue legalHoldLoginH) $ @@ -203,68 +204,68 @@ routesInternal = do -- Handlers -sendLoginCodeH :: JsonRequest Public.SendLoginCode -> Handler Response +sendLoginCodeH :: JsonRequest Public.SendLoginCode -> (Handler r) Response sendLoginCodeH req = do json <$> (sendLoginCode =<< parseJsonBody req) -sendLoginCode :: Public.SendLoginCode -> Handler Public.LoginCodeTimeout +sendLoginCode :: Public.SendLoginCode -> (Handler r) Public.LoginCodeTimeout sendLoginCode (Public.SendLoginCode phone call force) = do checkWhitelist (Right phone) c <- Auth.sendLoginCode phone call force !>> sendLoginCodeError return $ Public.LoginCodeTimeout (pendingLoginTimeout c) -getLoginCodeH :: JSON ::: Phone -> Handler Response +getLoginCodeH :: JSON ::: Phone -> (Handler r) Response getLoginCodeH (_ ::: phone) = json <$> getLoginCode phone -getLoginCode :: Phone -> Handler Public.PendingLoginCode +getLoginCode :: Phone -> (Handler r) Public.PendingLoginCode getLoginCode phone = do code <- lift $ Auth.lookupLoginCode phone maybe (throwStd loginCodeNotFound) return code -reAuthUserH :: UserId ::: JsonRequest ReAuthUser -> Handler Response +reAuthUserH :: UserId ::: JsonRequest ReAuthUser -> (Handler r) Response reAuthUserH (uid ::: req) = do reAuthUser uid =<< parseJsonBody req return empty -reAuthUser :: UserId -> ReAuthUser -> Handler () +reAuthUser :: UserId -> ReAuthUser -> (Handler r) () reAuthUser uid body = do User.reauthenticate uid (reAuthPassword body) !>> reauthError -loginH :: JsonRequest Public.Login ::: Bool ::: JSON -> Handler Response +loginH :: JsonRequest Public.Login ::: Bool ::: JSON -> (Handler r) Response loginH (req ::: persist ::: _) = do lift . tokenResponse =<< flip login persist =<< parseJsonBody req -login :: Public.Login -> Bool -> Handler (Auth.Access ZAuth.User) +login :: Public.Login -> Bool -> (Handler r) (Auth.Access ZAuth.User) login l persist = do let typ = if persist then PersistentCookie else SessionCookie Auth.login l typ !>> loginError -ssoLoginH :: JsonRequest SsoLogin ::: Bool ::: JSON -> Handler Response +ssoLoginH :: JsonRequest SsoLogin ::: Bool ::: JSON -> (Handler r) Response ssoLoginH (req ::: persist ::: _) = do lift . tokenResponse =<< flip ssoLogin persist =<< parseJsonBody req -ssoLogin :: SsoLogin -> Bool -> Handler (Auth.Access ZAuth.User) +ssoLogin :: SsoLogin -> Bool -> (Handler r) (Auth.Access ZAuth.User) ssoLogin l persist = do let typ = if persist then PersistentCookie else SessionCookie Auth.ssoLogin l typ !>> loginError -legalHoldLoginH :: JsonRequest LegalHoldLogin ::: JSON -> Handler Response +legalHoldLoginH :: JsonRequest LegalHoldLogin ::: JSON -> (Handler r) Response legalHoldLoginH (req ::: _) = do lift . tokenResponse =<< legalHoldLogin =<< parseJsonBody req -legalHoldLogin :: LegalHoldLogin -> Handler (Auth.Access ZAuth.LegalHoldUser) +legalHoldLogin :: LegalHoldLogin -> (Handler r) (Auth.Access ZAuth.LegalHoldUser) legalHoldLogin l = do let typ = PersistentCookie -- Session cookie isn't a supported use case here Auth.legalHoldLogin l typ !>> legalHoldLoginError -logoutH :: JSON ::: Maybe (Either (List1 ZAuth.UserToken) (List1 ZAuth.LegalHoldUserToken)) ::: Maybe (Either ZAuth.AccessToken ZAuth.LegalHoldAccessToken) -> Handler Response +logoutH :: JSON ::: Maybe (Either (List1 ZAuth.UserToken) (List1 ZAuth.LegalHoldUserToken)) ::: Maybe (Either ZAuth.AccessToken ZAuth.LegalHoldAccessToken) -> (Handler r) Response logoutH (_ ::: ut ::: at) = empty <$ logout ut at -- TODO: add legalhold test checking cookies are revoked (/access/logout is called) when legalhold device is deleted. logout :: Maybe (Either (List1 ZAuth.UserToken) (List1 ZAuth.LegalHoldUserToken)) -> Maybe (Either ZAuth.AccessToken ZAuth.LegalHoldAccessToken) -> - Handler () + (Handler r) () logout Nothing Nothing = throwStd authMissingCookieAndToken logout Nothing (Just _) = throwStd authMissingCookie logout (Just _) Nothing = throwStd authMissingToken @@ -278,7 +279,7 @@ changeSelfEmailH :: ::: JsonRequest Public.EmailUpdate ::: Maybe (Either (List1 ZAuth.UserToken) (List1 ZAuth.LegalHoldUserToken)) ::: Maybe (Either ZAuth.AccessToken ZAuth.LegalHoldAccessToken) -> - Handler Response + (Handler r) Response changeSelfEmailH (_ ::: req ::: ckies ::: toks) = do usr <- validateCredentials ckies toks email <- Public.euEmail <$> parseJsonBody req @@ -289,7 +290,7 @@ changeSelfEmailH (_ ::: req ::: ckies ::: toks) = do validateCredentials :: Maybe (Either (List1 ZAuth.UserToken) (List1 ZAuth.LegalHoldUserToken)) -> Maybe (Either ZAuth.AccessToken ZAuth.LegalHoldAccessToken) -> - Handler UserId + (Handler r) UserId validateCredentials = \case Nothing -> const $ throwStd authMissingCookie @@ -304,22 +305,22 @@ changeSelfEmailH (_ ::: req ::: ckies ::: toks) = do Just (Left userTokens) -> fst <$> (Auth.validateTokens userCookies (Just userTokens) !>> zauthError) -listCookiesH :: UserId ::: Maybe (List Public.CookieLabel) ::: JSON -> Handler Response +listCookiesH :: UserId ::: Maybe (List Public.CookieLabel) ::: JSON -> (Handler r) Response listCookiesH (u ::: ll ::: _) = json <$> lift (listCookies u ll) -listCookies :: UserId -> Maybe (List Public.CookieLabel) -> AppIO Public.CookieList +listCookies :: UserId -> Maybe (List Public.CookieLabel) -> (AppIO r) Public.CookieList listCookies u ll = do Public.CookieList <$> Auth.listCookies u (maybe [] fromList ll) -rmCookiesH :: UserId ::: JsonRequest Public.RemoveCookies -> Handler Response +rmCookiesH :: UserId ::: JsonRequest Public.RemoveCookies -> (Handler r) Response rmCookiesH (uid ::: req) = do empty <$ (rmCookies uid =<< parseJsonBody req) -rmCookies :: UserId -> Public.RemoveCookies -> Handler () +rmCookies :: UserId -> Public.RemoveCookies -> (Handler r) () rmCookies uid (Public.RemoveCookies pw lls ids) = do Auth.revokeAccess uid pw ids lls !>> authError -renewH :: JSON ::: Maybe (Either (List1 ZAuth.UserToken) (List1 ZAuth.LegalHoldUserToken)) ::: Maybe (Either ZAuth.AccessToken ZAuth.LegalHoldAccessToken) -> Handler Response +renewH :: JSON ::: Maybe (Either (List1 ZAuth.UserToken) (List1 ZAuth.LegalHoldUserToken)) ::: Maybe (Either ZAuth.AccessToken ZAuth.LegalHoldAccessToken) -> (Handler r) Response renewH (_ ::: ut ::: at) = lift . either tokenResponse tokenResponse =<< renew ut at -- | renew access for either: @@ -330,7 +331,7 @@ renewH (_ ::: ut ::: at) = lift . either tokenResponse tokenResponse =<< renew u renew :: Maybe (Either (List1 ZAuth.UserToken) (List1 ZAuth.LegalHoldUserToken)) -> Maybe (Either ZAuth.AccessToken ZAuth.LegalHoldAccessToken) -> - Handler (Either (Auth.Access ZAuth.User) (Auth.Access ZAuth.LegalHoldUser)) + (Handler r) (Either (Auth.Access ZAuth.User) (Auth.Access ZAuth.LegalHoldUser)) renew = \case Nothing -> const $ throwStd authMissingCookie @@ -343,7 +344,7 @@ renew = \case where renewAccess uts mat = Auth.renewAccess uts mat !>> zauthError - matchingOrNone :: (a -> Maybe b) -> Maybe a -> Handler (Maybe b) + matchingOrNone :: (a -> Maybe b) -> Maybe a -> (Handler r) (Maybe b) matchingOrNone matching = traverse $ \accessToken -> case matching accessToken of Just m -> pure m @@ -405,7 +406,7 @@ tokenRequest = opt (userToken ||| legalHoldUserToken) .&. opt (accessToken ||| l ) Just t -> return t -tokenResponse :: ZAuth.UserTokenLike u => Auth.Access u -> AppIO Response +tokenResponse :: ZAuth.UserTokenLike u => Auth.Access u -> (AppIO r) Response tokenResponse (Auth.Access t Nothing) = pure $ json t tokenResponse (Auth.Access t (Just c)) = Auth.setResponseCookie c (json t) diff --git a/services/brig/src/Brig/User/API/Handle.hs b/services/brig/src/Brig/User/API/Handle.hs index 7cf8ad8f0e1..82dc56f4c33 100644 --- a/services/brig/src/Brig/User/API/Handle.hs +++ b/services/brig/src/Brig/User/API/Handle.hs @@ -42,7 +42,7 @@ import qualified Wire.API.User as Public import Wire.API.User.Search import qualified Wire.API.User.Search as Public -getHandleInfo :: UserId -> Qualified Handle -> Handler (Maybe Public.UserProfile) +getHandleInfo :: UserId -> Qualified Handle -> (Handler r) (Maybe Public.UserProfile) getHandleInfo self handle = do lself <- qualifyLocal self foldQualified @@ -51,14 +51,14 @@ getHandleInfo self handle = do getRemoteHandleInfo handle -getRemoteHandleInfo :: Remote Handle -> Handler (Maybe Public.UserProfile) +getRemoteHandleInfo :: Remote Handle -> (Handler r) (Maybe Public.UserProfile) getRemoteHandleInfo handle = do Log.info $ Log.msg (Log.val "getHandleInfo - remote lookup") . Log.field "domain" (show (tDomain handle)) Federation.getUserHandleInfo handle !>> fedError -getLocalHandleInfo :: Local UserId -> Handle -> Handler (Maybe Public.UserProfile) +getLocalHandleInfo :: Local UserId -> Handle -> (Handler r) (Maybe Public.UserProfile) getLocalHandleInfo self handle = do Log.info $ Log.msg $ Log.val "getHandleInfo - local lookup" maybeOwnerId <- lift $ API.lookupHandle handle @@ -71,7 +71,7 @@ getLocalHandleInfo self handle = do return $ listToMaybe owner -- | Checks search permissions and filters accordingly -filterHandleResults :: Local UserId -> [Public.UserProfile] -> Handler [Public.UserProfile] +filterHandleResults :: Local UserId -> [Public.UserProfile] -> (Handler r) [Public.UserProfile] filterHandleResults searchingUser us = do sameTeamSearchOnly <- fromMaybe False <$> view (settings . searchSameTeamOnly) if sameTeamSearchOnly diff --git a/services/brig/src/Brig/User/API/Search.hs b/services/brig/src/Brig/User/API/Search.hs index 0b4b39add53..4a3124c1ed9 100644 --- a/services/brig/src/Brig/User/API/Search.hs +++ b/services/brig/src/Brig/User/API/Search.hs @@ -60,7 +60,7 @@ import qualified Wire.API.Team.Permission as Public import Wire.API.Team.SearchVisibility (TeamSearchVisibility) import qualified Wire.API.User.Search as Public -routesPublic :: Routes Doc.ApiBuilder Handler () +routesPublic :: Routes Doc.ApiBuilder (Handler r) () routesPublic = do get "/teams/:tid/search" (continue teamUserSearchH) $ accept "application" "json" @@ -94,7 +94,7 @@ routesPublic = do Doc.returns (Doc.ref $ Public.modelSearchResult Public.modelTeamContact) Doc.response 200 "The list of hits." Doc.end -routesInternal :: Routes a Handler () +routesInternal :: Routes a (Handler r) () routesInternal = do -- make index updates visible (e.g. for integration testing) post @@ -121,7 +121,7 @@ routesInternal = do -- FUTUREWORK: Consider augmenting 'SearchResult' with full user profiles -- for all results. This is tracked in https://wearezeta.atlassian.net/browse/SQCORE-599 -search :: UserId -> Text -> Maybe Domain -> Maybe (Range 1 500 Int32) -> Handler (Public.SearchResult Public.Contact) +search :: UserId -> Text -> Maybe Domain -> Maybe (Range 1 500 Int32) -> (Handler r) (Public.SearchResult Public.Contact) search searcherId searchTerm maybeDomain maybeMaxResults = do federationDomain <- viewFederationDomain let queryDomain = fromMaybe federationDomain maybeDomain @@ -129,7 +129,7 @@ search searcherId searchTerm maybeDomain maybeMaxResults = do then searchLocally searcherId searchTerm maybeMaxResults else searchRemotely queryDomain searchTerm -searchRemotely :: Domain -> Text -> Handler (Public.SearchResult Public.Contact) +searchRemotely :: Domain -> Text -> (Handler r) (Public.SearchResult Public.Contact) searchRemotely domain searchTerm = do Log.info $ msg (val "searchRemotely") @@ -145,7 +145,7 @@ searchRemotely domain searchTerm = do searchTook = 0 } -searchLocally :: UserId -> Text -> Maybe (Range 1 500 Int32) -> Handler (Public.SearchResult Public.Contact) +searchLocally :: UserId -> Text -> Maybe (Range 1 500 Int32) -> (Handler r) (Public.SearchResult Public.Contact) searchLocally searcherId searchTerm maybeMaxResults = do let maxResults = maybe 15 (fromIntegral . fromRange) maybeMaxResults teamSearchInfo <- mkTeamSearchInfo @@ -157,7 +157,7 @@ searchLocally searcherId searchTerm maybeMaxResults = do esResult <- if esMaxResults > 0 - then Q.searchIndex searcherId teamSearchInfo searchTerm esMaxResults + then Q.searchIndex (Just searcherId) (Just teamSearchInfo) searchTerm esMaxResults else pure $ SearchResult 0 0 0 [] -- Prepend results matching exact handle and results from ES. @@ -172,7 +172,7 @@ searchLocally searcherId searchTerm maybeMaxResults = do handleTeamVisibility t Team.SearchVisibilityStandard = Search.TeamAndNonMembers t handleTeamVisibility t Team.SearchVisibilityNoNameOutsideTeam = Search.TeamOnly t - mkTeamSearchInfo :: Handler TeamSearchInfo + mkTeamSearchInfo :: (Handler r) TeamSearchInfo mkTeamSearchInfo = lift $ do searcherTeamId <- DB.lookupUserTeam searcherId sameTeamSearchOnly <- fromMaybe False <$> view (settings . Opts.searchSameTeamOnly) @@ -186,7 +186,7 @@ searchLocally searcherId searchTerm maybeMaxResults = do -- For team users, we need to check the visibility flag handleTeamVisibility t <$> Intra.getTeamSearchVisibility t - exactHandleSearch :: TeamSearchInfo -> Handler (Maybe Contact) + exactHandleSearch :: TeamSearchInfo -> (Handler r) (Maybe Contact) exactHandleSearch teamSearchInfo = do lsearcherId <- qualifyLocal searcherId let searchedHandleMaybe = parseHandle searchTerm @@ -213,7 +213,7 @@ teamUserSearchH :: ::: Maybe TeamUserSearchSortOrder ::: Range 1 500 Int32 ) -> - Handler Response + (Handler r) Response teamUserSearchH (_ ::: uid ::: tid ::: mQuery ::: mRoleFilter ::: mSortBy ::: mSortOrder ::: size) = do json <$> teamUserSearch uid tid mQuery mRoleFilter mSortBy mSortOrder size @@ -225,7 +225,7 @@ teamUserSearch :: Maybe TeamUserSearchSortBy -> Maybe TeamUserSearchSortOrder -> Range 1 500 Int32 -> - Handler (Public.SearchResult Public.TeamContact) + (Handler r) (Public.SearchResult Public.TeamContact) teamUserSearch uid tid mQuery mRoleFilter mSortBy mSortOrder size = do ensurePermissions uid tid [Public.AddTeamMember] -- limit this to team admins to reduce risk of involuntary DOS attacks. (also, this way we don't need to worry about revealing confidential user data to other team members.) Q.teamUserSearch tid mQuery mRoleFilter mSortBy mSortOrder size diff --git a/services/brig/src/Brig/User/Auth.hs b/services/brig/src/Brig/User/Auth.hs index fc6868072cd..f0977ce66e4 100644 --- a/services/brig/src/Brig/User/Auth.hs +++ b/services/brig/src/Brig/User/Auth.hs @@ -71,13 +71,14 @@ import Network.Wai.Utilities.Error ((!>>)) import System.Logger (field, msg, val, (~~)) import qualified System.Logger.Class as Log import Wire.API.Team.Feature (TeamFeatureStatusNoConfig (..), TeamFeatureStatusValue (..)) +import Wire.API.User (TeamFeatureSndFPasswordChallengeNotImplemented (..)) data Access u = Access { accessToken :: !AccessToken, accessCookie :: !(Maybe (Cookie (ZAuth.Token u))) } -sendLoginCode :: Phone -> Bool -> Bool -> ExceptT SendLoginCodeError AppIO PendingLoginCode +sendLoginCode :: Phone -> Bool -> Bool -> ExceptT SendLoginCodeError (AppIO r) PendingLoginCode sendLoginCode phone call force = do pk <- maybe @@ -101,7 +102,7 @@ sendLoginCode phone call force = do else sendLoginSms ph (pendingLoginCode c) l return c -lookupLoginCode :: Phone -> AppIO (Maybe PendingLoginCode) +lookupLoginCode :: Phone -> (AppIO r) (Maybe PendingLoginCode) lookupLoginCode phone = Data.lookupKey (userPhoneKey phone) >>= \case Nothing -> return Nothing @@ -109,8 +110,12 @@ lookupLoginCode phone = Log.debug $ field "user" (toByteString u) . field "action" (Log.val "User.lookupLoginCode") Data.lookupLoginCode u -login :: Login -> CookieType -> ExceptT LoginError AppIO (Access ZAuth.User) -login (PasswordLogin li pw label) typ = do +login :: Login -> CookieType -> ExceptT LoginError (AppIO r) (Access ZAuth.User) +login (PasswordLogin li pw label _) typ = do + case TeamFeatureSndFPasswordChallengeNotImplemented of + -- mark this place to implement handling verification codes later + -- (for now just ignore them unconditionally.) + _ -> pure () uid <- resolveLoginId li Log.debug $ field "user" (toByteString uid) . field "action" (Log.val "User.login") checkRetryLimit uid @@ -130,19 +135,19 @@ login (SmsLogin phone code label) typ = do loginFailed uid newAccess @ZAuth.User @ZAuth.Access uid typ label -loginFailed :: UserId -> ExceptT LoginError AppIO () +loginFailed :: UserId -> ExceptT LoginError (AppIO r) () loginFailed uid = decrRetryLimit uid >> throwE LoginFailed -decrRetryLimit :: UserId -> ExceptT LoginError AppIO () +decrRetryLimit :: UserId -> ExceptT LoginError (AppIO r) () decrRetryLimit = withRetryLimit (\k b -> withBudget k b $ pure ()) -checkRetryLimit :: UserId -> ExceptT LoginError AppIO () +checkRetryLimit :: UserId -> ExceptT LoginError (AppIO r) () checkRetryLimit = withRetryLimit checkBudget withRetryLimit :: - (BudgetKey -> Budget -> ExceptT LoginError AppIO (Budgeted ())) -> + (BudgetKey -> Budget -> ExceptT LoginError (AppIO r) (Budgeted ())) -> UserId -> - ExceptT LoginError AppIO () + ExceptT LoginError (AppIO r) () withRetryLimit action uid = do mLimitFailedLogins <- view (settings . to Opt.setLimitFailedLogins) forM_ mLimitFailedLogins $ \opts -> do @@ -156,7 +161,7 @@ withRetryLimit action uid = do BudgetExhausted ttl -> throwE . LoginBlocked . RetryAfter . floor $ ttl BudgetedValue () _ -> pure () -logout :: ZAuth.TokenPair u a => List1 (ZAuth.Token u) -> ZAuth.Token a -> ExceptT ZAuth.Failure AppIO () +logout :: ZAuth.TokenPair u a => List1 (ZAuth.Token u) -> ZAuth.Token a -> ExceptT ZAuth.Failure (AppIO r) () logout uts at = do (u, ck) <- validateTokens uts (Just at) lift $ revokeCookies u [cookieId ck] [] @@ -165,7 +170,7 @@ renewAccess :: ZAuth.TokenPair u a => List1 (ZAuth.Token u) -> Maybe (ZAuth.Token a) -> - ExceptT ZAuth.Failure AppIO (Access u) + ExceptT ZAuth.Failure (AppIO r) (Access u) renewAccess uts at = do (uid, ck) <- validateTokens uts at Log.debug $ field "user" (toByteString uid) . field "action" (Log.val "User.renewAccess") @@ -179,7 +184,7 @@ revokeAccess :: PlainTextPassword -> [CookieId] -> [CookieLabel] -> - ExceptT AuthError AppIO () + ExceptT AuthError (AppIO r) () revokeAccess u pw cc ll = do Log.debug $ field "user" (toByteString u) . field "action" (Log.val "User.revokeAccess") Data.authenticate u pw @@ -188,7 +193,7 @@ revokeAccess u pw cc ll = do -------------------------------------------------------------------------------- -- Internal -catchSuspendInactiveUser :: UserId -> e -> ExceptT e AppIO () +catchSuspendInactiveUser :: UserId -> e -> ExceptT e (AppIO r) () catchSuspendInactiveUser uid errval = do mustsuspend <- lift $ mustSuspendInactiveUser uid when mustsuspend $ do @@ -199,7 +204,7 @@ catchSuspendInactiveUser uid errval = do lift $ suspendAccount (List1.singleton uid) throwE errval -newAccess :: forall u a. ZAuth.TokenPair u a => UserId -> CookieType -> Maybe CookieLabel -> ExceptT LoginError AppIO (Access u) +newAccess :: forall u a r. ZAuth.TokenPair u a => UserId -> CookieType -> Maybe CookieLabel -> ExceptT LoginError (AppIO r) (Access u) newAccess uid ct cl = do catchSuspendInactiveUser uid LoginSuspended r <- lift $ newCookieLimited uid ct cl @@ -209,7 +214,7 @@ newAccess uid ct cl = do t <- lift $ newAccessToken @u @a ck Nothing return $ Access t (Just ck) -resolveLoginId :: LoginId -> ExceptT LoginError AppIO UserId +resolveLoginId :: LoginId -> ExceptT LoginError (AppIO r) UserId resolveLoginId li = do usr <- validateLoginId li >>= lift . either lookupKey lookupHandle case usr of @@ -221,7 +226,7 @@ resolveLoginId li = do else LoginFailed Just uid -> return uid -validateLoginId :: LoginId -> ExceptT LoginError AppIO (Either UserKey Handle) +validateLoginId :: LoginId -> ExceptT LoginError (AppIO r) (Either UserKey Handle) validateLoginId (LoginByEmail email) = either (const $ throwE LoginFailed) @@ -235,7 +240,7 @@ validateLoginId (LoginByPhone phone) = validateLoginId (LoginByHandle h) = return (Right h) -isPendingActivation :: LoginId -> AppIO Bool +isPendingActivation :: LoginId -> (AppIO r) Bool isPendingActivation ident = case ident of (LoginByHandle _) -> return False (LoginByEmail e) -> checkKey (userEmailKey e) @@ -269,13 +274,13 @@ validateTokens :: ZAuth.TokenPair u a => List1 (ZAuth.Token u) -> Maybe (ZAuth.Token a) -> - ExceptT ZAuth.Failure AppIO (UserId, Cookie (ZAuth.Token u)) + ExceptT ZAuth.Failure (AppIO r) (UserId, Cookie (ZAuth.Token u)) validateTokens uts at = do tokens <- forM uts $ \ut -> lift $ runExceptT (validateToken ut at) getFirstSuccessOrFirstFail tokens where -- FUTUREWORK: There is surely a better way to do this - getFirstSuccessOrFirstFail :: List1 (Either ZAuth.Failure (UserId, Cookie (ZAuth.Token u))) -> ExceptT ZAuth.Failure AppIO (UserId, Cookie (ZAuth.Token u)) + getFirstSuccessOrFirstFail :: List1 (Either ZAuth.Failure (UserId, Cookie (ZAuth.Token u))) -> ExceptT ZAuth.Failure (AppIO r) (UserId, Cookie (ZAuth.Token u)) getFirstSuccessOrFirstFail tks = case (lefts $ NE.toList $ List1.toNonEmpty tks, rights $ NE.toList $ List1.toNonEmpty tks) of (_, (suc : _)) -> return suc ((e : _), _) -> throwE e @@ -285,7 +290,7 @@ validateToken :: ZAuth.TokenPair u a => ZAuth.Token u -> Maybe (ZAuth.Token a) -> - ExceptT ZAuth.Failure AppIO (UserId, Cookie (ZAuth.Token u)) + ExceptT ZAuth.Failure (AppIO r) (UserId, Cookie (ZAuth.Token u)) validateToken ut at = do unless (maybe True ((ZAuth.userTokenOf ut ==) . ZAuth.accessTokenOf) at) $ throwE ZAuth.Invalid @@ -298,7 +303,7 @@ validateToken ut at = do return (ZAuth.userTokenOf ut, ck) -- | Allow to login as any user without having the credentials. -ssoLogin :: SsoLogin -> CookieType -> ExceptT LoginError AppIO (Access ZAuth.User) +ssoLogin :: SsoLogin -> CookieType -> ExceptT LoginError (AppIO r) (Access ZAuth.User) ssoLogin (SsoLogin uid label) typ = do Data.reauthenticate uid Nothing `catchE` \case ReAuthMissingPassword -> pure () @@ -311,7 +316,7 @@ ssoLogin (SsoLogin uid label) typ = do newAccess @ZAuth.User @ZAuth.Access uid typ label -- | Log in as a LegalHold service, getting LegalHoldUser/Access Tokens. -legalHoldLogin :: LegalHoldLogin -> CookieType -> ExceptT LegalHoldLoginError AppIO (Access ZAuth.LegalHoldUser) +legalHoldLogin :: LegalHoldLogin -> CookieType -> ExceptT LegalHoldLoginError (AppIO r) (Access ZAuth.LegalHoldUser) legalHoldLogin (LegalHoldLogin uid plainTextPassword label) typ = do Data.reauthenticate uid plainTextPassword !>> LegalHoldReAuthError -- legalhold login is only possible if @@ -325,7 +330,7 @@ legalHoldLogin (LegalHoldLogin uid plainTextPassword label) typ = do newAccess @ZAuth.LegalHoldUser @ZAuth.LegalHoldAccess uid typ label !>> LegalHoldLoginError -assertLegalHoldEnabled :: TeamId -> ExceptT LegalHoldLoginError AppIO () +assertLegalHoldEnabled :: TeamId -> ExceptT LegalHoldLoginError (AppIO r) () assertLegalHoldEnabled tid = do stat <- lift $ Intra.getTeamLegalHoldStatus tid case tfwoStatus stat of diff --git a/services/brig/src/Brig/User/Auth/Cookie.hs b/services/brig/src/Brig/User/Auth/Cookie.hs index 1bb09328eaf..28023f58636 100644 --- a/services/brig/src/Brig/User/Auth/Cookie.hs +++ b/services/brig/src/Brig/User/Auth/Cookie.hs @@ -68,7 +68,7 @@ newCookie :: UserId -> CookieType -> Maybe CookieLabel -> - AppIO (Cookie (ZAuth.Token u)) + (AppIO r) (Cookie (ZAuth.Token u)) newCookie uid typ label = do now <- liftIO =<< view currentTime tok <- @@ -90,7 +90,7 @@ newCookie uid typ label = do -- | Renew the given cookie with a fresh token, if its age -- exceeds the configured minimum threshold. -nextCookie :: ZAuth.UserTokenLike u => Cookie (ZAuth.Token u) -> AppIO (Maybe (Cookie (ZAuth.Token u))) +nextCookie :: ZAuth.UserTokenLike u => Cookie (ZAuth.Token u) -> (AppIO r) (Maybe (Cookie (ZAuth.Token u))) nextCookie c = do s <- view settings now <- liftIO =<< view currentTime @@ -116,7 +116,7 @@ nextCookie c = do return c' {cookieValue = t} -- | Renew the given cookie with a fresh token. -renewCookie :: ZAuth.UserTokenLike u => Cookie (ZAuth.Token u) -> AppIO (Cookie (ZAuth.Token u)) +renewCookie :: ZAuth.UserTokenLike u => Cookie (ZAuth.Token u) -> (AppIO r) (Cookie (ZAuth.Token u)) renewCookie old = do let t = cookieValue old let uid = ZAuth.userTokenOf t @@ -134,7 +134,7 @@ renewCookie old = do -- 'suspendCookiesOlderThanSecs'. Call this always before 'newCookie', 'nextCookie', -- 'newCookieLimited' if there is a chance that the user should be suspended (we don't do it -- implicitly because of cyclical dependencies). -mustSuspendInactiveUser :: UserId -> AppIO Bool +mustSuspendInactiveUser :: UserId -> (AppIO r) Bool mustSuspendInactiveUser uid = view (settings . to setSuspendInactiveUsers) >>= \case Nothing -> pure False @@ -151,7 +151,7 @@ mustSuspendInactiveUser uid = | otherwise = True pure mustSuspend -newAccessToken :: forall u a. ZAuth.TokenPair u a => Cookie (ZAuth.Token u) -> Maybe (ZAuth.Token a) -> AppIO AccessToken +newAccessToken :: forall u a r. ZAuth.TokenPair u a => Cookie (ZAuth.Token u) -> Maybe (ZAuth.Token a) -> (AppIO r) AccessToken newAccessToken c mt = do t' <- case mt of Nothing -> ZAuth.newAccessToken (cookieValue c) @@ -166,7 +166,7 @@ newAccessToken c mt = do -- | Lookup the stored cookie associated with a user token, -- if one exists. -lookupCookie :: ZAuth.UserTokenLike u => ZAuth.Token u -> AppIO (Maybe (Cookie (ZAuth.Token u))) +lookupCookie :: ZAuth.UserTokenLike u => ZAuth.Token u -> (AppIO r) (Maybe (Cookie (ZAuth.Token u))) lookupCookie t = do let user = ZAuth.userTokenOf t let rand = ZAuth.userTokenRand t @@ -175,16 +175,16 @@ lookupCookie t = do where setToken c = c {cookieValue = t} -listCookies :: UserId -> [CookieLabel] -> AppIO [Cookie ()] +listCookies :: UserId -> [CookieLabel] -> (AppIO r) [Cookie ()] listCookies u [] = DB.listCookies u listCookies u ll = filter byLabel <$> DB.listCookies u where byLabel c = maybe False (`elem` ll) (cookieLabel c) -revokeAllCookies :: UserId -> AppIO () +revokeAllCookies :: UserId -> (AppIO r) () revokeAllCookies u = revokeCookies u [] [] -revokeCookies :: UserId -> [CookieId] -> [CookieLabel] -> AppIO () +revokeCookies :: UserId -> [CookieId] -> [CookieLabel] -> (AppIO r) () revokeCookies u [] [] = DB.deleteAllCookies u revokeCookies u ids labels = do cc <- filter matching <$> DB.listCookies u @@ -202,7 +202,7 @@ newCookieLimited :: UserId -> CookieType -> Maybe CookieLabel -> - AppIO (Either RetryAfter (Cookie (ZAuth.Token t))) + (AppIO r) (Either RetryAfter (Cookie (ZAuth.Token t))) newCookieLimited u typ label = do cs <- filter ((typ ==) . cookieType) <$> DB.listCookies u now <- liftIO =<< view currentTime @@ -246,7 +246,7 @@ setResponseCookie c r = do -------------------------------------------------------------------------------- -- Tracking -trackSuperseded :: UserId -> CookieId -> AppIO () +trackSuperseded :: UserId -> CookieId -> (AppIO r) () trackSuperseded u c = do m <- view metrics Metrics.counterIncr (Metrics.path "user.auth.cookie.superseded") m diff --git a/services/brig/src/Brig/User/EJPD.hs b/services/brig/src/Brig/User/EJPD.hs index 1a089b56da7..b1c4d49c730 100644 --- a/services/brig/src/Brig/User/EJPD.hs +++ b/services/brig/src/Brig/User/EJPD.hs @@ -40,19 +40,19 @@ import Wire.API.Routes.Internal.Brig.EJPD (EJPDRequestBody (EJPDRequestBody), EJ import qualified Wire.API.Team.Member as Team import Wire.API.User (User, userDisplayName, userEmail, userHandle, userId, userPhone, userTeam) -ejpdRequest :: Maybe Bool -> EJPDRequestBody -> Handler EJPDResponseBody +ejpdRequest :: Maybe Bool -> EJPDRequestBody -> (Handler r) EJPDResponseBody ejpdRequest includeContacts (EJPDRequestBody handles) = do ExceptT $ Right . EJPDResponseBody . catMaybes <$> forM handles (go1 (fromMaybe False includeContacts)) where -- find uid given handle - go1 :: Bool -> Handle -> AppIO (Maybe EJPDResponseItem) + go1 :: Bool -> Handle -> (AppIO r) (Maybe EJPDResponseItem) go1 includeContacts' handle = do mbUid <- lookupHandle handle mbUsr <- maybe (pure Nothing) (lookupUser NoPendingInvitations) mbUid maybe (pure Nothing) (fmap Just . go2 includeContacts') mbUsr -- construct response item given uid - go2 :: Bool -> User -> AppIO EJPDResponseItem + go2 :: Bool -> User -> (AppIO r) EJPDResponseItem go2 includeContacts' target = do let uid = userId target diff --git a/services/brig/src/Brig/User/Email.hs b/services/brig/src/Brig/User/Email.hs index 1a58f57285e..b1c3e11c6a0 100644 --- a/services/brig/src/Brig/User/Email.hs +++ b/services/brig/src/Brig/User/Email.hs @@ -45,14 +45,14 @@ import qualified Data.Text.Ascii as Ascii import Data.Text.Lazy (toStrict) import Imports -sendVerificationMail :: Email -> ActivationPair -> Maybe Locale -> AppIO () +sendVerificationMail :: Email -> ActivationPair -> Maybe Locale -> (AppIO r) () sendVerificationMail to pair loc = do tpl <- verificationEmail . snd <$> userTemplates loc branding <- view templateBranding let mail = VerificationEmail to pair Email.sendMail $ renderVerificationMail mail tpl branding -sendActivationMail :: Email -> Name -> ActivationPair -> Maybe Locale -> Maybe UserIdentity -> AppIO () +sendActivationMail :: Email -> Name -> ActivationPair -> Maybe Locale -> Maybe UserIdentity -> (AppIO r) () sendActivationMail to name pair loc ident = do tpl <- selectTemplate . snd <$> userTemplates loc branding <- view templateBranding @@ -64,26 +64,26 @@ sendActivationMail to name pair loc ident = do then activationEmail else activationEmailUpdate -sendPasswordResetMail :: Email -> PasswordResetPair -> Maybe Locale -> AppIO () +sendPasswordResetMail :: Email -> PasswordResetPair -> Maybe Locale -> (AppIO r) () sendPasswordResetMail to pair loc = do tpl <- passwordResetEmail . snd <$> userTemplates loc branding <- view templateBranding let mail = PasswordResetEmail to pair Email.sendMail $ renderPwResetMail mail tpl branding -sendDeletionEmail :: Name -> Email -> Code.Key -> Code.Value -> Locale -> AppIO () +sendDeletionEmail :: Name -> Email -> Code.Key -> Code.Value -> Locale -> (AppIO r) () sendDeletionEmail name email key code locale = do tpl <- deletionEmail . snd <$> userTemplates (Just locale) branding <- view templateBranding Email.sendMail $ renderDeletionEmail tpl (DeletionEmail email name key code) branding -sendNewClientEmail :: Name -> Email -> Client -> Locale -> AppIO () +sendNewClientEmail :: Name -> Email -> Client -> Locale -> (AppIO r) () sendNewClientEmail name email client locale = do tpl <- newClientEmail . snd <$> userTemplates (Just locale) branding <- view templateBranding Email.sendMail $ renderNewClientEmail tpl (NewClientEmail locale email name client) branding -sendTeamActivationMail :: Email -> Name -> ActivationPair -> Maybe Locale -> Text -> AppIO () +sendTeamActivationMail :: Email -> Name -> ActivationPair -> Maybe Locale -> Text -> (AppIO r) () sendTeamActivationMail to name pair loc team = do tpl <- teamActivationEmail . snd <$> userTemplates loc let mail = TeamActivationEmail to name team pair diff --git a/services/brig/src/Brig/User/Handle.hs b/services/brig/src/Brig/User/Handle.hs index 1f53d68492d..5616c5efb85 100644 --- a/services/brig/src/Brig/User/Handle.hs +++ b/services/brig/src/Brig/User/Handle.hs @@ -34,7 +34,7 @@ import Data.Id import Imports -- | Claim a new handle for an existing 'User'. -claimHandle :: UserId -> Maybe Handle -> Handle -> AppIO Bool +claimHandle :: UserId -> Maybe Handle -> Handle -> (AppIO r) Bool claimHandle uid oldHandle newHandle = isJust <$> do owner <- lookupHandle newHandle @@ -56,19 +56,19 @@ claimHandle uid oldHandle newHandle = return result -- | Free a 'Handle', making it available to be claimed again. -freeHandle :: UserId -> Handle -> AppIO () +freeHandle :: UserId -> Handle -> (AppIO r) () freeHandle uid h = do retry x5 $ write handleDelete (params LocalQuorum (Identity h)) let key = "@" <> fromHandle h deleteClaim uid key (30 # Minute) -- | Lookup the current owner of a 'Handle'. -lookupHandle :: Handle -> AppIO (Maybe UserId) +lookupHandle :: Handle -> (AppIO r) (Maybe UserId) lookupHandle = lookupHandleWithPolicy LocalQuorum -- | A weaker version of 'lookupHandle' that trades availability -- (and potentially speed) for the possibility of returning stale data. -glimpseHandle :: Handle -> AppIO (Maybe UserId) +glimpseHandle :: Handle -> (AppIO r) (Maybe UserId) glimpseHandle = lookupHandleWithPolicy One {-# INLINE lookupHandleWithPolicy #-} @@ -78,7 +78,7 @@ glimpseHandle = lookupHandleWithPolicy One -- -- FUTUREWORK: This should ideally be tackled by hiding constructor for 'Handle' -- and only allowing it to be parsed. -lookupHandleWithPolicy :: Consistency -> Handle -> AppIO (Maybe UserId) +lookupHandleWithPolicy :: Consistency -> Handle -> (AppIO r) (Maybe UserId) lookupHandleWithPolicy policy h = do join . fmap runIdentity <$> retry x1 (query1 handleSelect (params policy (Identity h))) diff --git a/services/brig/src/Brig/User/Phone.hs b/services/brig/src/Brig/User/Phone.hs index 31ab888f2ac..a172780d083 100644 --- a/services/brig/src/Brig/User/Phone.hs +++ b/services/brig/src/Brig/User/Phone.hs @@ -52,37 +52,37 @@ import Data.Text.Lazy (toStrict) import Imports import qualified Ropes.Nexmo as Nexmo -sendActivationSms :: Phone -> ActivationPair -> Maybe Locale -> AppIO () +sendActivationSms :: Phone -> ActivationPair -> Maybe Locale -> (AppIO r) () sendActivationSms to (_, c) loc = do branding <- view templateBranding (loc', tpl) <- userTemplates loc sendSms loc' $ renderActivationSms (ActivationSms to c) (activationSms tpl) branding -sendPasswordResetSms :: Phone -> PasswordResetPair -> Maybe Locale -> AppIO () +sendPasswordResetSms :: Phone -> PasswordResetPair -> Maybe Locale -> (AppIO r) () sendPasswordResetSms to (_, c) loc = do branding <- view templateBranding (loc', tpl) <- userTemplates loc sendSms loc' $ renderPasswordResetSms (PasswordResetSms to c) (passwordResetSms tpl) branding -sendLoginSms :: Phone -> LoginCode -> Maybe Locale -> AppIO () +sendLoginSms :: Phone -> LoginCode -> Maybe Locale -> (AppIO r) () sendLoginSms to code loc = do branding <- view templateBranding (loc', tpl) <- userTemplates loc sendSms loc' $ renderLoginSms (LoginSms to code) (loginSms tpl) branding -sendDeletionSms :: Phone -> Code.Key -> Code.Value -> Locale -> AppIO () +sendDeletionSms :: Phone -> Code.Key -> Code.Value -> Locale -> (AppIO r) () sendDeletionSms to key code loc = do branding <- view templateBranding (loc', tpl) <- userTemplates (Just loc) sendSms loc' $ renderDeletionSms (DeletionSms to key code) (deletionSms tpl) branding -sendActivationCall :: Phone -> ActivationPair -> Maybe Locale -> AppIO () +sendActivationCall :: Phone -> ActivationPair -> Maybe Locale -> (AppIO r) () sendActivationCall to (_, c) loc = do branding <- view templateBranding (loc', tpl) <- userTemplates loc sendCall $ renderActivationCall (ActivationCall to c) (activationCall tpl) loc' branding -sendLoginCall :: Phone -> LoginCode -> Maybe Locale -> AppIO () +sendLoginCall :: Phone -> LoginCode -> Maybe Locale -> (AppIO r) () sendLoginCall to c loc = do branding <- view templateBranding (loc', tpl) <- userTemplates loc diff --git a/services/brig/src/Brig/User/Search/SearchIndex.hs b/services/brig/src/Brig/User/Search/SearchIndex.hs index eab8d457486..9a323b02231 100644 --- a/services/brig/src/Brig/User/Search/SearchIndex.hs +++ b/services/brig/src/Brig/User/Search/SearchIndex.hs @@ -42,8 +42,8 @@ import Wire.API.User (ColourId (..), Name (fromName)) searchIndex :: (MonadIndexIO m, MonadReader Env m) => -- | The user performing the search. - UserId -> - TeamSearchInfo -> + (Maybe UserId) -> + Maybe TeamSearchInfo -> -- | The search query Text -> -- | The maximum number of results. @@ -90,7 +90,7 @@ userDocToContact localDomain UserDoc {..} = do -- it allows to experiment with different queries (perhaps in an A/B context). -- -- FUTUREWORK: Drop legacyPrefixMatch -defaultUserQuery :: UserId -> TeamSearchInfo -> Text -> IndexQuery Contact +defaultUserQuery :: Maybe UserId -> Maybe TeamSearchInfo -> Text -> IndexQuery Contact defaultUserQuery u teamSearchInfo (normalized -> term') = let matchPhraseOrPrefix = ES.QueryMultiMatchQuery $ @@ -143,32 +143,32 @@ defaultUserQuery u teamSearchInfo (normalized -> term') = } in mkUserQuery u teamSearchInfo queryWithBoost -mkUserQuery :: UserId -> TeamSearchInfo -> ES.Query -> IndexQuery Contact -mkUserQuery (review _TextId -> self) teamSearchInfo q = +mkUserQuery :: Maybe UserId -> Maybe TeamSearchInfo -> ES.Query -> IndexQuery Contact +mkUserQuery (fmap (review _TextId) -> self) teamSearchInfo q = IndexQuery q ( ES.Filter . ES.QueryBoolQuery $ boolQuery - { ES.boolQueryMustNotMatch = [termQ "_id" self], + { ES.boolQueryMustNotMatch = [termQ "_id" uid | uid <- maybeToList self], ES.boolQueryMustMatch = - [ optionallySearchWithinTeam teamSearchInfo, - ES.QueryBoolQuery - boolQuery - { ES.boolQueryShouldMatch = - [ termQ "account_status" "active", - -- Also match entries where the account_status field is not present. - -- These must have been inserted before we added the account_status - -- and at that time we only inserted active users in the first place. - -- This should be unnecessary after re-indexing, but let's be lenient - -- here for a while. - ES.QueryBoolQuery - boolQuery - { ES.boolQueryMustNotMatch = - [ES.QueryExistsQuery (ES.FieldName "account_status")] - } - ] - } - ] + maybe [] (pure . optionallySearchWithinTeam) teamSearchInfo + ++ [ ES.QueryBoolQuery + boolQuery + { ES.boolQueryShouldMatch = + [ termQ "account_status" "active", + -- Also match entries where the account_status field is not present. + -- These must have been inserted before we added the account_status + -- and at that time we only inserted active users in the first place. + -- This should be unnecessary after re-indexing, but let's be lenient + -- here for a while. + ES.QueryBoolQuery + boolQuery + { ES.boolQueryMustNotMatch = + [ES.QueryExistsQuery (ES.FieldName "account_status")] + } + ] + } + ] } ) [] diff --git a/services/brig/test/integration/API/Calling.hs b/services/brig/test/integration/API/Calling.hs index b4d1643e967..85bf7137d7e 100644 --- a/services/brig/test/integration/API/Calling.hs +++ b/services/brig/test/integration/API/Calling.hs @@ -113,7 +113,7 @@ testSFT b opts = do testSFTUnavailble :: Brig -> Opts.Opts -> String -> Http () testSFTUnavailble b opts domain = do uid <- userId <$> randomUser b - withSettingsOverrides (opts {Opts.optSettings = (Opts.optSettings opts) {Opts.setSftStaticUrl = fromByteString (cs domain)}}) $ do + withSettingsOverrides (opts {Opts.optSettings = (Opts.optSettings opts) {Opts.setSftStaticUrl = fromByteString (cs domain), Opts.setSftListAllServers = Just Opts.ListAllSFTServers}}) $ do cfg <- getTurnConfigurationV2 uid b liftIO $ do assertEqual diff --git a/services/brig/test/integration/API/Federation.hs b/services/brig/test/integration/API/Federation.hs index da58718cc40..d1559afc4c5 100644 --- a/services/brig/test/integration/API/Federation.hs +++ b/services/brig/test/integration/API/Federation.hs @@ -42,7 +42,7 @@ import Bilge.Assert import qualified Brig.Options as Opt import Brig.Types import Control.Arrow (Arrow (first), (&&&)) -import Data.Aeson (encode) +import Data.Aeson import Data.Domain (Domain (Domain)) import Data.Handle (Handle (..)) import Data.Id @@ -54,8 +54,7 @@ import Data.Timeout import qualified Data.UUID.V4 as UUIDv4 import Federation.Util (generateClientPrekeys) import Imports -import Test.QuickCheck (arbitrary) -import Test.QuickCheck.Gen (generate) +import Test.QuickCheck hiding ((===)) import Test.Tasty import qualified Test.Tasty.Cannon as WS import Test.Tasty.HUnit (assertEqual, assertFailure) @@ -74,6 +73,8 @@ tests m opts brig cannon fedBrigClient = testGroup "federation" [ test m "POST /federation/search-users : Found" (testSearchSuccess brig fedBrigClient), + test m "POST /federation/search-users : Found (fulltext)" (testFulltextSearchSuccess brig fedBrigClient), + test m "POST /federation/search-users : Found (multiple users)" (testFulltextSearchMultipleUsers brig fedBrigClient), test m "POST /federation/search-users : NotFound" (testSearchNotFound fedBrigClient), test m "POST /federation/search-users : Empty Input - NotFound" (testSearchNotFoundEmpty fedBrigClient), test m "POST /federation/get-user-by-handle : Found" (testGetUserByHandleSuccess brig fedBrigClient), @@ -92,17 +93,45 @@ tests m opts brig cannon fedBrigClient = testSearchSuccess :: Brig -> FedClient 'Brig -> Http () testSearchSuccess brig fedBrigClient = do (handle, user) <- createUserWithHandle brig + refreshIndex brig + + let quid = userQualifiedId user + + searchResult <- + runFedClient @"search-users" fedBrigClient (Domain "example.com") $ + SearchRequest (fromHandle handle) + liftIO $ do + let contacts = contactQualifiedId <$> searchResult + assertEqual "should return the user id" [quid] contacts + +testFulltextSearchSuccess :: Brig -> FedClient 'Brig -> Http () +testFulltextSearchSuccess brig fedBrigClient = do + (_, user) <- createUserWithHandle brig + refreshIndex brig + let quid = userQualifiedId user - -- create another user with a similar handle and the same display name - -- That user should not be returned in search results. - -- (as federated search should only search for exact handle matches) + searchResult <- + runFedClient @"search-users" fedBrigClient (Domain "example.com") $ + SearchRequest ((fromName . userDisplayName) user) + liftIO $ do + let contacts = contactQualifiedId <$> searchResult + assertEqual "should return the user id" [quid] contacts + +testFulltextSearchMultipleUsers :: Brig -> FedClient 'Brig -> Http () +testFulltextSearchMultipleUsers brig fedBrigClient = do + (handle, user) <- createUserWithHandle brig + + let quid = userQualifiedId user + + -- Create another user with a display name matching the first user's handle. + -- Both users should be returned in search results when freetext search is enabled. identityThief <- randomUser brig - void $ putHandle brig (userId identityThief) (fromHandle handle <> "a") update'' :: UserUpdate <- liftIO $ generate arbitrary let update' = update'' {uupName = Just (Name (fromHandle handle))} update = RequestBodyLBS . encode $ update' put (brig . path "/self" . contentJson . zUser (userId identityThief) . zConn "c" . body update) !!! const 200 === statusCode + refreshIndex brig searchResult <- @@ -110,7 +139,7 @@ testSearchSuccess brig fedBrigClient = do SearchRequest (fromHandle handle) liftIO $ do let contacts = contactQualifiedId <$> searchResult - assertEqual "should return only the first user id but not the identityThief" [quid] contacts + assertEqual "should find both users" (sort [quid, userQualifiedId identityThief]) (sort contacts) testSearchNotFound :: FedClient 'Brig -> Http () testSearchNotFound fedBrigClient = do diff --git a/services/brig/test/integration/API/Provider.hs b/services/brig/test/integration/API/Provider.hs index 5bbc50e43b7..1fff41468d4 100644 --- a/services/brig/test/integration/API/Provider.hs +++ b/services/brig/test/integration/API/Provider.hs @@ -1291,7 +1291,7 @@ createConvWithAccessRoles ars g u us = . header "Z-Type" "access" . header "Z-Connection" "conn" . contentJson - . body (RequestBodyLBS (encode (NewConvUnmanaged conv))) + . body (RequestBodyLBS (encode conv)) where conv = NewConv us [] Nothing Set.empty ars Nothing Nothing Nothing roleNameWireAdmin diff --git a/services/brig/test/integration/API/Team/Util.hs b/services/brig/test/integration/API/Team/Util.hs index 34700b3a301..4a4d9c9ad29 100644 --- a/services/brig/test/integration/API/Team/Util.hs +++ b/services/brig/test/integration/API/Team/Util.hs @@ -36,7 +36,7 @@ import Data.Misc (Milliseconds) import Data.Range import qualified Data.Set as Set import qualified Data.Text.Encoding as T -import Galley.Types (ConvTeamInfo (..), NewConv (..), NewConvManaged (..), NewConvUnmanaged (..)) +import Galley.Types (ConvTeamInfo (..), NewConv (..)) import Galley.Types.Conversations.Roles (roleNameWireAdmin) import qualified Galley.Types.Teams as Team import qualified Galley.Types.Teams.Intra as Team @@ -209,10 +209,9 @@ updatePermissions from tid (to, perm) galley = createTeamConv :: HasCallStack => Galley -> TeamId -> UserId -> [UserId] -> Maybe Milliseconds -> Http ConvId createTeamConv g tid u us mtimer = do - let tinfo = Just $ ConvTeamInfo tid False + let tinfo = Just $ ConvTeamInfo tid let conv = - NewConvUnmanaged $ - NewConv us [] Nothing (Set.fromList []) Nothing tinfo mtimer Nothing roleNameWireAdmin + NewConv us [] Nothing (Set.fromList []) Nothing tinfo mtimer Nothing roleNameWireAdmin r <- post ( g @@ -228,28 +227,6 @@ createTeamConv g tid u us mtimer = do fromByteString $ getHeader' "Location" r --- See Note [managed conversations] -createManagedConv :: HasCallStack => Galley -> TeamId -> UserId -> [UserId] -> Maybe Milliseconds -> Http ConvId -createManagedConv g tid u us mtimer = do - let tinfo = Just $ ConvTeamInfo tid True - let conv = - NewConvManaged $ - NewConv us [] Nothing (Set.fromList []) Nothing tinfo mtimer Nothing roleNameWireAdmin - r <- - post - ( g - . path "/i/conversations/managed" - . zUser u - . zConn "conn" - . contentJson - . lbytes (encode conv) - ) - Galley -> TeamId -> ConvId -> UserId -> Http () deleteTeamConv g tid cid u = do delete diff --git a/services/brig/test/integration/API/User/Account.hs b/services/brig/test/integration/API/User/Account.hs index 77074404f81..808f075999d 100644 --- a/services/brig/test/integration/API/User/Account.hs +++ b/services/brig/test/integration/API/User/Account.hs @@ -137,6 +137,7 @@ tests _ at opts p b c ch g aws = test' aws p "put /access/self/email - 2xx" $ testEmailUpdate b aws, test' aws p "put /self/phone - 202" $ testPhoneUpdate b, test' aws p "put /self/phone - 403" $ testPhoneUpdateBlacklisted b, + test' aws p "put /self/phone - 409" $ testPhoneUpdateConflict b, test' aws p "head /self/password - 200/404" $ testPasswordSet b, test' aws p "put /self/password - 200" $ testPasswordChange b, test' aws p "put /self/locale - 200" $ testUserLocaleUpdate b aws, @@ -823,12 +824,13 @@ testUserUpdate brig cannon aws = do bobUser <- randomUser brig liftIO $ Util.assertUserJournalQueue "user create bob" aws (userActivateJournaled bobUser) let bob = userId bobUser + aliceNewName <- randomName connectUsers brig alice (singleton bob) let newColId = Just 5 newAssets = Just [ImageAsset "abc" (Just AssetComplete)] - newName = Just $ Name "dogbert" + mNewName = Just $ aliceNewName newPic = Nothing -- Legacy - userUpdate = UserUpdate newName newPic newAssets newColId + userUpdate = UserUpdate mNewName newPic newAssets newColId update = RequestBodyLBS . encode $ userUpdate -- Update profile & receive notification WS.bracketRN cannon [alice, bob] $ \[aliceWS, bobWS] -> do @@ -840,7 +842,7 @@ testUserUpdate brig cannon aws = do -- get the updated profile get (brig . path "/self" . zUser alice) !!! do const 200 === statusCode - const (newName, newColId, newAssets) + const (mNewName, newColId, newAssets) === ( \u -> ( fmap userDisplayName u, fmap userAccentId u, @@ -848,18 +850,10 @@ testUserUpdate brig cannon aws = do ) ) . responseJsonMaybe - -- get only the new name - get (brig . path "/self/name" . zUser alice) !!! do - const 200 === statusCode - const (String . fromName <$> newName) - === ( \r -> do - b <- responseBody r - b ^? key "name" - ) -- should appear in search by 'newName' suid <- userId <$> randomUser brig Search.refreshIndex brig - Search.assertCanFind brig suid aliceQ "dogbert" + Search.assertCanFind brig suid aliceQ (fromName aliceNewName) -- This tests the behavior of `/i/self/email` instead of `/self/email` or -- `/access/self/email`. tests for session token handling under `/access/self/email` are in @@ -934,6 +928,17 @@ testPhoneUpdateBlacklisted brig = do -- cleanup to avoid other tests failing sporadically deletePrefix brig (phonePrefix prefix) +testPhoneUpdateConflict :: Brig -> Http () +testPhoneUpdateConflict brig = do + uid1 <- userId <$> randomUser brig + phn <- randomPhone + updatePhone brig uid1 phn + + uid2 <- userId <$> randomUser brig + let phoneUpdate = RequestBodyLBS . encode $ PhoneUpdate phn + put (brig . path "/self/phone" . contentJson . zUser uid2 . zConn "c" . body phoneUpdate) + !!! (const 409 === statusCode) + testCreateAccountPendingActivationKey :: Opt.Opts -> Brig -> Http () testCreateAccountPendingActivationKey (Opt.setRestrictUserCreation . Opt.optSettings -> Just True) _ = pure () testCreateAccountPendingActivationKey _ brig = do @@ -1067,7 +1072,7 @@ testPasswordChange brig = do put (brig . path "/self/password" . contentJson . zUser uid . body pwChange) !!! const 200 === statusCode -- login with new password - login brig (PasswordLogin (LoginByEmail email) newPass Nothing) PersistentCookie + login brig (PasswordLogin (LoginByEmail email) newPass Nothing Nothing) PersistentCookie !!! const 200 === statusCode -- try to change the password to itself should fail put (brig . path "/self/password" . contentJson . zUser uid . body pwChange') diff --git a/services/brig/test/integration/API/User/Auth.hs b/services/brig/test/integration/API/User/Auth.hs index dc0691bb902..73e0375fd4f 100644 --- a/services/brig/test/integration/API/User/Auth.hs +++ b/services/brig/test/integration/API/User/Auth.hs @@ -318,7 +318,7 @@ testHandleLogin brig = do let update = RequestBodyLBS . encode $ HandleUpdate hdl put (brig . path "/self/handle" . contentJson . zUser usr . zConn "c" . Http.body update) !!! const 200 === statusCode - let l = PasswordLogin (LoginByHandle (Handle hdl)) defPassword Nothing + let l = PasswordLogin (LoginByHandle (Handle hdl)) defPassword Nothing Nothing login brig l PersistentCookie !!! const 200 === statusCode -- | Check that local part after @+@ is ignored by equality on email addresses if the domain is @@ -370,11 +370,11 @@ testLoginFailure brig = do Just email <- userEmail <$> randomUser brig -- login with wrong password let badpw = PlainTextPassword "wrongpassword" - login brig (PasswordLogin (LoginByEmail email) badpw Nothing) PersistentCookie + login brig (PasswordLogin (LoginByEmail email) badpw Nothing Nothing) PersistentCookie !!! const 403 === statusCode -- login with wrong / non-existent email let badmail = Email "wrong" "wire.com" - login brig (PasswordLogin (LoginByEmail badmail) defPassword Nothing) PersistentCookie + login brig (PasswordLogin (LoginByEmail badmail) defPassword Nothing Nothing) PersistentCookie !!! const 403 === statusCode -- @END @@ -402,7 +402,7 @@ testThrottleLogins conf b = do login b (defEmailLogin e) SessionCookie !!! const 200 === statusCode -- The testLimitRetries test conforms to the following testing standards: --- @SF.Channel @TSFI.RESTfulAPI @S2 +-- @SF.Channel @TSFI.RESTfulAPI @TSFI.NTP @S2 -- -- The following test tests the login retries. It checks that a user can make -- only a prespecified number of attempts to log in with an invalid password, @@ -585,7 +585,7 @@ testNoUserSsoLogin brig = do -- Token Refresh -- The testInvalidCookie test conforms to the following testing standards: --- @SF.Provisioning @TSFI.RESTfulAPI @S2 +-- @SF.Provisioning @TSFI.RESTfulAPI @TSFI.NTP @S2 -- -- Test that invalid and expired tokens do not work. testInvalidCookie :: forall u. ZAuth.UserTokenLike u => ZAuth.Env -> Brig -> Http () diff --git a/services/brig/test/integration/API/User/PasswordReset.hs b/services/brig/test/integration/API/User/PasswordReset.hs index a1da873e571..507f66b17c1 100644 --- a/services/brig/test/integration/API/User/PasswordReset.hs +++ b/services/brig/test/integration/API/User/PasswordReset.hs @@ -55,7 +55,7 @@ testPasswordReset brig = do -- try login login brig (defEmailLogin email) PersistentCookie !!! const 403 === statusCode - login brig (PasswordLogin (LoginByEmail email) newpw Nothing) PersistentCookie + login brig (PasswordLogin (LoginByEmail email) newpw Nothing Nothing) PersistentCookie !!! const 200 === statusCode -- reset password again to the same new password, get 400 "must be different" do diff --git a/services/brig/test/integration/API/UserPendingActivation.hs b/services/brig/test/integration/API/UserPendingActivation.hs index 5dd0cfe45c8..3c7565d8b1d 100644 --- a/services/brig/test/integration/API/UserPendingActivation.hs +++ b/services/brig/test/integration/API/UserPendingActivation.hs @@ -108,7 +108,8 @@ createScimToken spar' owner = do createToken spar' owner $ CreateScimToken { createScimTokenDescr = "testCreateToken", - createScimTokenPassword = Just defPassword + createScimTokenPassword = Just defPassword, + createScimTokenCode = Nothing } pure tok diff --git a/services/brig/test/integration/API/Version.hs b/services/brig/test/integration/API/Version.hs new file mode 100644 index 00000000000..eefdf7879c8 --- /dev/null +++ b/services/brig/test/integration/API/Version.hs @@ -0,0 +1,59 @@ +-- 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.Version (tests) where + +import Bilge +import Bilge.Assert +import Imports +import qualified Network.Wai.Utilities.Error as Wai +import Test.Tasty +import Test.Tasty.HUnit +import Util +import Wire.API.Routes.Version + +tests :: Manager -> Brig -> TestTree +tests p brig = + testGroup + "version" + [ test p "GET /api-version" $ testVersion brig, + test p "GET /v1/api-version" $ testVersionV1 brig, + test p "GET /v500/api-version" $ testUnsupportedVersion brig + ] + +testVersion :: Brig -> Http () +testVersion brig = do + vinfo <- + responseJsonError =<< get (brig . path "/api-version") + Http () +testVersionV1 brig = do + vinfo <- + responseJsonError =<< get (brig . path "/v1/api-version") + Http () +testUnsupportedVersion brig = do + e <- + responseJsonError =<< get (brig . path "/v500/api-version") + Blank.getEnv "INTEGRATION_FEDERATION_TESTS" internalApi <- API.Internal.tests brigOpts mg db b (brig iConf) gd g + let versionApi = API.Version.tests mg b withArgs otherArgs . defaultMain $ testGroup "Brig API Integration" @@ -157,7 +159,8 @@ runTests iConf brigOpts otherArgs = do userPendingActivation, browseTeam, federationEndpoints, - internalApi + internalApi, + versionApi ] <> [federationEnd2End | includeFederationTests] where diff --git a/services/brig/test/integration/Util.hs b/services/brig/test/integration/Util.hs index 70fd87e91c0..3d67663cb98 100644 --- a/services/brig/test/integration/Util.hs +++ b/services/brig/test/integration/Util.hs @@ -674,7 +674,7 @@ getConversationQualified galley usr cnv = createConversation :: (MonadIO m, MonadHttp m) => Galley -> UserId -> [Qualified UserId] -> m ResponseLBS createConversation galley zusr usersToAdd = do - let conv = NewConvUnmanaged $ NewConv [] usersToAdd (Just "gossip") mempty Nothing Nothing Nothing Nothing roleNameWireAdmin + let conv = NewConv [] usersToAdd (Just "gossip") mempty Nothing Nothing Nothing Nothing roleNameWireAdmin post $ galley . path "/conversations" @@ -820,7 +820,7 @@ defEmailLogin :: Email -> Login defEmailLogin e = emailLogin e defPassword (Just defCookieLabel) emailLogin :: Email -> PlainTextPassword -> Maybe CookieLabel -> Login -emailLogin e = PasswordLogin (LoginByEmail e) +emailLogin e pw cl = PasswordLogin (LoginByEmail e) pw cl Nothing somePrekeys :: [Prekey] somePrekeys = diff --git a/services/brig/test/unit/Test/Brig/Calling.hs b/services/brig/test/unit/Test/Brig/Calling.hs index ba2e0152021..83915bcf972 100644 --- a/services/brig/test/unit/Test/Brig/Calling.hs +++ b/services/brig/test/unit/Test/Brig/Calling.hs @@ -114,7 +114,8 @@ tests = [ testCase "deprecated endpoint" testSFTStaticDeprecatedEndpoint, testCase "v2 endpoint, no SFT static URL" testSFTStaticV2NoStaticUrl, testCase "v2 endpoint, SFT static URL without /sft_servers_all.json" testSFTStaticV2StaticUrlError, - testCase "v2 endpoint, SFT static URL with /sft_servers_all.json" testSFTStaticV2StaticUrlList + testCase "v2 endpoint, SFT static URL with /sft_servers_all.json" testSFTStaticV2StaticUrlList, + testCase "v2 endpoint, SFT static URL with with setSftListAllServers \"disabeld\"" testSFTStaticV2ListAllServersDisabled ] ] @@ -284,7 +285,7 @@ testSFTStaticDeprecatedEndpoint = do runM @IO . discardLogs . interpretSFTInMemory mempty - $ newConfig env Nothing Nothing Nothing CallsConfigDeprecated + $ newConfig env Nothing Nothing Nothing HideAllSFTServers CallsConfigDeprecated assertEqual "when SFT static URL is disabled, sft_servers should be empty." Set.empty @@ -308,7 +309,7 @@ testSFTStaticV2NoStaticUrl = do runM @IO . discardLogs . interpretSFTInMemory mempty - $ newConfig env Nothing (Just sftEnv) (Just . unsafeRange $ 2) CallsConfigV2 + $ newConfig env Nothing (Just sftEnv) (Just . unsafeRange $ 2) ListAllSFTServers CallsConfigV2 assertEqual "when SFT static URL is disabled, sft_servers_all should be from SFT environment" (Just . fmap (sftServerFromSrvTarget . srvTarget) . toList $ servers) @@ -323,9 +324,9 @@ testSFTStaticV2StaticUrlError = do . discardLogs . interpretSFTInMemory mempty -- an empty lookup map, meaning there was -- an error - $ newConfig env (Just staticUrl) Nothing (Just . unsafeRange $ 2) CallsConfigV2 + $ newConfig env (Just staticUrl) Nothing (Just . unsafeRange $ 2) ListAllSFTServers CallsConfigV2 assertEqual - "when SFT static URL is enabled, but returns error, sft_servers_all should be omitted" + "when SFT static URL is enabled (and setSftListAllServers is enabled), but returns error, sft_servers_all should be omitted" Nothing (cfg ^. rtcConfSftServersAll) @@ -340,8 +341,24 @@ testSFTStaticV2StaticUrlList = do runM @IO . discardLogs . interpretSFTInMemory (Map.singleton staticUrl (SFTGetResponse . Right $ servers)) - $ newConfig env (Just staticUrl) Nothing (Just . unsafeRange $ 3) CallsConfigV2 + $ newConfig env (Just staticUrl) Nothing (Just . unsafeRange $ 3) ListAllSFTServers CallsConfigV2 assertEqual - "when SFT static URL is enabled, sft_servers_all should be from /sft_servers_all.json" + "when SFT static URL and setSftListAllServers are enabled, sft_servers_all should be from /sft_servers_all.json" (Just servers) (cfg ^. rtcConfSftServersAll) + +testSFTStaticV2ListAllServersDisabled :: IO () +testSFTStaticV2ListAllServersDisabled = do + (env, staticUrl) <- sftStaticEnv + -- 10 servers compared to the limit of 3 below that should be disregarded + -- for sft_servers_all + servers <- generate $ replicateM 10 arbitrary + cfg <- + runM @IO + . discardLogs + . interpretSFTInMemory (Map.singleton staticUrl (SFTGetResponse . Right $ servers)) + $ newConfig env (Just staticUrl) Nothing (Just . unsafeRange $ 3) HideAllSFTServers CallsConfigV2 + assertEqual + "when SFT static URL is enabled and setSftListAllServers is \"disabled\" then sft_servers_all is missing" + Nothing + (cfg ^. rtcConfSftServersAll) diff --git a/services/cannon/cannon.cabal b/services/cannon/cannon.cabal index c8571bd1fa2..afe223905a5 100644 --- a/services/cannon/cannon.cabal +++ b/services/cannon/cannon.cabal @@ -85,6 +85,7 @@ library , bilge >=0.12 , bytestring >=0.10 , bytestring-conversion >=0.2 + , conduit >=1.3.4.2 , data-default >=0.5 , data-timeout >=0.3 , exceptions >=0.6 @@ -100,6 +101,7 @@ library , retry >=0.7 , safe-exceptions , servant + , servant-conduit , servant-server , strict >=0.3.2 , swagger >=0.2 diff --git a/services/cannon/package.yaml b/services/cannon/package.yaml index c784a2bc4bd..274eca7524b 100644 --- a/services/cannon/package.yaml +++ b/services/cannon/package.yaml @@ -22,6 +22,7 @@ library: - bilge >=0.12 - bytestring >=0.10 - bytestring-conversion >=0.2 + - conduit >=1.3.4.2 - data-default >=0.5 - data-timeout >=0.3 - exceptions >=0.6 @@ -35,6 +36,7 @@ library: - retry >=0.7 - safe-exceptions - servant + - servant-conduit - servant-server - strict >=0.3.2 - swagger >=0.2 diff --git a/services/cannon/src/Cannon/API/Internal.hs b/services/cannon/src/Cannon/API/Internal.hs index de3f20d495b..03c895687a7 100644 --- a/services/cannon/src/Cannon/API/Internal.hs +++ b/services/cannon/src/Cannon/API/Internal.hs @@ -1,3 +1,7 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DerivingVia #-} +{-# LANGUAGE StandaloneDeriving #-} + -- This file is part of the Wire Server implementation. -- -- Copyright (C) 2022 Wire Swiss GmbH @@ -16,7 +20,8 @@ -- with this program. If not, see . module Cannon.API.Internal - ( sitemap, + ( InternalAPI, + internalServer, ) where @@ -26,61 +31,82 @@ import Cannon.Types import Cannon.WS import Control.Monad.Catch import Data.Aeson (encode) -import qualified Data.ByteString.Lazy as L -import Data.Id (ConnId, UserId) -import Data.Swagger.Build.Api hiding (Response) +import Data.Id hiding (client) import Gundeck.Types import Gundeck.Types.BulkPush -import Imports hiding (head) -import Network.HTTP.Types -import Network.Wai -import Network.Wai.Predicate -import Network.Wai.Routing -import Network.Wai.Utilities +import Imports +import Network.WebSockets +import Servant +import Servant.Conduit () import System.Logger.Class (msg, val) import qualified System.Logger.Class as LC +import Wire.API.ErrorDescription +import Wire.API.RawJson +import Wire.API.Routes.MultiVerb +import Wire.API.Routes.Named -sitemap :: Routes ApiBuilder Cannon () -sitemap = do - get "/i/status" (continue (const $ return empty)) true - head "/i/status" (continue (const $ return empty)) true - - post "/i/push/:user/:conn" (continue pushH) $ - capture "user" .&. capture "conn" .&. request - - post "/i/bulkpush" (continue bulkpushH) $ - request - - head "/i/presences/:uid/:conn" (continue checkPresenceH) $ - param "uid" .&. param "conn" - -pushH :: UserId ::: ConnId ::: Request -> Cannon Response -pushH (user ::: conn ::: req) = - singlePush (readBody req) (PushTarget user conn) >>= \case - PushStatusOk -> return empty - PushStatusGone -> return $ errorRs status410 "general" "client gone" +type InternalAPI = + "i" + :> ( Named + "get-status" + ( "status" + :> MultiVerb + 'GET + '[PlainText] + '[RespondEmpty 200 "Service is alive."] + () + ) + :<|> Named + "push-notification" + ( "push" + :> Capture "user" UserId + :> Capture "conn" ConnId + :> ReqBody '[JSON] RawJson + :> MultiVerb + 'POST + '[JSON] + '[ ClientGone, + RespondEmpty 200 "Successfully pushed." + ] + (Maybe ()) + ) + :<|> Named + "bulk-push-notifications" + ( "bulkpush" + :> ReqBody '[JSON] BulkPushRequest + :> Post '[JSON] BulkPushResponse + ) + :<|> Named + "check-presence" + ( "presences" + :> Capture "uid" UserId + :> Capture "conn" ConnId + :> MultiVerb + 'HEAD + '[JSON] + '[ PresenceNotRegistered, + RespondEmpty 200 "Presence checked successfully." + ] + (Maybe ()) + ) + ) --- | Parse the entire list of notifcations and targets, then call 'singlePush' on the each of them --- in order. -bulkpushH :: Request -> Cannon Response -bulkpushH req = json <$> (parseBody' (JsonRequest req) >>= bulkpush) +internalServer :: ServerT InternalAPI Cannon +internalServer = + Named @"get-status" (pure ()) + :<|> Named @"push-notification" pushHandler + :<|> Named @"bulk-push-notifications" bulkPushHandler + :<|> Named @"check-presence" checkPresenceHandler --- | The typed part of 'bulkpush'. -bulkpush :: BulkPushRequest -> Cannon BulkPushResponse -bulkpush (BulkPushRequest notifs) = - BulkPushResponse . mconcat . zipWith compileResp notifs <$> (uncurry doNotif `mapM` notifs) - where - doNotif :: Notification -> [PushTarget] -> Cannon [PushStatus] - doNotif (pure . encode -> notif) = mapConcurrentlyCannon (singlePush notif) - compileResp :: - (Notification, [PushTarget]) -> - [PushStatus] -> - [(NotificationId, PushTarget, PushStatus)] - compileResp (notif, prcs) pss = zip3 (repeat (ntfId notif)) prcs pss +pushHandler :: UserId -> ConnId -> RawJson -> Cannon (Maybe ()) +pushHandler user conn body = + singlePush (rawJsonBytes body) (PushTarget user conn) >>= \case + PushStatusOk -> pure $ Just () + PushStatusGone -> pure Nothing --- | Take a serialized 'Notification' string and send it to the 'PushTarget'. -singlePush :: Cannon L.ByteString -> PushTarget -> Cannon PushStatus -singlePush notification (PushTarget usrid conid) = do +-- | Take notification @n@ and send it to the 'PushTarget'. +singlePush :: (WebSocketsData a) => a -> PushTarget -> Cannon PushStatus +singlePush n (PushTarget usrid conid) = do let k = mkKey usrid conid d <- clients LC.debug $ client (key2bytes k) . msg (val "push") @@ -91,15 +117,28 @@ singlePush notification (PushTarget usrid conid) = do return PushStatusGone Just x -> do e <- wsenv - b <- notification - runWS e $ - (sendMsg b k x >> return PushStatusOk) - `catchAll` const (terminate k x >> return PushStatusGone) + runWS e $ do + catchAll + (runWS e (sendMsg n k x) >> pure PushStatusOk) + (const (terminate k x >> pure PushStatusGone)) + +bulkPushHandler :: BulkPushRequest -> Cannon BulkPushResponse +bulkPushHandler (BulkPushRequest ns) = + BulkPushResponse . mconcat . zipWith compileResp ns <$> (uncurry doNotify `Imports.mapM` ns) + where + doNotify :: Notification -> [PushTarget] -> Cannon [PushStatus] + doNotify (encode -> notification) = + mapConcurrentlyCannon (singlePush notification) + compileResp :: + (Notification, [PushTarget]) -> + [PushStatus] -> + [(NotificationId, PushTarget, PushStatus)] + compileResp (notif, prcs) pss = zip3 (repeat (ntfId notif)) prcs pss -checkPresenceH :: UserId ::: ConnId -> Cannon Response -checkPresenceH (u ::: c) = do +checkPresenceHandler :: UserId -> ConnId -> Cannon (Maybe ()) +checkPresenceHandler u c = do e <- wsenv registered <- runWS e $ isRemoteRegistered u c if registered - then return empty - else return $ errorRs status404 "not-found" "presence not registered" + then pure $ Just () + else pure Nothing diff --git a/services/cannon/src/Cannon/API/Public.hs b/services/cannon/src/Cannon/API/Public.hs index 8e6fff49065..0eb81bf5fb1 100644 --- a/services/cannon/src/Cannon/API/Public.hs +++ b/services/cannon/src/Cannon/API/Public.hs @@ -16,8 +16,7 @@ -- with this program. If not, see . module Cannon.API.Public - ( API, - publicAPIServer, + ( publicAPIServer, ) where @@ -29,12 +28,11 @@ import Data.Id import GHC.Base import Network.WebSockets.Connection import Servant +import Wire.API.Routes.Named import Wire.API.Routes.Public.Cannon -type API = ServantAPI :<|> Raw - -publicAPIServer :: ServerT ServantAPI Cannon -publicAPIServer = streamData +publicAPIServer :: ServerT PublicAPI Cannon +publicAPIServer = Named @"await-notifications" streamData streamData :: UserId -> ConnId -> Maybe ClientId -> PendingConnection -> Cannon () streamData userId connId clientId con = do diff --git a/services/cannon/src/Cannon/App.hs b/services/cannon/src/Cannon/App.hs index 4435d779e4c..a49e3681911 100644 --- a/services/cannon/src/Cannon/App.hs +++ b/services/cannon/src/Cannon/App.hs @@ -149,7 +149,7 @@ readLoop ws s = loop (DataMessage _ _ _ (Text "ping" _)) -> True (DataMessage _ _ _ (Binary "ping")) -> True _ -> False - sendAppLevelPong = sendMsgIO "pong" ws + sendAppLevelPong = sendMsgIO @ByteString "pong" ws rejectOnError :: PendingConnection -> HandshakeException -> IO a rejectOnError p x = do diff --git a/services/cannon/src/Cannon/Run.hs b/services/cannon/src/Cannon/Run.hs index d4902460d39..55c190f0663 100644 --- a/services/cannon/src/Cannon/Run.hs +++ b/services/cannon/src/Cannon/Run.hs @@ -17,6 +17,7 @@ module Cannon.Run ( run, + CombinedAPI, ) where @@ -26,7 +27,7 @@ import Cannon.API.Public import Cannon.App (maxPingInterval) import qualified Cannon.Dict as D import Cannon.Options -import Cannon.Types (Cannon, applog, clients, mkEnv, monitor, runCannon, runCannon', runCannonToServant) +import Cannon.Types (Cannon, applog, clients, mkEnv, monitor, runCannon', runCannonToServant) import Cannon.WS hiding (env) import qualified Control.Concurrent.Async as Async import Control.Exception.Safe (catchAny) @@ -49,6 +50,9 @@ import qualified System.Logger.Class as LC import qualified System.Logger.Extended as L import System.Random.MWC (createSystemRandom) import Wire.API.Routes.Public.Cannon +import Wire.API.Routes.Version.Wai + +type CombinedAPI = PublicAPI :<|> InternalAPI run :: Opts -> IO () run o = do @@ -66,17 +70,18 @@ run o = do <*> mkClock refreshMetricsThread <- Async.async $ runCannon' e refreshMetrics s <- newSettings $ Server (o ^. cannon . host) (o ^. cannon . port) (applog e) m (Just idleTimeout) - let rtree = compile sitemap - internalApp r k = runCannon e (Network.Wai.Utilities.Server.route rtree r k) r - middleware :: Wai.Middleware + let middleware :: Wai.Middleware middleware = - servantPlusWAIPrometheusMiddleware sitemap (Proxy @ServantAPI) + servantPrometheusMiddleware (Proxy @CombinedAPI) . Gzip.gzip Gzip.def . catchErrors g [Right m] + . versionMiddleware app :: Application - app = middleware (serve (Proxy @API) server) - server :: Servant.Server API - server = hoistServer (Proxy @ServantAPI) (runCannonToServant e) publicAPIServer :<|> Tagged internalApp + app = middleware (serve (Proxy @CombinedAPI) server) + server :: Servant.Server CombinedAPI + server = + hoistServer (Proxy @PublicAPI) (runCannonToServant e) publicAPIServer + :<|> hoistServer (Proxy @InternalAPI) (runCannonToServant e) internalServer runSettings s app `finally` do Async.cancel refreshMetricsThread L.close (applog e) diff --git a/services/cannon/src/Cannon/WS.hs b/services/cannon/src/Cannon/WS.hs index 76035acdc4b..cbcaaf0f72d 100644 --- a/services/cannon/src/Cannon/WS.hs +++ b/services/cannon/src/Cannon/WS.hs @@ -29,7 +29,6 @@ module Cannon.WS unregisterLocal, isRemoteRegistered, registerRemote, - sendMsg, sendMsgIO, Clock, mkClock, @@ -42,6 +41,7 @@ module Cannon.WS mkKey, key2bytes, client, + sendMsg, ) where @@ -50,6 +50,7 @@ import Bilge.RPC import Bilge.Retry import Cannon.Dict (Dict) import qualified Cannon.Dict as D +import Conduit import Control.Concurrent.Timeout import Control.Monad.Catch import Control.Retry @@ -224,16 +225,23 @@ isRemoteRegistered u c = do cs <- map connId <$> parseResponse (mkError status502 "server-error") rs return $ c `elem` cs -sendMsg :: L.ByteString -> Key -> Websocket -> WS () -sendMsg m k c = do - let kb = key2bytes k - trace $ client kb . msg (val "sendMsg: \"" +++ L.take 128 m +++ val "...\"") - liftIO $ sendMsgIO m c - -sendMsgIO :: L.ByteString -> Websocket -> IO () -sendMsgIO m c = do +sendMsgIO :: (WebSocketsData a) => a -> Websocket -> IO () +sendMsgIO m c = recoverAll retry3x $ const $ sendBinaryData (connection c) m +sendMsg :: (WebSocketsData a) => a -> Key -> Websocket -> WS () +sendMsg message k c = do + traceLog message + liftIO $ sendMsgIO message c + where + traceLog :: (WebSocketsData a) => a -> WS () + traceLog m = trace $ client kb . msg (logMsg m) + + logMsg :: (WebSocketsData a) => a -> Builder + logMsg m = val "sendMsgConduit: \"" +++ L.take 128 (toLazyByteString m) +++ val "...\"" + + kb = key2bytes k + close :: Key -> Websocket -> WS () close k c = do let kb = key2bytes k diff --git a/services/cannon/test/Main.hs b/services/cannon/test/Main.hs index cd5591198ab..534aa711a82 100644 --- a/services/cannon/test/Main.hs +++ b/services/cannon/test/Main.hs @@ -17,11 +17,10 @@ module Main where -import qualified Cannon.API.Internal +import Cannon.API.Internal +import Data.Metrics.Servant (routesToPaths) import Data.Metrics.Test (pathsConsistencyCheck) -import Data.Metrics.WaiRoute (treeToPaths) import Imports -import Network.Wai.Utilities.Server (compile) import qualified Test.Cannon.Dict as D import Test.Tasty import Test.Tasty.HUnit @@ -35,6 +34,6 @@ main = assertEqual "inconcistent sitemap" mempty - (pathsConsistencyCheck . treeToPaths . compile $ Cannon.API.Internal.sitemap), + (pathsConsistencyCheck $ routesToPaths @InternalAPI), D.tests ] diff --git a/services/cargohold/src/CargoHold/API/Public.hs b/services/cargohold/src/CargoHold/API/Public.hs index b9c4514c9ec..41b15c2695d 100644 --- a/services/cargohold/src/CargoHold/API/Public.hs +++ b/services/cargohold/src/CargoHold/API/Public.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 CargoHold.API.Public (servantSitemap) where +module CargoHold.API.Public (servantSitemap, internalSitemap) where import qualified CargoHold.API.Legacy as LegacyAPI import CargoHold.API.Util @@ -36,17 +36,18 @@ import Servant.Server hiding (Handler) import URI.ByteString import Wire.API.Asset import Wire.API.Routes.AssetBody +import Wire.API.Routes.Internal.Cargohold import Wire.API.Routes.Public.Cargohold servantSitemap :: ServerT ServantAPI Handler servantSitemap = - renewTokenV3 :<|> deleteTokenV3 + renewTokenV3 + :<|> deleteTokenV3 :<|> userAPI :<|> botAPI :<|> providerAPI :<|> qualifiedAPI :<|> legacyAPI - :<|> internalAPI where userAPI :: forall tag. tag ~ 'UserPrincipalTag => ServerT (BaseAPIv3 tag) Handler userAPI = uploadAssetV3 @tag :<|> downloadAssetV3 @tag :<|> deleteAssetV3 @tag @@ -56,7 +57,9 @@ servantSitemap = providerAPI = uploadAssetV3 @tag :<|> downloadAssetV3 @tag :<|> deleteAssetV3 @tag legacyAPI = legacyDownloadPlain :<|> legacyDownloadPlain :<|> legacyDownloadOtr qualifiedAPI = downloadAssetV4 :<|> deleteAssetV4 - internalAPI = pure () + +internalSitemap :: ServerT InternalAPI Handler +internalSitemap = pure () class HasLocation (tag :: PrincipalTag) where assetLocation :: Local AssetKey -> [Text] diff --git a/services/cargohold/src/CargoHold/Run.hs b/services/cargohold/src/CargoHold/Run.hs index 7254b96efe4..f6a5201d93c 100644 --- a/services/cargohold/src/CargoHold/Run.hs +++ b/services/cargohold/src/CargoHold/Run.hs @@ -44,9 +44,11 @@ import qualified Servant import Servant.API import Servant.Server hiding (Handler, runHandler) import Util.Options -import qualified Wire.API.Routes.Public.Cargohold as Public +import Wire.API.Routes.Internal.Cargohold +import Wire.API.Routes.Public.Cargohold +import Wire.API.Routes.Version.Wai -type CombinedAPI = FederationAPI :<|> Public.ServantAPI +type CombinedAPI = FederationAPI :<|> ServantAPI :<|> InternalAPI run :: Opts -> IO () run o = lowerCodensity $ do @@ -71,13 +73,15 @@ mkApp o = Codensity $ \k -> servantPrometheusMiddleware (Proxy @CombinedAPI) . GZip.gzip GZip.def . catchErrors (e ^. appLogger) [Right $ e ^. metrics] + . versionMiddleware servantApp e0 r = let e = set requestId (maybe def RequestId (lookupRequestId r)) e0 in Servant.serveWithContext (Proxy @CombinedAPI) ((o ^. optSettings . setFederationDomain) :. Servant.EmptyContext) ( hoistServer' @FederationAPI (toServantHandler e) federationSitemap - :<|> hoistServer' @Public.ServantAPI (toServantHandler e) servantSitemap + :<|> hoistServer' @ServantAPI (toServantHandler e) servantSitemap + :<|> hoistServer' @InternalAPI (toServantHandler e) internalSitemap ) r diff --git a/services/federator/src/Federator/Remote.hs b/services/federator/src/Federator/Remote.hs index 9d0222617ae..d831563e7d0 100644 --- a/services/federator/src/Federator/Remote.hs +++ b/services/federator/src/Federator/Remote.hs @@ -161,20 +161,22 @@ mkTLSConfig settings hostname port = TLS.clientShared = def {TLS.sharedCAStore = settings ^. caStore} } --- FUTUREWORK: get review on blessed ciphers --- (https://wearezeta.atlassian.net/browse/SQCORE-910) +-- Context and possible future work see +-- https://wearezeta.atlassian.net/browse/FS-33 +-- https://wearezeta.atlassian.net/browse/FS-444 +-- https://wearezeta.atlassian.net/browse/FS-443 +-- +-- The current list is compliant to TR-02102-2 +-- https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/TechGuidelines/TG02102/BSI-TR-02102-2.html blessedCiphers :: [Cipher] blessedCiphers = [ TLS.cipher_TLS13_AES128CCM8_SHA256, TLS.cipher_TLS13_AES128CCM_SHA256, TLS.cipher_TLS13_AES128GCM_SHA256, TLS.cipher_TLS13_AES256GCM_SHA384, - TLS.cipher_TLS13_CHACHA20POLY1305_SHA256, -- For TLS 1.2 (copied from default nginx ingress config): TLS.cipher_ECDHE_ECDSA_AES256GCM_SHA384, TLS.cipher_ECDHE_RSA_AES256GCM_SHA384, - TLS.cipher_ECDHE_RSA_AES128GCM_SHA256, TLS.cipher_ECDHE_ECDSA_AES128GCM_SHA256, - TLS.cipher_ECDHE_ECDSA_CHACHA20POLY1305_SHA256, - TLS.cipher_ECDHE_RSA_CHACHA20POLY1305_SHA256 + TLS.cipher_ECDHE_RSA_AES128GCM_SHA256 ] diff --git a/services/federator/test/unit/Test/Federator/Client.hs b/services/federator/test/unit/Test/Federator/Client.hs index 225a2dbd071..ddd318e02d2 100644 --- a/services/federator/test/unit/Test/Federator/Client.hs +++ b/services/federator/test/unit/Test/Federator/Client.hs @@ -36,7 +36,7 @@ import qualified Network.HTTP2.Client as HTTP2 import qualified Network.Wai as Wai import qualified Network.Wai.Utilities.Error as Wai import Servant.API -import Servant.Client +import Servant.Client hiding ((//)) import Servant.Client.Core import Servant.Types.SourceT import Test.QuickCheck (arbitrary, generate) diff --git a/services/federator/test/unit/Test/Federator/InternalServer.hs b/services/federator/test/unit/Test/Federator/InternalServer.hs index f4560fd9f3b..89e79ff1837 100644 --- a/services/federator/test/unit/Test/Federator/InternalServer.hs +++ b/services/federator/test/unit/Test/Federator/InternalServer.hs @@ -100,7 +100,7 @@ federatedRequestSuccess = body <- Wai.lazyResponseBody res body @?= "\"bar\"" --- @SF.Federation @TSFI.RESTfulAPI @S2 @S3 @S7 +-- @SF.Federation @TSFI.Federate @TSFI.DNS @S2 @S3 @S7 -- -- Refuse to send outgoing request to non-included domain when allowlist is configured. federatedRequestFailureAllowList :: TestTree diff --git a/services/federator/test/unit/Test/Federator/Options.hs b/services/federator/test/unit/Test/Federator/Options.hs index b891fc6d810..b6b2e884b05 100644 --- a/services/federator/test/unit/Test/Federator/Options.hs +++ b/services/federator/test/unit/Test/Federator/Options.hs @@ -171,7 +171,7 @@ testSettings = assertFailure $ "expected failure for non-existing client certificate, got: " <> show (tlsSettings ^. creds), - -- @SF.Federation @TSFI.RESTfulAPI @S3 @S7 + -- @SF.Federation @TSFI.Federate @S3 @S7 testCase "failToStartWithInvalidServerCredentials" $ do let settings = defRunSettings diff --git a/services/federator/test/unit/Test/Federator/Remote.hs b/services/federator/test/unit/Test/Federator/Remote.hs index 842feba3602..67512bcdbb6 100644 --- a/services/federator/test/unit/Test/Federator/Remote.hs +++ b/services/federator/test/unit/Test/Federator/Remote.hs @@ -120,7 +120,7 @@ testValidatesCertificateSuccess = Right _ -> assertFailure "Congratulations, you fixed a known issue!" ] --- @SF.Federation @TSFI.RESTfulAPI @S2 +-- @SF.Federation @TSFI.Federate @TSFI.DNS @S2 -- -- This is a group of test cases where refusing to connect with the server is -- checked. The second test case refuses to connect with a server when the diff --git a/services/federator/test/unit/Test/Federator/Validation.hs b/services/federator/test/unit/Test/Federator/Validation.hs index 33ff845826c..498a4079e8f 100644 --- a/services/federator/test/unit/Test/Federator/Validation.hs +++ b/services/federator/test/unit/Test/Federator/Validation.hs @@ -117,7 +117,7 @@ validateDomainAllowListFailSemantic = $ validateDomain (Just exampleCert) "invalid//.><-semantic-&@-domain" res @?= Left (DomainParseError "invalid//.><-semantic-&@-domain") --- @SF.Federation @TSFI.RESTfulAPI @S2 @S3 @S7 +-- @SF.Federation @TSFI.Federate @TSFI.DNS @S2 @S3 @S7 -- -- Refuse to send outgoing request to non-included domain when allowlist is configured. validateDomainAllowListFail :: TestTree @@ -162,7 +162,7 @@ validateDomainCertMissing = $ validateDomain Nothing "foo.example.com" res @?= Left NoClientCertificate --- @SF.Federation @TSFI.RESTfulAPI @S2 @S3 @S7 +-- @SF.Federation @TSFI.Federate @TSFI.DNS @S2 @S3 @S7 validateDomainCertInvalid :: TestTree validateDomainCertInvalid = testCase "should fail if the client certificate is invalid" $ do @@ -176,7 +176,7 @@ validateDomainCertInvalid = -- @END --- @SF.Federation @TSFI.RESTfulAPI @S3 @S7 +-- @SF.Federation @TSFI.Federate @TSFI.DNS @S3 @S7 -- -- Reject request if the infrastructure domain in the client cert does not match the backend -- domain in the `Wire-origin-domain` header. diff --git a/services/galley/galley.cabal b/services/galley/galley.cabal index 35ef91c0317..66d82b2f610 100644 --- a/services/galley/galley.cabal +++ b/services/galley/galley.cabal @@ -598,6 +598,7 @@ executable galley-schema V57_GuestLinksLockStatus V58_ConversationAccessRoleV2 V59_FileSharingLockStatus + V60_TeamFeatureSndFactorPasswordChallenge Paths_galley hs-source-dirs: schema/src diff --git a/services/galley/schema/src/Main.hs b/services/galley/schema/src/Main.hs index 450b1342320..ae17ad977e3 100644 --- a/services/galley/schema/src/Main.hs +++ b/services/galley/schema/src/Main.hs @@ -62,6 +62,7 @@ import qualified V56_GuestLinksTeamFeatureStatus import qualified V57_GuestLinksLockStatus import qualified V58_ConversationAccessRoleV2 import qualified V59_FileSharingLockStatus +import qualified V60_TeamFeatureSndFactorPasswordChallenge main :: IO () main = do @@ -109,7 +110,8 @@ main = do V56_GuestLinksTeamFeatureStatus.migration, V57_GuestLinksLockStatus.migration, V58_ConversationAccessRoleV2.migration, - V59_FileSharingLockStatus.migration + V59_FileSharingLockStatus.migration, + V60_TeamFeatureSndFactorPasswordChallenge.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/V60_TeamFeatureSndFactorPasswordChallenge.hs b/services/galley/schema/src/V60_TeamFeatureSndFactorPasswordChallenge.hs new file mode 100644 index 00000000000..f71ab44871f --- /dev/null +++ b/services/galley/schema/src/V60_TeamFeatureSndFactorPasswordChallenge.hs @@ -0,0 +1,34 @@ +-- 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 V60_TeamFeatureSndFactorPasswordChallenge + ( migration, + ) +where + +import Cassandra.Schema +import Imports +import Text.RawString.QQ + +migration :: Migration +migration = Migration 60 "Add feature config for team feature snd factor password challenge" $ do + schema' + [r| ALTER TABLE team_features ADD ( + snd_factor_password_challenge_status int, + snd_factor_password_challenge_lock_status int + ) + |] diff --git a/services/galley/src/Galley/API.hs b/services/galley/src/Galley/API.hs index bc6d83c4d6d..fe28713db29 100644 --- a/services/galley/src/Galley/API.hs +++ b/services/galley/src/Galley/API.hs @@ -22,7 +22,7 @@ module Galley.API where import qualified Data.Swagger.Build.Api as Doc -import qualified Galley.API.Internal as Internal +import Galley.API.Internal import qualified Galley.API.Public as Public import Galley.API.Public.Servant import Galley.App (GalleyEffects) @@ -33,4 +33,4 @@ sitemap :: Routes Doc.ApiBuilder (Sem GalleyEffects) () sitemap = do Public.sitemap Public.apiDocs - Internal.sitemap + internalSitemap diff --git a/services/galley/src/Galley/API/Create.hs b/services/galley/src/Galley/API/Create.hs index 1e1ffd12b91..68562d43fea 100644 --- a/services/galley/src/Galley/API/Create.hs +++ b/services/galley/src/Galley/API/Create.hs @@ -24,7 +24,6 @@ -- with this program. If not, see . module Galley.API.Create ( createGroupConversation, - internalCreateManagedConversationH, createSelfConversation, createOne2OneConversation, createConnectConversation, @@ -51,7 +50,6 @@ import qualified Galley.Effects.ConversationStore as E import qualified Galley.Effects.GundeckAccess as E import qualified Galley.Effects.MemberStore as E import qualified Galley.Effects.TeamStore as E -import Galley.Effects.WaiRoutes import Galley.Intra.Push import Galley.Options import Galley.Types.Conversations.Members @@ -59,16 +57,11 @@ import Galley.Types.Teams (ListType (..), Perm (..), TeamBinding (Binding), notT import Galley.Types.UserList import Galley.Validation import Imports hiding ((\\)) -import Network.HTTP.Types -import Network.Wai -import Network.Wai.Predicate hiding (Error, setStatus) -import Network.Wai.Utilities hiding (Error) import Polysemy import Polysemy.Error import Polysemy.Input import qualified Polysemy.TinyLog as P import Wire.API.Conversation hiding (Conversation, Member) -import qualified Wire.API.Conversation as Public import Wire.API.ErrorDescription import Wire.API.Event.Conversation hiding (Conversation) import Wire.API.Federation.Error @@ -103,111 +96,17 @@ createGroupConversation :: r => Local UserId -> ConnId -> - Public.NewConvUnmanaged -> + NewConv -> Sem r ConversationResponse -createGroupConversation user conn wrapped@(Public.NewConvUnmanaged body) = - case newConvTeam body of - Nothing -> createRegularGroupConv user conn wrapped - Just tinfo -> createTeamGroupConv user conn tinfo body - --- | An internal endpoint for creating managed group conversations. Will --- throw an error for everything else. -internalCreateManagedConversationH :: - Members - '[ ConversationStore, - BrigAccess, - Error ActionError, - Error ConversationError, - Error InternalError, - Error InvalidInput, - Error LegalHoldError, - Error NotATeamMember, - FederatorAccess, - GundeckAccess, - Input (Local ()), - Input Opts, - Input UTCTime, - LegalHoldStore, - TeamStore, - P.TinyLog, - WaiRoutes - ] - r => - UserId ::: ConnId ::: JsonRequest NewConvManaged -> - Sem r Response -internalCreateManagedConversationH (zusr ::: zcon ::: req) = do - lusr <- qualifyLocal zusr - newConv <- fromJsonBody req - handleConversationResponse <$> internalCreateManagedConversation lusr zcon newConv - -internalCreateManagedConversation :: - Members - '[ ConversationStore, - BrigAccess, - Error ActionError, - Error ConversationError, - Error InternalError, - Error InvalidInput, - Error LegalHoldError, - Error NotATeamMember, - FederatorAccess, - GundeckAccess, - Input Opts, - LegalHoldStore, - Input UTCTime, - TeamStore, - P.TinyLog - ] - r => - Local UserId -> - ConnId -> - NewConvManaged -> - Sem r ConversationResponse -internalCreateManagedConversation lusr zcon (NewConvManaged body) = do - tinfo <- note CannotCreateManagedConv (newConvTeam body) - createTeamGroupConv lusr zcon tinfo body - -ensureNoLegalholdConflicts :: - Members '[Error LegalHoldError, Input Opts, LegalHoldStore, TeamStore] r => - [Remote UserId] -> - [UserId] -> - Sem r () -ensureNoLegalholdConflicts remotes locals = do - let FutureWork _remotes = FutureWork @'LegalholdPlusFederationNotImplemented remotes - whenM (anyLegalholdActivated locals) $ - unlessM (allLegalholdConsentGiven locals) $ - throw MissingLegalholdConsent - --- | A helper for creating a regular (non-team) group conversation. -createRegularGroupConv :: - Members - '[ ConversationStore, - BrigAccess, - FederatorAccess, - Error ActionError, - Error InternalError, - Error InvalidInput, - Error LegalHoldError, - GundeckAccess, - Input Opts, - Input UTCTime, - LegalHoldStore, - TeamStore, - P.TinyLog - ] - r => - Local UserId -> - ConnId -> - NewConvUnmanaged -> - Sem r ConversationResponse -createRegularGroupConv lusr zcon (NewConvUnmanaged body) = do +createGroupConversation lusr conn body = do + let tinfo = newConvTeam body + allUsers = newConvMembers lusr body name <- rangeCheckedMaybe (newConvName body) - let allUsers = newConvMembers lusr body o <- input checkedUsers <- checkedConvSize o allUsers - ensureConnected lusr allUsers + checkCreateConvPermissions lusr body tinfo allUsers ensureNoLegalholdConflicts (ulRemotes allUsers) (ulLocals allUsers) - c <- + conv <- E.createConversation NewConversation { ncType = RegularConv, @@ -221,46 +120,44 @@ createRegularGroupConv lusr zcon (NewConvUnmanaged body) = do ncUsers = checkedUsers, ncRole = newConvUsersRole body } - notifyCreatedConversation Nothing lusr (Just zcon) c - conversationCreated lusr c + now <- input + -- NOTE: We only send (conversation) events to members of the conversation + notifyCreatedConversation (Just now) lusr (Just conn) conv + conversationCreated lusr conv + +ensureNoLegalholdConflicts :: + Members '[Error LegalHoldError, Input Opts, LegalHoldStore, TeamStore] r => + [Remote UserId] -> + [UserId] -> + Sem r () +ensureNoLegalholdConflicts remotes locals = do + let FutureWork _remotes = FutureWork @'LegalholdPlusFederationNotImplemented remotes + whenM (anyLegalholdActivated locals) $ + unlessM (allLegalholdConsentGiven locals) $ + throw MissingLegalholdConsent --- | A helper for creating a team group conversation, used by the endpoint --- handlers above. Only supports unmanaged conversations. -createTeamGroupConv :: +checkCreateConvPermissions :: Members - '[ ConversationStore, - BrigAccess, + '[ BrigAccess, Error ActionError, Error ConversationError, - Error InternalError, - Error InvalidInput, - Error LegalHoldError, Error NotATeamMember, - FederatorAccess, - GundeckAccess, - Input Opts, - Input UTCTime, - LegalHoldStore, - TeamStore, - P.TinyLog + TeamStore ] r => Local UserId -> - ConnId -> - Public.ConvTeamInfo -> - Public.NewConv -> - Sem r ConversationResponse -createTeamGroupConv lusr zcon tinfo body = do - name <- rangeCheckedMaybe (newConvName body) - let allUsers = newConvMembers lusr body - convTeam = cnvTeamId tinfo - + NewConv -> + Maybe ConvTeamInfo -> + UserList UserId -> + Sem r () +checkCreateConvPermissions lusr _newConv Nothing allUsers = + ensureConnected lusr allUsers +checkCreateConvPermissions lusr newConv (Just tinfo) allUsers = do + let convTeam = cnvTeamId tinfo zusrMembership <- E.getTeamMember convTeam (tUnqualified lusr) void $ permissionCheck CreateConversation zusrMembership - o <- input - checkedUsers <- checkedConvSize o allUsers convLocalMemberships <- mapM (E.getTeamMember convTeam) (ulLocals allUsers) - ensureAccessRole (accessRoles body) (zip (ulLocals allUsers) convLocalMemberships) + ensureAccessRole (accessRoles newConv) (zip (ulLocals allUsers) convLocalMemberships) -- In teams we don't have 1:1 conversations, only regular conversations. We want -- users without the 'AddRemoveConvMember' permission to still be able to create -- regular conversations, therefore we check for 'AddRemoveConvMember' only if @@ -273,29 +170,11 @@ createTeamGroupConv lusr zcon tinfo body = do -- think of is that 'partners' can create convs but not be admins... when (length allUsers > 1) $ do void $ permissionCheck DoNotUseDeprecatedAddRemoveConvMember zusrMembership + -- Team members are always considered to be connected, so we only check -- 'ensureConnected' for non-team-members. ensureConnectedToLocals (tUnqualified lusr) (notTeamMember (ulLocals allUsers) (catMaybes convLocalMemberships)) ensureConnectedToRemotes lusr (ulRemotes allUsers) - ensureNoLegalholdConflicts (ulRemotes allUsers) (ulLocals allUsers) - conv <- - E.createConversation - NewConversation - { ncType = RegularConv, - ncCreator = tUnqualified lusr, - ncAccess = access body, - ncAccessRoles = accessRoles body, - ncName = name, - ncTeam = fmap cnvTeamId (newConvTeam body), - ncMessageTimer = newConvMessageTimer body, - ncReceiptMode = newConvReceiptMode body, - ncUsers = checkedUsers, - ncRole = newConvUsersRole body - } - now <- input - -- NOTE: We only send (conversation) events to members of the conversation - notifyCreatedConversation (Just now) lusr (Just zcon) conv - conversationCreated lusr conv ---------------------------------------------------------------------------- -- Other kinds of conversations @@ -335,22 +214,20 @@ createOne2OneConversation :: r => Local UserId -> ConnId -> - NewConvUnmanaged -> + NewConv -> Sem r ConversationResponse -createOne2OneConversation lusr zcon (NewConvUnmanaged j) = do +createOne2OneConversation lusr zcon j = do let allUsers = newConvMembers lusr j other <- ensureOne (ulAll lusr allUsers) when (qUntagged lusr == other) $ throw . InvalidOp $ One2OneConv mtid <- case newConvTeam j of - Just ti - | cnvManaged ti -> throw NoManagedTeamConv - | otherwise -> do - foldQualified - lusr - (\lother -> checkBindingTeamPermissions lother (cnvTeamId ti)) - (const (pure Nothing)) - other + Just ti -> do + foldQualified + lusr + (\lother -> checkBindingTeamPermissions lother (cnvTeamId ti)) + (const (pure Nothing)) + other Nothing -> ensureConnected lusr allUsers $> Nothing n <- rangeCheckedMaybe (newConvName j) foldQualified @@ -490,41 +367,7 @@ createConnectConversation :: Connect -> Sem r ConversationResponse createConnectConversation lusr conn j = do - foldQualified - lusr - (\lrcpt -> createLegacyConnectConversation lusr conn lrcpt j) - (createConnectConversationWithRemote lusr conn) - (cRecipient j) - -createConnectConversationWithRemote :: - Member (Error FederationError) r => - Local UserId -> - Maybe ConnId -> - Remote UserId -> - Sem r ConversationResponse -createConnectConversationWithRemote _ _ _ = - throw FederationNotImplemented - -createLegacyConnectConversation :: - Members - '[ ConversationStore, - Error ActionError, - Error InvalidInput, - Error ConversationError, - Error InternalError, - FederatorAccess, - GundeckAccess, - Input UTCTime, - MemberStore, - P.TinyLog - ] - r => - Local UserId -> - Maybe ConnId -> - Local UserId -> - Connect -> - Sem r ConversationResponse -createLegacyConnectConversation lusr conn lrecipient j = do + lrecipient <- ensureLocal lusr (cRecipient j) (x, y) <- toUUIDs (tUnqualified lusr) (tUnqualified lrecipient) n <- rangeCheckedMaybe (cName j) conv <- E.getConversation (Data.localOne2OneConvId x y) @@ -601,11 +444,6 @@ conversationExisted :: Sem r ConversationResponse conversationExisted lusr cnv = Existed <$> conversationView lusr cnv -handleConversationResponse :: ConversationResponse -> Response -handleConversationResponse = \case - Created cnv -> json cnv & setStatus status201 . location (qUnqualified . cnvQualifiedId $ cnv) - Existed cnv -> json cnv & setStatus status200 . location (qUnqualified . cnvQualifiedId $ cnv) - notifyCreatedConversation :: Members '[Error InternalError, FederatorAccess, GundeckAccess, Input UTCTime, P.TinyLog] r => Maybe UTCTime -> diff --git a/services/galley/src/Galley/API/Error.hs b/services/galley/src/Galley/API/Error.hs index d6eb0aa94b1..8f8b635f22c 100644 --- a/services/galley/src/Galley/API/Error.hs +++ b/services/galley/src/Galley/API/Error.hs @@ -68,7 +68,6 @@ data ActionError | InvalidOp ConvType | OperationDenied String | NotConnected - | NoAddToManaged | BroadcastLimitExceeded | InvalidTeamStatusUpdate | InvalidPermissions @@ -85,8 +84,7 @@ instance APIError ActionError where toWai (OperationDenied p) = errorDescriptionToWai $ operationDeniedSpecialized p toWai NotConnected = errorDescriptionTypeToWai @NotConnected toWai InvalidTargetUserOp = invalidTargetUserOp - toWai NoAddToManaged = noAddToManaged - toWai BroadcastLimitExceeded = broadcastLimitExceeded + toWai BroadcastLimitExceeded = errorDescriptionTypeToWai @BroadcastLimitExceeded toWai InvalidTeamStatusUpdate = invalidTeamStatusUpdate toWai InvalidPermissions = invalidPermissions @@ -121,16 +119,14 @@ data ConversationError | TooManyMembers | ConvMemberNotFound | NoBindingTeamMembers - | NoManagedTeamConv | GuestLinksDisabled instance APIError ConversationError where toWai ConvAccessDenied = errorDescriptionTypeToWai @ConvAccessDenied toWai ConvNotFound = errorDescriptionTypeToWai @ConvNotFound - toWai TooManyMembers = tooManyMembers + toWai TooManyMembers = errorDescriptionTypeToWai @TooManyMembers toWai ConvMemberNotFound = errorDescriptionTypeToWai @ConvMemberNotFound toWai NoBindingTeamMembers = noBindingTeamMembers - toWai NoManagedTeamConv = noManagedTeamConv toWai GuestLinksDisabled = guestLinksDisabled data TeamError @@ -148,9 +144,9 @@ data TeamError instance APIError TeamError where toWai NoBindingTeam = noBindingTeam toWai NoAddToBinding = noAddToBinding - toWai NotABindingTeamMember = nonBindingTeam + toWai NotABindingTeamMember = errorDescriptionTypeToWai @NonBindingTeam toWai NotAOneMemberTeam = notAOneMemberTeam - toWai TeamNotFound = teamNotFound + toWai TeamNotFound = errorDescriptionTypeToWai @TeamNotFound toWai TeamMemberNotFound = teamMemberNotFound toWai TeamSearchVisibilityNotEnabled = teamSearchVisibilityNotEnabled toWai UserBindingExists = userBindingExists @@ -367,19 +363,6 @@ bulkGetMemberLimitExceeded = "too-many-uids" ("Can only process " <> cs (show @Int hardTruncationLimit) <> " user ids per request.") -broadcastLimitExceeded :: Error -broadcastLimitExceeded = - mkError - status400 - "too-many-users-to-broadcast" - ("Too many users to fan out the broadcast event to.") - -noAddToManaged :: Error -noAddToManaged = mkError status403 "no-add-to-managed" "Adding users/bots directly to managed conversation is not allowed." - -teamNotFound :: Error -teamNotFound = mkError status404 "no-team" "team not found" - invalidPermissions :: Error invalidPermissions = mkError status403 "invalid-permissions" "The specified permissions are invalid." @@ -395,9 +378,6 @@ tooManyTeamMembersOnTeamWithLegalhold = mkError status403 "too-many-members-for- teamMemberNotFound :: Error teamMemberNotFound = mkError status404 "no-team-member" "team member not found" -noManagedTeamConv :: Error -noManagedTeamConv = mkError status400 "no-managed-team-conv" "Managed team conversations have been deprecated." - guestLinksDisabled :: Error guestLinksDisabled = mkError status409 "guest-links-disabled" "The guest link feature is disabled and all guest links have been revoked." @@ -410,9 +390,6 @@ noAddToBinding = mkError status403 "binding-team" "Cannot add users to binding t deleteQueueFull :: Error deleteQueueFull = mkError status503 "queue-full" "The delete queue is full. No further delete requests can be processed at the moment." -nonBindingTeam :: Error -nonBindingTeam = mkError status404 "non-binding-team" "not member of a binding team" - noBindingTeamMembers :: Error noBindingTeamMembers = mkError status403 "non-binding-team-members" "Both users must be members of the same binding team." diff --git a/services/galley/src/Galley/API/Federation.hs b/services/galley/src/Galley/API/Federation.hs index 496bb26cfe4..2c86e342fe6 100644 --- a/services/galley/src/Galley/API/Federation.hs +++ b/services/galley/src/Galley/API/Federation.hs @@ -301,13 +301,15 @@ onMessageSent domain rmUnqualified = do ByteString ) localMembers <- sequence $ Map.fromSet mkLocalMember (Set.fromList members) + loc <- qualifyLocal () void $ sendLocalMessages + loc (F.rmTime rm) (F.rmSender rm) (F.rmSenderClient rm) Nothing - convId + (Just convId) localMembers msgMetadata msgs diff --git a/services/galley/src/Galley/API/Internal.hs b/services/galley/src/Galley/API/Internal.hs index 59f8d668d79..16909bcc4bf 100644 --- a/services/galley/src/Galley/API/Internal.hs +++ b/services/galley/src/Galley/API/Internal.hs @@ -16,10 +16,9 @@ -- with this program. If not, see . module Galley.API.Internal - ( sitemap, - servantSitemap, - InternalApi, - ServantAPI, + ( internalSitemap, + internalAPI, + InternalAPI, deleteLoop, safeForever, ) @@ -41,18 +40,17 @@ import qualified Galley.API.CustomBackend as CustomBackend import Galley.API.Error import Galley.API.LegalHold (getTeamLegalholdWhitelistedH, setTeamLegalholdWhitelistedH, unsetTeamLegalholdWhitelistedH) import Galley.API.LegalHold.Conflicts -import qualified Galley.API.One2One as One2One +import Galley.API.One2One import qualified Galley.API.Query as Query import Galley.API.Teams (uncheckedDeleteTeamMember) import qualified Galley.API.Teams as Teams -import Galley.API.Teams.Features (DoAuth (..)) -import qualified Galley.API.Teams.Features as Features +import Galley.API.Teams.Features import qualified Galley.API.Update as Update import Galley.API.Util import Galley.App import Galley.Cassandra.Paging import qualified Galley.Data.Conversation as Data -import Galley.Data.TeamFeatures (MaybeHasLockStatusCol) +import Galley.Data.TeamFeatures import Galley.Effects import Galley.Effects.ClientStore import Galley.Effects.ConversationStore @@ -88,9 +86,7 @@ import Polysemy.Input import qualified Polysemy.TinyLog as P import Servant.API hiding (JSON) import qualified Servant.API as Servant -import Servant.API.Generic import Servant.Server -import Servant.Server.Generic (genericServerT) import System.Logger.Class hiding (Path, name) import qualified System.Logger.Class as Log import Wire.API.Conversation (ConvIdsPage, pattern GetPaginatedConversationIds) @@ -101,247 +97,126 @@ import Wire.API.Federation.API.Galley import Wire.API.Federation.Error import Wire.API.Routes.MultiTablePaging (mtpHasMore, mtpPagingState, mtpResults) import Wire.API.Routes.MultiVerb (MultiVerb, RespondEmpty) +import Wire.API.Routes.Named import Wire.API.Routes.Public (ZLocalUser, ZOptConn) -import Wire.API.Routes.Public.Galley (ConversationVerb) -import qualified Wire.API.Team.Feature as Public - -data InternalApi routes = InternalApi - { iStatusGet :: - routes - :- "i" - :> "status" - :> Get '[Servant.JSON] NoContent, - iStatusHead :: - routes - :- "i" - :> "status" - :> Verb 'HEAD 200 '[Servant.JSON] NoContent, - -- Team Feature Flag API (internal) ----------------------------------- - -- - -- Configuring some features should only be possible internally. - -- Viewing the config for features should be allowed for any admin. - iTeamFeatureStatusSSOGet :: - routes - :- IFeatureStatusGet 'Public.WithoutLockStatus 'Public.TeamFeatureSSO, - iTeamFeatureStatusSSOPut :: - routes - :- IFeatureStatusPut 'Public.TeamFeatureSSO, - iTeamFeatureStatusLegalHoldGet :: - routes - :- IFeatureStatusGet 'Public.WithoutLockStatus 'Public.TeamFeatureLegalHold, - iTeamFeatureStatusLegalHoldPut :: - routes - :- IFeatureStatusPut 'Public.TeamFeatureLegalHold, - iTeamFeatureStatusSearchVisibilityGet :: - routes - :- IFeatureStatusGet 'Public.WithoutLockStatus 'Public.TeamFeatureSearchVisibility, - iTeamFeatureStatusSearchVisibilityPut :: - routes - :- IFeatureStatusPut 'Public.TeamFeatureSearchVisibility, - iTeamFeatureStatusSearchVisibilityDeprecatedGet :: - routes - :- IFeatureStatusDeprecatedGet 'Public.WithoutLockStatus 'Public.TeamFeatureSearchVisibility, - iTeamFeatureStatusSearchVisibilityDeprecatedPut :: - routes - :- IFeatureStatusDeprecatedPut 'Public.TeamFeatureSearchVisibility, - iTeamFeatureStatusValidateSAMLEmailsGet :: - routes - :- IFeatureStatusGet 'Public.WithoutLockStatus 'Public.TeamFeatureValidateSAMLEmails, - iTeamFeatureStatusValidateSAMLEmailsPut :: - routes - :- IFeatureStatusPut 'Public.TeamFeatureValidateSAMLEmails, - iTeamFeatureStatusValidateSAMLEmailsDeprecatedGet :: - routes - :- IFeatureStatusDeprecatedGet 'Public.WithoutLockStatus 'Public.TeamFeatureValidateSAMLEmails, - iTeamFeatureStatusValidateSAMLEmailsDeprecatedPut :: - routes - :- IFeatureStatusDeprecatedPut 'Public.TeamFeatureValidateSAMLEmails, - iTeamFeatureStatusDigitalSignaturesGet :: - routes - :- IFeatureStatusGet 'Public.WithoutLockStatus 'Public.TeamFeatureDigitalSignatures, - iTeamFeatureStatusDigitalSignaturesPut :: - routes - :- IFeatureStatusPut 'Public.TeamFeatureDigitalSignatures, - iTeamFeatureStatusDigitalSignaturesDeprecatedGet :: - routes - :- IFeatureStatusDeprecatedGet 'Public.WithoutLockStatus 'Public.TeamFeatureDigitalSignatures, - iTeamFeatureStatusDigitalSignaturesDeprecatedPut :: - routes - :- IFeatureStatusDeprecatedPut 'Public.TeamFeatureDigitalSignatures, - iTeamFeatureStatusAppLockGet :: - routes - :- IFeatureStatusGet 'Public.WithoutLockStatus 'Public.TeamFeatureAppLock, - iTeamFeatureStatusAppLockPut :: - routes - :- IFeatureStatusPut 'Public.TeamFeatureAppLock, - iTeamFeatureStatusFileSharingGet :: - routes - :- IFeatureStatusGet 'Public.WithLockStatus 'Public.TeamFeatureFileSharing, - iTeamFeatureStatusFileSharingPut :: - routes - :- IFeatureStatusPut 'Public.TeamFeatureFileSharing, - iTeamFeatureLockStatusFileSharingPut :: - routes - :- IFeatureStatusLockStatusPut 'Public.TeamFeatureFileSharing, - iTeamFeatureStatusClassifiedDomainsGet :: - routes - :- IFeatureStatusGet 'Public.WithoutLockStatus 'Public.TeamFeatureClassifiedDomains, - iTeamFeatureStatusConferenceCallingPut :: - routes - :- IFeatureStatusPut 'Public.TeamFeatureConferenceCalling, - iTeamFeatureStatusConferenceCallingGet :: - routes - :- IFeatureStatusGet 'Public.WithoutLockStatus 'Public.TeamFeatureConferenceCalling, - iTeamFeatureStatusSelfDeletingMessagesPut :: - routes - :- IFeatureStatusPut 'Public.TeamFeatureSelfDeletingMessages, - iTeamFeatureStatusSelfDeletingMessagesGet :: - routes - :- IFeatureStatusGet 'Public.WithLockStatus 'Public.TeamFeatureSelfDeletingMessages, - iTeamFeatureLockStatusSelfDeletingMessagesPut :: - routes - :- IFeatureStatusLockStatusPut 'Public.TeamFeatureSelfDeletingMessages, - iTeamFeatureStatusGuestLinksGet :: - routes - :- IFeatureStatusGet 'Public.WithLockStatus 'Public.TeamFeatureGuestLinks, - iTeamFeatureStatusGuestLinksPut :: - routes - :- IFeatureStatusPut 'Public.TeamFeatureGuestLinks, - iTeamFeatureLockStatusGuestLinksPut :: - routes - :- IFeatureStatusLockStatusPut 'Public.TeamFeatureGuestLinks, - -- This endpoint can lead to the following events being sent: - -- - MemberLeave event to members for all conversations the user was in - iDeleteUser :: - routes - :- Summary - "Remove a user from their teams and conversations and erase their clients" - :> ZLocalUser - :> ZOptConn - :> "i" - :> "user" - :> MultiVerb 'DELETE '[Servant.JSON] '[RespondEmpty 200 "Remove a user from Galley"] (), - -- This endpoint can lead to the following events being sent: - -- - ConvCreate event to self, if conversation did not exist before - -- - ConvConnect event to self, if other didn't join the connect conversation before - iConnect :: - routes - :- Summary "Create a connect conversation (deprecated)" - :> ZLocalUser - :> ZOptConn - :> "i" - :> "conversations" - :> "connect" - :> ReqBody '[Servant.JSON] Connect - :> ConversationVerb, - iUpsertOne2OneConversation :: - routes - :- Summary "Create or Update a connect or one2one conversation." - :> "i" - :> "conversations" - :> "one2one" - :> "upsert" - :> ReqBody '[Servant.JSON] UpsertOne2OneConversationRequest - :> Post '[Servant.JSON] UpsertOne2OneConversationResponse - } - deriving (Generic) - -type ServantAPI = ToServantApi InternalApi - -type IFeatureStatusGet lockStatus featureName = - Summary (AppendSymbol "Get config for " (Public.KnownTeamFeatureNameSymbol featureName)) - :> "i" - :> "teams" - :> Capture "tid" TeamId - :> "features" - :> Public.KnownTeamFeatureNameSymbol featureName - :> Get '[Servant.JSON] (Public.TeamFeatureStatus lockStatus featureName) - -type IFeatureStatusPut featureName = - Summary (AppendSymbol "Put config for " (Public.KnownTeamFeatureNameSymbol featureName)) - :> "i" - :> "teams" - :> Capture "tid" TeamId - :> "features" - :> Public.KnownTeamFeatureNameSymbol featureName - :> ReqBody '[Servant.JSON] (Public.TeamFeatureStatus 'Public.WithoutLockStatus featureName) - :> Put '[Servant.JSON] (Public.TeamFeatureStatus 'Public.WithoutLockStatus featureName) +import Wire.API.Routes.Public.Galley +import Wire.API.Team.Feature + +type IFeatureAPI = + IFeatureStatus 'TeamFeatureSSO + :<|> IFeatureStatus 'TeamFeatureLegalHold + :<|> IFeatureStatus 'TeamFeatureSearchVisibility + :<|> IFeatureStatusDeprecated 'TeamFeatureSearchVisibility + :<|> IFeatureStatus 'TeamFeatureValidateSAMLEmails + :<|> IFeatureStatusDeprecated 'TeamFeatureValidateSAMLEmails + :<|> IFeatureStatus 'TeamFeatureDigitalSignatures + :<|> IFeatureStatusDeprecated 'TeamFeatureDigitalSignatures + :<|> IFeatureStatus 'TeamFeatureAppLock + :<|> IFeatureStatusWithLock 'TeamFeatureFileSharing + :<|> IFeatureStatusGet 'WithLockStatus 'TeamFeatureClassifiedDomains + :<|> IFeatureStatus 'TeamFeatureConferenceCalling + :<|> IFeatureStatusWithLock 'TeamFeatureSelfDeletingMessages + :<|> IFeatureStatusWithLock 'TeamFeatureGuestLinks + :<|> IFeatureStatusWithLock 'TeamFeatureSndFactorPasswordChallenge + +type InternalAPI = + "i" + :> ( Named + "status" + ( "status" :> MultiVerb 'GET '[Servant.JSON] '[RespondEmpty 200 "OK"] () + ) + -- This endpoint can lead to the following events being sent: + -- - MemberLeave event to members for all conversations the user was in + :<|> Named + "delete-user" + ( Summary + "Remove a user from their teams and conversations and erase their clients" + :> ZLocalUser + :> ZOptConn + :> "user" + :> MultiVerb 'DELETE '[Servant.JSON] '[RespondEmpty 200 "Remove a user from Galley"] () + ) + -- This endpoint can lead to the following events being sent: + -- - ConvCreate event to self, if conversation did not exist before + -- - ConvConnect event to self, if other didn't join the connect conversation before + :<|> Named + "connect" + ( Summary "Create a connect conversation (deprecated)" + :> ZLocalUser + :> ZOptConn + :> "conversations" + :> "connect" + :> ReqBody '[Servant.JSON] Connect + :> ConversationVerb + ) + :<|> Named + "upsert-one2one" + ( Summary "Create or Update a connect or one2one conversation." + :> "conversations" + :> "one2one" + :> "upsert" + :> ReqBody '[Servant.JSON] UpsertOne2OneConversationRequest + :> Post '[Servant.JSON] UpsertOne2OneConversationResponse + ) + :<|> IFeatureAPI + ) + +type IFeatureStatusGet l f = Named '("iget", f) (FeatureStatusBaseGet l f) + +type IFeatureStatusPut f = Named '("iput", f) (FeatureStatusBasePut f) + +type IFeatureStatus f = IFeatureStatusGet 'WithoutLockStatus f :<|> IFeatureStatusPut f + +type IFeatureStatusDeprecated f = + Named '("iget-deprecated", f) (FeatureStatusBaseDeprecatedGet 'WithoutLockStatus f) + :<|> Named '("iput-deprecated", f) (FeatureStatusBaseDeprecatedPut f) type IFeatureStatusLockStatusPut featureName = - Summary (AppendSymbol "(Un-)lock " (Public.KnownTeamFeatureNameSymbol featureName)) - :> "i" - :> "teams" - :> Capture "tid" TeamId - :> "features" - :> Public.KnownTeamFeatureNameSymbol featureName - :> Capture "lockStatus" Public.LockStatusValue - :> Put '[Servant.JSON] Public.LockStatus - --- | A type for a GET endpoint for a feature with a deprecated path -type IFeatureStatusDeprecatedGet lockStatus featureName = - Summary (AppendSymbol "[deprecated] Get config for " (Public.KnownTeamFeatureNameSymbol featureName)) - :> "i" - :> "teams" - :> Capture "tid" TeamId - :> "features" - :> Public.DeprecatedFeatureName featureName - :> Get '[Servant.JSON] (Public.TeamFeatureStatus lockStatus featureName) - --- | A type for a PUT endpoint for a feature with a deprecated path -type IFeatureStatusDeprecatedPut featureName = - Summary (AppendSymbol "[deprecated] Put config for " (Public.KnownTeamFeatureNameSymbol featureName)) - :> "i" - :> "teams" - :> Capture "tid" TeamId - :> "features" - :> Public.DeprecatedFeatureName featureName - :> ReqBody '[Servant.JSON] (Public.TeamFeatureStatus 'Public.WithoutLockStatus featureName) - :> Put '[Servant.JSON] (Public.TeamFeatureStatus 'Public.WithoutLockStatus featureName) - -servantSitemap :: ServerT ServantAPI (Sem GalleyEffects) -servantSitemap = - genericServerT $ - InternalApi - { iStatusGet = pure NoContent, - iStatusHead = pure NoContent, - iTeamFeatureStatusSSOGet = iGetTeamFeature @'Public.WithoutLockStatus @'Public.TeamFeatureSSO Features.getSSOStatusInternal, - iTeamFeatureStatusSSOPut = iPutTeamFeature @'Public.TeamFeatureSSO Features.setSSOStatusInternal, - iTeamFeatureStatusLegalHoldGet = iGetTeamFeature @'Public.WithoutLockStatus @'Public.TeamFeatureLegalHold Features.getLegalholdStatusInternal, - iTeamFeatureStatusLegalHoldPut = iPutTeamFeature @'Public.TeamFeatureLegalHold (Features.setLegalholdStatusInternal @InternalPaging), - iTeamFeatureStatusSearchVisibilityGet = iGetTeamFeature @'Public.WithoutLockStatus @'Public.TeamFeatureSearchVisibility Features.getTeamSearchVisibilityAvailableInternal, - iTeamFeatureStatusSearchVisibilityPut = iPutTeamFeature @'Public.TeamFeatureLegalHold Features.setTeamSearchVisibilityAvailableInternal, - iTeamFeatureStatusSearchVisibilityDeprecatedGet = iGetTeamFeature @'Public.WithoutLockStatus @'Public.TeamFeatureSearchVisibility Features.getTeamSearchVisibilityAvailableInternal, - iTeamFeatureStatusSearchVisibilityDeprecatedPut = iPutTeamFeature @'Public.TeamFeatureLegalHold Features.setTeamSearchVisibilityAvailableInternal, - iTeamFeatureStatusValidateSAMLEmailsGet = iGetTeamFeature @'Public.WithoutLockStatus @'Public.TeamFeatureValidateSAMLEmails Features.getValidateSAMLEmailsInternal, - iTeamFeatureStatusValidateSAMLEmailsPut = iPutTeamFeature @'Public.TeamFeatureValidateSAMLEmails Features.setValidateSAMLEmailsInternal, - iTeamFeatureStatusValidateSAMLEmailsDeprecatedGet = iGetTeamFeature @'Public.WithoutLockStatus @'Public.TeamFeatureValidateSAMLEmails Features.getValidateSAMLEmailsInternal, - iTeamFeatureStatusValidateSAMLEmailsDeprecatedPut = iPutTeamFeature @'Public.TeamFeatureValidateSAMLEmails Features.setValidateSAMLEmailsInternal, - iTeamFeatureStatusDigitalSignaturesGet = iGetTeamFeature @'Public.WithoutLockStatus @'Public.TeamFeatureDigitalSignatures Features.getDigitalSignaturesInternal, - iTeamFeatureStatusDigitalSignaturesPut = iPutTeamFeature @'Public.TeamFeatureDigitalSignatures Features.setDigitalSignaturesInternal, - iTeamFeatureStatusDigitalSignaturesDeprecatedGet = iGetTeamFeature @'Public.WithoutLockStatus @'Public.TeamFeatureDigitalSignatures Features.getDigitalSignaturesInternal, - iTeamFeatureStatusDigitalSignaturesDeprecatedPut = iPutTeamFeature @'Public.TeamFeatureDigitalSignatures Features.setDigitalSignaturesInternal, - iTeamFeatureStatusAppLockGet = iGetTeamFeature @'Public.WithoutLockStatus @'Public.TeamFeatureAppLock Features.getAppLockInternal, - iTeamFeatureStatusAppLockPut = iPutTeamFeature @'Public.TeamFeatureAppLock Features.setAppLockInternal, - iTeamFeatureStatusFileSharingGet = iGetTeamFeature @'Public.WithLockStatus @'Public.TeamFeatureFileSharing Features.getFileSharingInternal, - iTeamFeatureStatusFileSharingPut = iPutTeamFeature @'Public.TeamFeatureFileSharing Features.setFileSharingInternal, - iTeamFeatureLockStatusFileSharingPut = Features.setLockStatus @'Public.TeamFeatureFileSharing, - iTeamFeatureStatusClassifiedDomainsGet = iGetTeamFeature @'Public.WithoutLockStatus @'Public.TeamFeatureClassifiedDomains Features.getClassifiedDomainsInternal, - iTeamFeatureStatusConferenceCallingPut = iPutTeamFeature @'Public.TeamFeatureConferenceCalling Features.setConferenceCallingInternal, - iTeamFeatureStatusConferenceCallingGet = iGetTeamFeature @'Public.WithoutLockStatus @'Public.TeamFeatureConferenceCalling Features.getConferenceCallingInternal, - iTeamFeatureStatusSelfDeletingMessagesPut = iPutTeamFeature @'Public.TeamFeatureSelfDeletingMessages Features.setSelfDeletingMessagesInternal, - iTeamFeatureStatusSelfDeletingMessagesGet = iGetTeamFeature @'Public.WithLockStatus @'Public.TeamFeatureSelfDeletingMessages Features.getSelfDeletingMessagesInternal, - iTeamFeatureLockStatusSelfDeletingMessagesPut = Features.setLockStatus @'Public.TeamFeatureSelfDeletingMessages, - iTeamFeatureStatusGuestLinksGet = iGetTeamFeature @'Public.WithLockStatus @'Public.TeamFeatureGuestLinks Features.getGuestLinkInternal, - iTeamFeatureStatusGuestLinksPut = iPutTeamFeature @'Public.TeamFeatureGuestLinks Features.setGuestLinkInternal, - iTeamFeatureLockStatusGuestLinksPut = Features.setLockStatus @'Public.TeamFeatureGuestLinks, - iDeleteUser = rmUser, - iConnect = Create.createConnectConversation, - iUpsertOne2OneConversation = One2One.iUpsertOne2OneConversation - } - -iGetTeamFeature :: - forall ps a r. - ( Public.KnownTeamFeatureName a, + Named + '("lock", featureName) + ( Summary (AppendSymbol "(Un-)lock " (KnownTeamFeatureNameSymbol featureName)) + :> "teams" + :> Capture "tid" TeamId + :> "features" + :> KnownTeamFeatureNameSymbol featureName + :> Capture "lockStatus" LockStatusValue + :> Put '[Servant.JSON] LockStatus + ) + +type IFeatureStatusWithLock f = + IFeatureStatusGet 'WithLockStatus f + :<|> IFeatureStatusPut f + :<|> IFeatureStatusLockStatusPut f + +internalAPI :: ServerT InternalAPI (Sem GalleyEffects) +internalAPI = + Named @"status" (pure ()) + :<|> Named @"delete-user" rmUser + :<|> Named @"connect" Create.createConnectConversation + :<|> Named @"upsert-one2one" iUpsertOne2OneConversation + :<|> featureAPI + +featureAPI :: ServerT IFeatureAPI (Sem GalleyEffects) +featureAPI = + featureStatus getSSOStatusInternal setSSOStatusInternal + :<|> featureStatus getLegalholdStatusInternal (setLegalholdStatusInternal @InternalPaging) + :<|> featureStatus getTeamSearchVisibilityAvailableInternal setTeamSearchVisibilityAvailableInternal + :<|> featureStatusDeprecated getTeamSearchVisibilityAvailableInternal setTeamSearchVisibilityAvailableInternal + :<|> featureStatus getValidateSAMLEmailsInternal setValidateSAMLEmailsInternal + :<|> featureStatusDeprecated getValidateSAMLEmailsInternal setValidateSAMLEmailsInternal + :<|> featureStatus getDigitalSignaturesInternal setDigitalSignaturesInternal + :<|> featureStatusDeprecated getDigitalSignaturesInternal setDigitalSignaturesInternal + :<|> featureStatus getAppLockInternal setAppLockInternal + :<|> featureStatusWithLock getFileSharingInternal setFileSharingInternal + :<|> featureStatusGet @'WithLockStatus getClassifiedDomainsInternal + :<|> featureStatus @'TeamFeatureConferenceCalling getConferenceCallingInternal setConferenceCallingInternal + :<|> featureStatusWithLock getSelfDeletingMessagesInternal setSelfDeletingMessagesInternal + :<|> featureStatusWithLock getGuestLinkInternal setGuestLinkInternal + :<|> featureStatusWithLock getSndFactorPasswordChallengeInternal setSndFactorPasswordChallengeInternal + +featureStatusGet :: + forall (l :: IncludeLockStatus) f r. + ( KnownTeamFeatureName f, Members '[ Error ActionError, Error NotATeamMember, @@ -350,33 +225,92 @@ iGetTeamFeature :: ] r ) => - (Features.GetFeatureInternalParam -> Sem r (Public.TeamFeatureStatus ps a)) -> - TeamId -> - Sem r (Public.TeamFeatureStatus ps a) -iGetTeamFeature getter = Features.getFeatureStatus @ps @a getter DontDoAuth - -iPutTeamFeature :: - forall a r. - ( Public.KnownTeamFeatureName a, - MaybeHasLockStatusCol a, + (GetFeatureInternalParam -> Sem r (TeamFeatureStatus l f)) -> + ServerT (IFeatureStatusGet l f) (Sem r) +featureStatusGet getter = + Named @'("iget", f) (getFeatureStatus @l @f getter DontDoAuth) + +featureStatusPut :: + forall f r. + ( KnownTeamFeatureName f, + MaybeHasLockStatusCol f, Members '[ Error ActionError, Error NotATeamMember, Error TeamError, - Error TeamFeatureError, - TeamStore, - TeamFeatureStore + TeamFeatureStore, + TeamStore + ] + r + ) => + (TeamId -> TeamFeatureStatus 'WithoutLockStatus f -> Sem r (TeamFeatureStatus 'WithoutLockStatus f)) -> + ServerT (IFeatureStatusPut f) (Sem r) +featureStatusPut setter = + Named @'("iput", f) (setFeatureStatus @f setter DontDoAuth) + +featureStatus :: + forall f r. + ( KnownTeamFeatureName f, + MaybeHasLockStatusCol f, + Members + '[ Error ActionError, + Error NotATeamMember, + Error TeamError, + TeamFeatureStore, + TeamStore + ] + r + ) => + (GetFeatureInternalParam -> Sem r (TeamFeatureStatus 'WithoutLockStatus f)) -> + (TeamId -> TeamFeatureStatus 'WithoutLockStatus f -> Sem r (TeamFeatureStatus 'WithoutLockStatus f)) -> + ServerT (IFeatureStatus f) (Sem r) +featureStatus getter setter = + featureStatusGet @'WithoutLockStatus @f getter :<|> featureStatusPut @f setter + +featureStatusDeprecated :: + forall f r. + ( KnownTeamFeatureName f, + MaybeHasLockStatusCol f, + Members + '[ Error ActionError, + Error NotATeamMember, + Error TeamError, + TeamFeatureStore, + TeamStore ] r ) => - (TeamId -> Public.TeamFeatureStatus 'Public.WithoutLockStatus a -> Sem r (Public.TeamFeatureStatus 'Public.WithoutLockStatus a)) -> - TeamId -> - Public.TeamFeatureStatus 'Public.WithoutLockStatus a -> - Sem r (Public.TeamFeatureStatus 'Public.WithoutLockStatus a) -iPutTeamFeature setter = Features.setFeatureStatus @a setter DontDoAuth - -sitemap :: Routes a (Sem GalleyEffects) () -sitemap = do + (GetFeatureInternalParam -> Sem r (TeamFeatureStatus 'WithoutLockStatus f)) -> + (TeamId -> TeamFeatureStatus 'WithoutLockStatus f -> Sem r (TeamFeatureStatus 'WithoutLockStatus f)) -> + ServerT (IFeatureStatusDeprecated f) (Sem r) +featureStatusDeprecated getter setter = + Named @'("iget-deprecated", f) (getFeatureStatus @'WithoutLockStatus @f getter DontDoAuth) + :<|> Named @'("iput-deprecated", f) (setFeatureStatus @f setter DontDoAuth) + +featureStatusWithLock :: + forall f r. + ( KnownTeamFeatureName f, + HasLockStatusCol f, + MaybeHasLockStatusCol f, + Members + '[ Error ActionError, + Error NotATeamMember, + Error TeamError, + TeamFeatureStore, + TeamStore + ] + r + ) => + (GetFeatureInternalParam -> Sem r (TeamFeatureStatus 'WithLockStatus f)) -> + (TeamId -> TeamFeatureStatus 'WithoutLockStatus f -> Sem r (TeamFeatureStatus 'WithoutLockStatus f)) -> + ServerT (IFeatureStatusWithLock f) (Sem r) +featureStatusWithLock getter setter = + featureStatusGet @'WithLockStatus getter + :<|> featureStatusPut setter + :<|> Named @'("lock", f) (setLockStatus @f) + +internalSitemap :: Routes a (Sem GalleyEffects) () +internalSitemap = do -- Conversation API (internal) ---------------------------------------- put "/i/conversations/:cnv/channel" (continue $ const (return empty)) $ @@ -388,13 +322,6 @@ sitemap = do capture "cnv" .&. capture "usr" - -- This endpoint can lead to the following events being sent: - -- - ConvCreate event to members - post "/i/conversations/managed" (continue Create.internalCreateManagedConversationH) $ - zauthUserId - .&. zauthConnId - .&. jsonRequest @NewConvManaged - -- This endpoint can lead to the following events being sent: -- - MemberJoin event to you, if the conversation existed and had < 2 members before -- - MemberJoin event to other, if the conversation existed and only the other was member diff --git a/services/galley/src/Galley/API/Message.hs b/services/galley/src/Galley/API/Message.hs index 4de70882086..f8fb535398e 100644 --- a/services/galley/src/Galley/API/Message.hs +++ b/services/galley/src/Galley/API/Message.hs @@ -15,31 +15,47 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.Message where +module Galley.API.Message + ( UserType (..), + sendLocalMessages, + postQualifiedOtrMessage, + postBroadcast, + postRemoteOtrMessage, + legacyClientMismatchStrategy, + Unqualify (..), + userToProtectee, + MessageMetadata (..), + + -- * Only exported for tests + checkMessageClients, + QualifiedMismatch (..), + mkQualifiedUserClients, + clientMismatchStrategyApply, + ) +where import Control.Lens import Control.Monad.Except (throwError) import Control.Monad.Extra (eitherM) import Control.Monad.Trans.Except (runExceptT) import Data.Aeson (encode) +import Data.Bifunctor import Data.ByteString.Conversion (toByteString') import Data.Domain (Domain) -import Data.Id (ClientId, ConnId, ConvId, UserId) +import Data.Id import Data.Json.Util - ( Base64ByteString (..), - UTCTimeMillis, - toBase64Text, - toUTCTimeMillis, - ) import Data.List1 (singleton) import qualified Data.Map as Map import Data.Map.Lens (toMapOf) import Data.Qualified +import Data.Range import qualified Data.Set as Set import Data.Set.Lens import Data.Time.Clock (UTCTime) +import Galley.API.Error import Galley.API.LegalHold.Conflicts import Galley.API.Util +import Galley.Data.Conversation import Galley.Data.Services as Data import Galley.Effects import Galley.Effects.BrigAccess @@ -49,6 +65,7 @@ import Galley.Effects.ExternalAccess import Galley.Effects.FederatorAccess import Galley.Effects.GundeckAccess hiding (Push) import Galley.Effects.MemberStore +import Galley.Effects.TeamStore import Galley.Intra.Push import Galley.Options import qualified Galley.Types.Clients as Clients @@ -67,6 +84,7 @@ import Wire.API.Federation.API.Galley import Wire.API.Federation.Error import Wire.API.Message import Wire.API.Team.LegalHold +import Wire.API.Team.Member import Wire.API.User.Client import Wire.API.UserMap (UserMap (..)) @@ -228,6 +246,111 @@ postRemoteOtrMessage sender conv rawMsg = do rpc = fedClient @'Galley @"send-message" msr msResponse <$> runFederated conv rpc +postBroadcast :: + Members + '[ BrigAccess, + ClientStore, + Error ActionError, + Error TeamError, + GundeckAccess, + Input Opts, + Input UTCTime, + TeamStore, + P.TinyLog + ] + r => + Local UserId -> + Maybe ConnId -> + QualifiedNewOtrMessage -> + Sem r (PostOtrResponse MessageSendingStatus) +postBroadcast lusr con msg = runError $ do + let senderClient = qualifiedNewOtrSender msg + senderDomain = tDomain lusr + senderUser = tUnqualified lusr + rcps = + Map.findWithDefault mempty senderDomain + . qualifiedUserClientMap + . qualifiedOtrRecipientsMap + . qualifiedNewOtrRecipients + $ msg + now <- input + + tid <- lookupBindingTeam senderUser + limit <- fromIntegral . fromRange <$> fanoutLimit + -- If we are going to fan this out to more than limit, we want to fail early + unless (Map.size rcps <= limit) $ + throw BroadcastLimitExceeded + -- In large teams, we may still use the broadcast endpoint but only if `report_missing` + -- is used and length `report_missing` < limit since we cannot fetch larger teams than + -- that. + tMembers <- + fmap (view userId) <$> case qualifiedNewOtrClientMismatchStrategy msg of + -- Note: remote ids are not in a local team + MismatchReportOnly qus -> + maybeFetchLimitedTeamMemberList + limit + tid + (fst (partitionQualified lusr qus)) + rcps + _ -> maybeFetchAllMembersInTeam tid + contacts <- getContactList senderUser + let users = toList $ Set.union (Set.fromList tMembers) (Set.fromList contacts) + + isInternal <- useIntraClientListing + localClients <- + if isInternal + then Clients.fromUserClients <$> lookupClients users + else getClients users + let qualifiedLocalClients = + Map.mapKeys (tDomain lusr,) + . makeUserMap (Set.fromList users) + . Clients.toMap + $ localClients + + let (sendMessage, validMessages, mismatch) = + checkMessageClients + (senderDomain, senderUser, senderClient) + qualifiedLocalClients + (flattenMap $ qualifiedNewOtrRecipients msg) + (qualifiedNewOtrClientMismatchStrategy msg) + otrResult = mkMessageSendingStatus (toUTCTimeMillis now) mismatch mempty + unless sendMessage $ do + let lhProtectee = qualifiedUserToProtectee (tDomain lusr) User (qUntagged lusr) + missingClients = qmMissing mismatch + + mapError @LegalholdConflicts @(MessageNotSent MessageSendingStatus) + (const MessageNotSentLegalhold) + $ runLocalInput lusr $ + guardQualifiedLegalholdPolicyConflicts lhProtectee missingClients + throw $ MessageNotSentClientMissing otrResult + + failedToSend <- + sendBroadcastMessages + lusr + now + (qUntagged lusr) + senderClient + con + (qualifiedNewOtrMetadata msg) + validMessages + pure otrResult {mssFailedToSend = failedToSend} + where + maybeFetchLimitedTeamMemberList limit tid localUserIdsInFilter rcps = do + let localUserIdsInRcps = Map.keys rcps + let localUserIdsToLookup = Set.toList $ Set.union (Set.fromList localUserIdsInFilter) (Set.fromList localUserIdsInRcps) + unless (length localUserIdsToLookup <= limit) $ + throw BroadcastLimitExceeded + selectTeamMembers tid localUserIdsToLookup + maybeFetchAllMembersInTeam :: + Members '[Error ActionError, TeamStore] r => + TeamId -> + Sem r [TeamMember] + maybeFetchAllMembersInTeam tid = do + mems <- getTeamMembersForFanout tid + when (mems ^. teamMemberListType == ListTruncated) $ + throw BroadcastLimitExceeded + pure (mems ^. teamMembers) + postQualifiedOtrMessage :: Members '[ BrigAccess, @@ -325,7 +448,7 @@ postQualifiedOtrMessage senderType sender mconn lcnv msg = runExceptT $ do failedToSend <- lift $ - sendMessages + sendMessages @'NormalMessage now sender senderClient @@ -335,21 +458,23 @@ postQualifiedOtrMessage senderType sender mconn lcnv msg = runExceptT $ do (qualifiedNewOtrMetadata msg) validMessages pure otrResult {mssFailedToSend = failedToSend} - where - makeUserMap :: Set UserId -> Map UserId (Set ClientId) -> Map UserId (Set ClientId) - makeUserMap keys = (<> Map.fromSet (const mempty) keys) + +makeUserMap :: Set UserId -> Map UserId (Set ClientId) -> Map UserId (Set ClientId) +makeUserMap keys = (<> Map.fromSet (const mempty) keys) -- | Send both local and remote messages, return the set of clients for which -- sending has failed. sendMessages :: - Members '[GundeckAccess, ExternalAccess, FederatorAccess, Input (Local ()), P.TinyLog] r => - -- FUTUREWORK: remove Input (Local ()) effect + forall t r. + ( t ~ 'NormalMessage, + Members '[GundeckAccess, ExternalAccess, FederatorAccess, P.TinyLog] r + ) => UTCTime -> Qualified UserId -> ClientId -> Maybe ConnId -> Local ConvId -> - Map UserId LocalMember -> + LocalMemberMap t -> MessageMetadata -> Map (Domain, UserId, ClientId) ByteString -> Sem r QualifiedUserClients @@ -358,33 +483,52 @@ sendMessages now sender senderClient mconn lcnv localMemberMap metadata messages let send dom = foldQualified lcnv - (\_ -> sendLocalMessages now sender senderClient mconn (qUntagged lcnv) localMemberMap metadata) + (\l -> sendLocalMessages l now sender senderClient mconn (Just (qUntagged lcnv)) localMemberMap metadata) (\r -> sendRemoteMessages r now sender senderClient lcnv metadata) (Qualified () dom) - mkQualifiedUserClientsByDomain <$> Map.traverseWithKey send messageMap - where - byDomain :: Map (Domain, UserId, ClientId) a -> Map Domain (Map (UserId, ClientId) a) - byDomain = - Map.foldrWithKey - (\(d, u, c) t -> Map.insertWith (<>) d (Map.singleton (u, c) t)) - mempty + +sendBroadcastMessages :: + Members '[GundeckAccess, P.TinyLog] r => + Local x -> + UTCTime -> + Qualified UserId -> + ClientId -> + Maybe ConnId -> + MessageMetadata -> + Map (Domain, UserId, ClientId) ByteString -> + Sem r QualifiedUserClients +sendBroadcastMessages loc now sender senderClient mconn metadata messages = do + let messageMap = byDomain $ fmap toBase64Text messages + localMessages = Map.findWithDefault mempty (tDomain loc) messageMap + failed <- sendLocalMessages loc now sender senderClient mconn Nothing () metadata localMessages + pure . mkQualifiedUserClientsByDomain $ Map.singleton (tDomain loc) failed + +byDomain :: Map (Domain, UserId, ClientId) a -> Map Domain (Map (UserId, ClientId) a) +byDomain = + Map.foldrWithKey + (\(d, u, c) t -> Map.insertWith (<>) d (Map.singleton (u, c) t)) + mempty sendLocalMessages :: - Members '[GundeckAccess, ExternalAccess, Input (Local ()), P.TinyLog] r => + forall t r x. + ( RunMessagePush t, + Members (MessagePushEffects t) r, + Monoid (MessagePush t) + ) => + Local x -> UTCTime -> Qualified UserId -> ClientId -> Maybe ConnId -> - Qualified ConvId -> - Map UserId LocalMember -> + Maybe (Qualified ConvId) -> + LocalMemberMap t -> MessageMetadata -> Map (UserId, ClientId) Text -> Sem r (Set (UserId, ClientId)) -sendLocalMessages now sender senderClient mconn qcnv localMemberMap metadata localMessages = do - loc <- qualifyLocal () +sendLocalMessages loc now sender senderClient mconn qcnv localMemberMap metadata localMessages = do let events = - localMessages & reindexed snd itraversed + localMessages & reindexed (first (qualifyAs loc)) itraversed %@~ newMessageEvent qcnv sender @@ -394,7 +538,7 @@ sendLocalMessages now sender senderClient mconn qcnv localMemberMap metadata loc pushes = events & itraversed %@~ newMessagePush loc localMemberMap mconn metadata - runMessagePush qcnv (pushes ^. traversed) + runMessagePush @t loc qcnv (pushes ^. traversed) pure mempty sendRemoteMessages :: @@ -439,86 +583,134 @@ sendRemoteMessages domain now sender senderClient lcnv metadata messages = (hand Log.~~ Log.msg ("Remote message sending failed" :: Text) pure (Map.keysSet messages) -flatten :: Map Domain (Map UserId (Set ClientId)) -> Set (Domain, UserId, ClientId) -flatten = - setOf $ - (itraversed <.> itraversed <. folded) - . withIndex - . to (\((d, u), c) -> (d, u, c)) - flattenMap :: QualifiedOtrRecipients -> Map (Domain, UserId, ClientId) ByteString flattenMap (QualifiedOtrRecipients (QualifiedUserClientMap m)) = toMapOf (reindexed (\(d, (u, c)) -> (d, u, c)) (itraversed <.> itraversed <.> itraversed)) m -data MessagePush = MessagePush +data MessageType = NormalMessage | Broadcast + +data family MessagePush (t :: MessageType) + +data instance MessagePush 'NormalMessage = NormalMessagePush { userPushes :: [Push], botPushes :: [(BotMember, Event)] } -instance Semigroup MessagePush where - MessagePush us1 bs1 <> MessagePush us2 bs2 = MessagePush (us1 <> us2) (bs1 <> bs2) +data instance MessagePush 'Broadcast = BroadcastPush + {broadcastPushes :: [Push]} -instance Monoid MessagePush where - mempty = MessagePush mempty mempty +instance Semigroup (MessagePush 'NormalMessage) where + NormalMessagePush us1 bs1 <> NormalMessagePush us2 bs2 = + NormalMessagePush (us1 <> us2) (bs1 <> bs2) -newUserPush :: Push -> MessagePush -newUserPush p = MessagePush {userPushes = pure p, botPushes = mempty} +instance Monoid (MessagePush 'NormalMessage) where + mempty = NormalMessagePush mempty mempty -newBotPush :: BotMember -> Event -> MessagePush -newBotPush b e = MessagePush {userPushes = mempty, botPushes = pure (b, e)} +instance Semigroup (MessagePush 'Broadcast) where + BroadcastPush us1 <> BroadcastPush us2 = BroadcastPush (us1 <> us2) -runMessagePush :: - forall r. - Members '[GundeckAccess, ExternalAccess, Input (Local ()), P.TinyLog] r => - Qualified ConvId -> - MessagePush -> - Sem r () -runMessagePush qcnv mp = do - push (userPushes mp) - pushToBots (botPushes mp) - where - pushToBots :: [(BotMember, Event)] -> Sem r () - pushToBots pushes = do - localDomain <- tDomain <$> qualifyLocal () - if localDomain /= qDomain qcnv - then unless (null pushes) $ do - P.warn $ Log.msg ("Ignoring messages for local bots in a remote conversation" :: ByteString) . Log.field "conversation" (show qcnv) - else deliverAndDeleteAsync (qUnqualified qcnv) pushes - -newMessageEvent :: Qualified ConvId -> Qualified UserId -> ClientId -> Maybe Text -> UTCTime -> ClientId -> Text -> Event -newMessageEvent convId sender senderClient dat time receiverClient cipherText = - Event OtrMessageAdd convId sender time . EdOtrMessage $ - OtrMessage - { otrSender = senderClient, - otrRecipient = receiverClient, - otrCiphertext = cipherText, - otrData = dat - } +instance Monoid (MessagePush 'Broadcast) where + mempty = BroadcastPush mempty + +class HasUserPush (t :: MessageType) where + newUserPush :: Push -> MessagePush t + +instance HasUserPush 'NormalMessage where + newUserPush p = NormalMessagePush {userPushes = pure p, botPushes = mempty} + +instance HasUserPush 'Broadcast where + newUserPush p = BroadcastPush (pure p) + +newBotPush :: BotMember -> Event -> MessagePush 'NormalMessage +newBotPush b e = NormalMessagePush {userPushes = mempty, botPushes = pure (b, e)} -newMessagePush :: - Ord k => +type family LocalMemberMap (t :: MessageType) = (m :: *) | m -> t where + LocalMemberMap 'NormalMessage = Map UserId LocalMember + LocalMemberMap 'Broadcast = () + +newMessageEvent :: + Maybe (Qualified ConvId) -> + Qualified UserId -> + ClientId -> + Maybe Text -> + UTCTime -> + (Local UserId, ClientId) -> + Text -> + Event +newMessageEvent mconvId sender senderClient dat time (receiver, receiverClient) cipherText = + let convId = fromMaybe (qUntagged (fmap selfConv receiver)) mconvId + in Event OtrMessageAdd convId sender time . EdOtrMessage $ + OtrMessage + { otrSender = senderClient, + otrRecipient = receiverClient, + otrCiphertext = cipherText, + otrData = dat + } + +class RunMessagePush (t :: MessageType) where + type MessagePushEffects t :: [Effect] + newMessagePush :: + Local x -> + LocalMemberMap t -> + Maybe ConnId -> + MessageMetadata -> + (UserId, ClientId) -> + Event -> + MessagePush t + runMessagePush :: + Members (MessagePushEffects t) r => + Local x -> + Maybe (Qualified ConvId) -> + MessagePush t -> + Sem r () + +instance RunMessagePush 'NormalMessage where + type MessagePushEffects 'NormalMessage = '[ExternalAccess, GundeckAccess, P.TinyLog] + newMessagePush loc members mconn mm (k, client) e = fold $ do + member <- Map.lookup k members + newBotMessagePush member <|> newUserMessagePush loc mconn mm (lmId member) client e + where + newBotMessagePush :: LocalMember -> Maybe (MessagePush 'NormalMessage) + newBotMessagePush member = newBotPush <$> newBotMember member <*> pure e + runMessagePush loc mqcnv mp = do + push (userPushes mp) + pushToBots (botPushes mp) + where + pushToBots :: + Members '[ExternalAccess, P.TinyLog] r => + [(BotMember, Event)] -> + Sem r () + pushToBots pushes = for_ mqcnv $ \qcnv -> + if tDomain loc /= qDomain qcnv + then unless (null pushes) $ do + P.warn $ Log.msg ("Ignoring messages for local bots in a remote conversation" :: ByteString) . Log.field "conversation" (show qcnv) + else deliverAndDeleteAsync (qUnqualified qcnv) pushes + +instance RunMessagePush 'Broadcast where + type MessagePushEffects 'Broadcast = '[GundeckAccess] + newMessagePush loc () mconn mm (user, client) e = + fold $ + newUserMessagePush loc mconn mm user client e + + runMessagePush _ _ mp = push (broadcastPushes mp) + +newUserMessagePush :: + HasUserPush t => Local x -> - Map k LocalMember -> Maybe ConnId -> MessageMetadata -> - (k, ClientId) -> + UserId -> + ClientId -> Event -> - MessagePush -newMessagePush loc members mconn mm (k, client) e = fromMaybe mempty $ do - member <- Map.lookup k members - newBotMessagePush member <|> newUserMessagePush member - where - newBotMessagePush :: LocalMember -> Maybe MessagePush - newBotMessagePush member = newBotPush <$> newBotMember member <*> pure e - newUserMessagePush :: LocalMember -> Maybe MessagePush - newUserMessagePush member = - fmap newUserPush $ - newConversationEventPush e (qualifyAs loc [lmId member]) - <&> set pushConn mconn - . set pushNativePriority (mmNativePriority mm) - . set pushRoute (bool RouteDirect RouteAny (mmNativePush mm)) - . set pushTransient (mmTransient mm) - . set (pushRecipients . traverse . recipientClients) (RecipientClientsSome (singleton client)) + Maybe (MessagePush t) +newUserMessagePush loc mconn mm user cli e = + fmap newUserPush $ + newConversationEventPush e (qualifyAs loc [user]) + <&> set pushConn mconn + . set pushNativePriority (mmNativePriority mm) + . set pushRoute (bool RouteDirect RouteAny (mmNativePush mm)) + . set pushTransient (mmTransient mm) + . set (pushRecipients . traverse . recipientClients) (RecipientClientsSome (singleton cli)) data MessageMetadata = MessageMetadata { mmNativePush :: Bool, diff --git a/services/galley/src/Galley/API/One2One.hs b/services/galley/src/Galley/API/One2One.hs index e0ea6df3721..9983d7ded48 100644 --- a/services/galley/src/Galley/API/One2One.hs +++ b/services/galley/src/Galley/API/One2One.hs @@ -1,19 +1,3 @@ --- This file is part of the Wire Server implementation. --- --- Copyright (C) 2021 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 RecordWildCards #-} -- This file is part of the Wire Server implementation. diff --git a/services/galley/src/Galley/API/Public.hs b/services/galley/src/Galley/API/Public.hs index 3112574d1ec..99dc38c63ca 100644 --- a/services/galley/src/Galley/API/Public.hs +++ b/services/galley/src/Galley/API/Public.hs @@ -37,7 +37,6 @@ import qualified Galley.API.LegalHold as LegalHold import qualified Galley.API.Query as Query import qualified Galley.API.Teams as Teams import qualified Galley.API.Teams.Features as Features -import qualified Galley.API.Update as Update import Galley.App import Imports hiding (head) import Network.HTTP.Types @@ -50,8 +49,6 @@ import Network.Wai.Utilities import Network.Wai.Utilities.Swagger import Network.Wai.Utilities.ZAuth hiding (ZAuthUser) import Polysemy -import qualified Wire.API.Conversation.Code as Public -import qualified Wire.API.Conversation.Typing as Public import qualified Wire.API.CustomBackend as Public import qualified Wire.API.ErrorDescription as Error import qualified Wire.API.Event.Team as Public () @@ -175,7 +172,7 @@ sitemap = do description "Maximum number of events to return (1..10000; default: 1000)" returns (ref Public.modelNotificationList) response 200 "List of team notifications" end - errorResponse Error.teamNotFound + errorResponse (Error.errorDescriptionTypeToWai @Error.TeamNotFound) errorResponse Error.invalidTeamNotificationId post "/teams/:tid/members" (continue Teams.addTeamMemberH) $ @@ -358,190 +355,6 @@ sitemap = do .&. zauthConvId .&. accept "application" "json" - -- This endpoint can lead to the following events being sent: - -- - OtrMessageAdd event to recipients - post "/bot/messages" (continue Update.postBotMessageH) $ - zauth ZAuthBot - .&> zauthBotId - .&. zauthConvId - .&. def Public.OtrReportAllMissing filterMissing - .&. jsonRequest @Public.NewOtrMessage - .&. accept "application" "json" - - -- Conversation API --------------------------------------------------- - - -- This endpoint can lead to the following events being sent: - -- - MemberJoin event to members - post "/conversations/:cnv/join" (continue Update.joinConversationByIdH) $ - zauthUserId - .&. zauthConnId - .&. capture "cnv" - .&. accept "application" "json" - document "POST" "joinConversationById" $ do - summary "Join a conversation by its ID (if link access enabled)" - parameter Path "cnv" bytes' $ - description "Conversation ID" - returns (ref Public.modelEvent) - response 200 "Conversation joined." end - errorResponse (Error.errorDescriptionTypeToWai @Error.ConvNotFound) - - post "/conversations/code-check" (continue Update.checkReusableCodeH) $ - jsonRequest @Public.ConversationCode - document "POST" "checkConversationCode" $ do - summary "Check validity of a conversation code" - response 200 "Valid" end - body (ref Public.modelConversationCode) $ - description "JSON body" - errorResponse (Error.errorDescriptionTypeToWai @Error.CodeNotFound) - - -- This endpoint can lead to the following events being sent: - -- - MemberJoin event to members - post "/conversations/join" (continue Update.joinConversationByReusableCodeH) $ - zauthUserId - .&. zauthConnId - .&. jsonRequest @Public.ConversationCode - document "POST" "joinConversationByCode" $ do - summary "Join a conversation using a reusable code" - returns (ref Public.modelEvent) - response 200 "Conversation joined." end - body (ref Public.modelConversationCode) $ - description "JSON body" - errorResponse (Error.errorDescriptionTypeToWai @Error.CodeNotFound) - errorResponse (Error.errorDescriptionTypeToWai @Error.ConvNotFound) - errorResponse Error.tooManyMembers - - -- This endpoint can lead to the following events being sent: - -- - ConvCodeUpdate event to members, if code didn't exist before - post "/conversations/:cnv/code" (continue Update.addCodeH) $ - zauthUserId - .&. zauthConnId - .&. capture "cnv" - document "POST" "createConversationCode" $ do - summary "Create or recreate a conversation code" - parameter Path "cnv" bytes' $ - description "Conversation ID" - returns (ref Public.modelEvent) - returns (ref Public.modelConversationCode) - response 201 "Conversation code created." (model Public.modelEvent) - response 200 "Conversation code already exists." (model Public.modelConversationCode) - errorResponse (Error.errorDescriptionTypeToWai @Error.ConvNotFound) - errorResponse Error.invalidAccessOp - - -- This endpoint can lead to the following events being sent: - -- - ConvCodeDelete event to members - delete "/conversations/:cnv/code" (continue Update.rmCodeH) $ - zauthUserId - .&. zauthConnId - .&. capture "cnv" - document "DELETE" "deleteConversationCode" $ do - summary "Delete conversation code" - parameter Path "cnv" bytes' $ - description "Conversation ID" - returns (ref Public.modelEvent) - response 200 "Conversation code deleted." end - errorResponse (Error.errorDescriptionTypeToWai @Error.ConvNotFound) - errorResponse Error.invalidAccessOp - - get "/conversations/:cnv/code" (continue Update.getCodeH) $ - zauthUserId - .&. capture "cnv" - document "GET" "getConversationCode" $ do - summary "Get existing conversation code" - parameter Path "cnv" bytes' $ - description "Conversation ID" - returns (ref Public.modelConversationCode) - response 200 "Conversation Code" end - errorResponse (Error.errorDescriptionTypeToWai @Error.ConvNotFound) - errorResponse Error.invalidAccessOp - - -- This endpoint can lead to the following events being sent: - -- - Typing event to members - post "/conversations/:cnv/typing" (continue Update.isTypingH) $ - zauthUserId - .&. zauthConnId - .&. capture "cnv" - .&. jsonRequest @Public.TypingData - document "POST" "isTyping" $ do - summary "Sending typing notifications" - parameter Path "cnv" bytes' $ - description "Conversation ID" - body (ref Public.modelTyping) $ - description "JSON body" - errorResponse (Error.errorDescriptionTypeToWai @Error.ConvNotFound) - - -- This endpoint can lead to the following events being sent: - -- - OtrMessageAdd event to recipients - post "/broadcast/otr/messages" (continue Update.postOtrBroadcastH) $ - zauthUserId - .&. zauthConnId - .&. def Public.OtrReportAllMissing filterMissing - .&. jsonRequest @Public.NewOtrMessage - document "POST" "postOtrBroadcast" $ do - summary "Broadcast an encrypted message to all team members and all contacts (accepts JSON)" - parameter Query "ignore_missing" bool' $ do - description - "Force message delivery even when clients are missing. \ - \NOTE: can also be a comma-separated list of user IDs, \ - \in which case it specifies who exactly is allowed to \ - \have missing clients." - optional - parameter Query "report_missing" bool' $ do - description - "Don't allow message delivery when clients are missing \ - \('ignore_missing' takes precedence when present). \ - \NOTE: can also be a comma-separated list of user IDs, \ - \in which case it specifies who exactly is forbidden from \ - \having missing clients. \ - \To support large lists of user IDs exceeding the allowed \ - \URL length, you can also put this list in the body, in \ - \the optional field 'report_missing'. That body field takes \ - \precedence over both query params." - optional - body (ref Public.modelNewOtrMessage) $ - description "JSON body" - returns (ref Public.modelClientMismatch) - response 201 "Message posted" end - response 412 "Missing clients" end - errorResponse Error.teamNotFound - errorResponse Error.nonBindingTeam - errorResponse (Error.errorDescriptionTypeToWai @Error.UnknownClient) - errorResponse Error.broadcastLimitExceeded - - -- This endpoint can lead to the following events being sent: - -- - OtrMessageAdd event to recipients - post "/broadcast/otr/messages" (continue Update.postProtoOtrBroadcastH) $ - zauthUserId - .&. zauthConnId - .&. def Public.OtrReportAllMissing filterMissing - .&. request - .&. contentType "application" "x-protobuf" - document "POST" "postOtrBroadcast" $ do - summary "Broadcast an encrypted message to all team members and all contacts (accepts Protobuf)" - parameter Query "ignore_missing" bool' $ do - description - "Force message delivery even when clients are missing. \ - \NOTE: can also be a comma-separated list of user IDs, \ - \in which case it specifies who exactly is allowed to \ - \have missing clients." - optional - parameter Query "report_missing" bool' $ do - description - "Don't allow message delivery when clients are missing \ - \('ignore_missing' takes precedence when present). \ - \NOTE: can also be a comma-separated list of user IDs, \ - \in which case it specifies who exactly is forbidden from \ - \having missing clients." - optional - body (ref Public.modelNewOtrMessage) $ - description "Protobuf body" - returns (ref Public.modelClientMismatch) - response 201 "Message posted" end - response 412 "Missing clients" end - errorResponse Error.teamNotFound - errorResponse Error.nonBindingTeam - errorResponse (Error.errorDescriptionTypeToWai @Error.UnknownClient) - errorResponse Error.broadcastLimitExceeded - apiDocs :: Routes ApiBuilder (Sem r) () apiDocs = get "/conversations/api-docs" (continue docs) $ diff --git a/services/galley/src/Galley/API/Public/Servant.hs b/services/galley/src/Galley/API/Public/Servant.hs index 8f55c10c9c6..78374814986 100644 --- a/services/galley/src/Galley/API/Public/Servant.hs +++ b/services/galley/src/Galley/API/Public/Servant.hs @@ -33,7 +33,7 @@ import Wire.API.Routes.Public.Galley import Wire.API.Team.Feature servantSitemap :: ServerT ServantAPI (Sem GalleyEffects) -servantSitemap = conversations :<|> teamConversations :<|> messaging :<|> team :<|> features +servantSitemap = conversations :<|> teamConversations :<|> messaging :<|> bot :<|> team :<|> features where conversations = Named @"get-unqualified-conversation" getUnqualifiedConversation @@ -49,6 +49,13 @@ servantSitemap = conversations :<|> teamConversations :<|> messaging :<|> team : :<|> Named @"create-one-to-one-conversation" createOne2OneConversation :<|> Named @"add-members-to-conversation-unqualified" addMembersUnqualified :<|> Named @"add-members-to-conversation" addMembers + :<|> Named @"join-conversation-by-id-unqualified" joinConversationById + :<|> Named @"join-conversation-by-code-unqualified" joinConversationByReusableCode + :<|> Named @"code-check" checkReusableCode + :<|> Named @"create-conversation-code-unqualified" addCodeUnqualified + :<|> Named @"remove-code-unqualified" rmCodeUnqualified + :<|> Named @"get-code" getCode + :<|> Named @"member-typing-unqualified" isTypingUnqualified :<|> Named @"remove-member-unqualified" removeMemberUnqualified :<|> Named @"remove-member" removeMemberQualified :<|> Named @"update-other-member-unqualified" updateOtherMemberUnqualified @@ -74,8 +81,12 @@ servantSitemap = conversations :<|> teamConversations :<|> messaging :<|> team : messaging = Named @"post-otr-message-unqualified" postOtrMessageUnqualified + :<|> Named @"post-otr-broadcast-unqualified" postOtrBroadcastUnqualified :<|> Named @"post-proteus-message" postProteusMessage + bot = + Named @"post-bot-message-unqualified" postBotMessageUnqualified + team = Named @"create-non-binding-team" createNonBindingTeamH :<|> Named @"update-team" updateTeamH @@ -189,6 +200,16 @@ servantSitemap = conversations :<|> teamConversations :<|> messaging :<|> team : setGuestLinkInternal . DoAuth ) + :<|> Named @'("get", 'TeamFeatureSndFactorPasswordChallenge) + ( getFeatureStatus @'WithLockStatus @'TeamFeatureSndFactorPasswordChallenge + getSndFactorPasswordChallengeInternal + . DoAuth + ) + :<|> Named @'("put", 'TeamFeatureSndFactorPasswordChallenge) + ( setFeatureStatus @'TeamFeatureSndFactorPasswordChallenge + setSndFactorPasswordChallengeInternal + . DoAuth + ) :<|> Named @"get-all-feature-configs" getAllFeatureConfigs :<|> Named @'("get-config", 'TeamFeatureLegalHold) ( getFeatureConfig @'WithoutLockStatus @'TeamFeatureLegalHold @@ -234,3 +255,7 @@ servantSitemap = conversations :<|> teamConversations :<|> messaging :<|> team : ( getFeatureConfig @'WithLockStatus @'TeamFeatureGuestLinks getGuestLinkInternal ) + :<|> Named @'("get-config", 'TeamFeatureSndFactorPasswordChallenge) + ( getFeatureConfig @'WithLockStatus @'TeamFeatureSndFactorPasswordChallenge + getSndFactorPasswordChallengeInternal + ) diff --git a/services/galley/src/Galley/API/Query.hs b/services/galley/src/Galley/API/Query.hs index d93eb382c45..bc2c34e127b 100644 --- a/services/galley/src/Galley/API/Query.hs +++ b/services/galley/src/Galley/API/Query.hs @@ -48,6 +48,7 @@ module Galley.API.Query getConversationMetaH, getConversationByReusableCode, ensureGuestLinksEnabled, + ensureGuestLinksEnabledWithError, ) where @@ -67,6 +68,7 @@ import Galley.API.Error import qualified Galley.API.Mapping as Mapping import Galley.API.Util import Galley.Cassandra.Paging +import Galley.Data.Types (Code (codeConversation)) import qualified Galley.Data.Types as Data import Galley.Effects import qualified Galley.Effects.ConversationStore as E @@ -529,8 +531,9 @@ getConversationByReusableCode :: Sem r ConversationCoverView getConversationByReusableCode lusr key value = do c <- verifyReusableCode (ConversationCode key value Nothing) - conv <- ensureConversationAccess (tUnqualified lusr) (Data.codeConversation c) CodeAccess - ensureGuestLinksEnabled conv + conv <- E.getConversation (codeConversation c) >>= note ConvNotFound + ensureConversationAccess (tUnqualified lusr) conv CodeAccess + ensureGuestLinksEnabled (Data.convTeam conv) pure $ coverView conv where coverView :: Data.Conversation -> ConversationCoverView @@ -541,22 +544,37 @@ getConversationByReusableCode lusr key value = do } -- FUTUREWORK(leif): refactor and make it consistent for all team features -ensureGuestLinksEnabled :: +ensureGuestLinksEnabledWithError :: forall r. ( Member (Error ConversationError) r, + Member (Error CodeError) r, Member TeamFeatureStore r, Member (Input Opts) r ) => - Data.Conversation -> + Either ConversationError CodeError -> + Maybe TeamId -> Sem r () -ensureGuestLinksEnabled conv = do +ensureGuestLinksEnabledWithError ex mbTid = do defaultStatus <- getDefaultFeatureStatus - maybeFeatureStatus <- join <$> TeamFeatures.getFeatureStatusNoConfig @'TeamFeatureGuestLinks `traverse` Data.convTeam conv + maybeFeatureStatus <- join <$> TeamFeatures.getFeatureStatusNoConfig @'TeamFeatureGuestLinks `traverse` mbTid case maybe defaultStatus tfwoStatus maybeFeatureStatus of TeamFeatureEnabled -> pure () - TeamFeatureDisabled -> throw GuestLinksDisabled + TeamFeatureDisabled -> case ex of + Left e -> throw e + Right e -> throw e where getDefaultFeatureStatus :: Sem r TeamFeatureStatusValue getDefaultFeatureStatus = do status <- input <&> view (optSettings . setFeatureFlags . flagConversationGuestLinks . unDefaults) pure $ tfwoapsStatus status + +ensureGuestLinksEnabled :: + forall r. + ( Member (Error ConversationError) r, + Member (Error CodeError) r, + Member TeamFeatureStore r, + Member (Input Opts) r + ) => + Maybe TeamId -> + Sem r () +ensureGuestLinksEnabled = ensureGuestLinksEnabledWithError (Left GuestLinksDisabled) diff --git a/services/galley/src/Galley/API/Teams.hs b/services/galley/src/Galley/API/Teams.hs index ae1ffb0e88b..bac8563893c 100644 --- a/services/galley/src/Galley/API/Teams.hs +++ b/services/galley/src/Galley/API/Teams.hs @@ -436,8 +436,7 @@ uncheckedDeleteTeam lusr zcon tid = do when (isJust team) $ do Spar.deleteTeam tid now <- input - convs <- - filter (not . view managedConversation) <$> E.getTeamConversations tid + convs <- E.getTeamConversations tid -- Even for LARGE TEAMS, we _DO_ want to fetch all team members here because we -- want to generate conversation deletion events for non-team users. This should -- be fine as it is done once during the life team of a team and we still do not @@ -1093,7 +1092,7 @@ uncheckedDeleteTeamMember lusr zcon tid remove mems = do for_ conv $ \dc -> when (remove `isMember` Data.convLocalMembers dc) $ do E.deleteMembers (c ^. conversationId) (UserList [remove] []) -- If the list was truncated, then the tmids list is incomplete so we simply drop these events - unless (c ^. managedConversation || mems ^. teamMemberListType == ListTruncated) $ + unless (mems ^. teamMemberListType == ListTruncated) $ pushEvent tmids edata now dc pushEvent :: Set UserId -> Conv.EventData -> UTCTime -> Data.Conversation -> Sem r () pushEvent exceptTo edata now dc = do @@ -1339,12 +1338,7 @@ addTeamMemberInternal tid origin originConn (ntmNewTeamMember -> new) memList = . Log.field "action" (Log.val "Teams.addTeamMemberInternal") sizeBeforeAdd <- ensureNotTooLarge tid E.createTeamMember tid new - cc <- filter (view managedConversation) <$> E.getTeamConversations tid now <- input - for_ cc $ \c -> do - lcid <- qualifyLocal (c ^. conversationId) - luid <- qualifyLocal (new ^. userId) - E.createMember lcid luid let e = newEvent MemberJoin tid now & eventData ?~ EdMemberJoin (new ^. userId) E.push1 $ newPushLocal1 (memList ^. teamMemberListType) (new ^. userId) (TeamEvent e) (recipients origin new) & pushConn .~ originConn @@ -1410,24 +1404,8 @@ finishCreateTeam team owner others zcon = do let r = membersToRecipients Nothing others E.push1 $ newPushLocal1 ListComplete zusr (TeamEvent e) (list1 (userRecipient zusr) r) & pushConn .~ zcon --- FUTUREWORK: Get rid of CPS -withBindingTeam :: - Members '[Error TeamError, TeamStore] r => - UserId -> - (TeamId -> Sem r b) -> - Sem r b -withBindingTeam zusr callback = do - tid <- E.getOneUserTeam zusr >>= note TeamNotFound - binding <- E.getTeamBinding tid >>= note TeamNotFound - case binding of - Binding -> callback tid - NonBinding -> throw NotABindingTeamMember - getBindingTeamIdH :: Members '[Error TeamError, TeamStore] r => UserId -> Sem r Response -getBindingTeamIdH = fmap json . getBindingTeamId - -getBindingTeamId :: Members '[Error TeamError, TeamStore] r => UserId -> Sem r TeamId -getBindingTeamId zusr = withBindingTeam zusr pure +getBindingTeamIdH = fmap json . E.lookupBindingTeam getBindingTeamMembersH :: Members '[Error TeamError, TeamStore] r => UserId -> Sem r Response getBindingTeamMembersH = fmap json . getBindingTeamMembers @@ -1440,7 +1418,8 @@ getBindingTeamMembers :: r => UserId -> Sem r TeamMemberList -getBindingTeamMembers zusr = withBindingTeam zusr $ \tid -> +getBindingTeamMembers zusr = do + tid <- E.lookupBindingTeam zusr getTeamMembersForFanout tid canUserJoinTeamH :: diff --git a/services/galley/src/Galley/API/Teams/Features.hs b/services/galley/src/Galley/API/Teams/Features.hs index 20582c84846..6d10186d14e 100644 --- a/services/galley/src/Galley/API/Teams/Features.hs +++ b/services/galley/src/Galley/API/Teams/Features.hs @@ -41,6 +41,8 @@ module Galley.API.Teams.Features setConferenceCallingInternal, getSelfDeletingMessagesInternal, setSelfDeletingMessagesInternal, + getSndFactorPasswordChallengeInternal, + setSndFactorPasswordChallengeInternal, getGuestLinkInternal, setGuestLinkInternal, setLockStatus, @@ -52,6 +54,7 @@ where import Control.Lens import qualified Data.Aeson as Aeson import Data.ByteString.Conversion hiding (fromList) +import Data.Either.Extra (eitherToMaybe) import qualified Data.HashMap.Strict as HashMap import Data.Id import Data.Qualified @@ -235,7 +238,8 @@ getAllFeatureConfigs zusr = do getStatus @'Public.WithoutLockStatus @'Public.TeamFeatureClassifiedDomains getClassifiedDomainsInternal, getStatus @'Public.WithoutLockStatus @'Public.TeamFeatureConferenceCalling getConferenceCallingInternal, getStatus @'Public.WithLockStatus @'Public.TeamFeatureSelfDeletingMessages getSelfDeletingMessagesInternal, - getStatus @'Public.WithLockStatus @'Public.TeamFeatureGuestLinks getGuestLinkInternal + getStatus @'Public.WithLockStatus @'Public.TeamFeatureGuestLinks getGuestLinkInternal, + getStatus @'Public.WithLockStatus @'Public.TeamFeatureSndFactorPasswordChallenge getSndFactorPasswordChallengeInternal ] getAllFeaturesH :: @@ -284,7 +288,8 @@ getAllFeatures uid tid = do getStatus @'Public.WithoutLockStatus @'Public.TeamFeatureClassifiedDomains getClassifiedDomainsInternal, getStatus @'Public.WithoutLockStatus @'Public.TeamFeatureConferenceCalling getConferenceCallingInternal, getStatus @'Public.WithLockStatus @'Public.TeamFeatureSelfDeletingMessages getSelfDeletingMessagesInternal, - getStatus @'Public.WithLockStatus @'Public.TeamFeatureGuestLinks getGuestLinkInternal + getStatus @'Public.WithLockStatus @'Public.TeamFeatureGuestLinks getGuestLinkInternal, + getStatus @'Public.WithLockStatus @'Public.TeamFeatureSndFactorPasswordChallenge getSndFactorPasswordChallengeInternal ] where getStatus :: @@ -382,18 +387,16 @@ setTeamSearchVisibilityAvailableInternal = setFeatureStatusNoConfig @'Public.Tea Public.TeamFeatureEnabled -> const (pure ()) getValidateSAMLEmailsInternal :: - Member TeamFeatureStore r => + forall r. + ( Member TeamFeatureStore r, + Member (Input Opts) r + ) => GetFeatureInternalParam -> Sem r (Public.TeamFeatureStatus 'Public.WithoutLockStatus 'Public.TeamFeatureValidateSAMLEmails) getValidateSAMLEmailsInternal = - either - (const $ Public.TeamFeatureStatusNoConfig <$> getDef) - (getFeatureStatusNoConfig @'Public.TeamFeatureValidateSAMLEmails getDef) - where - -- FUTUREWORK: we may also want to get a default from the server config file here, like for - -- sso, and team search visibility. - -- Use getFeatureStatusWithDefault - getDef = pure Public.TeamFeatureDisabled + getFeatureStatusWithDefaultConfig @'Public.TeamFeatureValidateSAMLEmails + flagsTeamFeatureValidateSAMLEmailsStatus + . eitherToMaybe setValidateSAMLEmailsInternal :: Members '[GundeckAccess, TeamFeatureStore, TeamStore, P.TinyLog] r => @@ -711,6 +714,48 @@ setGuestLinkInternal tid status = do getDftLockStatus :: Sem r Public.LockStatusValue getDftLockStatus = input <&> view (optSettings . setFeatureFlags . flagConversationGuestLinks . unDefaults . to Public.tfwoapsLockStatus) +getSndFactorPasswordChallengeInternal :: + forall r. + (Member (Input Opts) r, Member TeamFeatureStore r) => + GetFeatureInternalParam -> + Sem r (Public.TeamFeatureStatus 'Public.WithLockStatus 'Public.TeamFeatureSndFactorPasswordChallenge) +getSndFactorPasswordChallengeInternal = \case + Left _ -> getCfgDefault + Right tid -> do + cfgDefault <- getCfgDefault + (mbFeatureStatus, fromMaybe (Public.tfwoapsLockStatus cfgDefault) -> lockStatus) <- TeamFeatures.getFeatureStatusNoConfigAndLockStatus @'Public.TeamFeatureSndFactorPasswordChallenge tid + pure $ determineFeatureStatus cfgDefault lockStatus mbFeatureStatus + where + getCfgDefault :: Sem r (Public.TeamFeatureStatus 'Public.WithLockStatus 'Public.TeamFeatureSndFactorPasswordChallenge) + getCfgDefault = input <&> view (optSettings . setFeatureFlags . flagTeamFeatureSndFactorPasswordChallengeStatus . unDefaults) + +setSndFactorPasswordChallengeInternal :: + forall r. + ( Member GundeckAccess r, + Member TeamStore r, + Member TeamFeatureStore r, + Member P.TinyLog r, + Member (Error TeamFeatureError) r, + Member (Input Opts) r + ) => + TeamId -> + Public.TeamFeatureStatus 'Public.WithoutLockStatus 'Public.TeamFeatureSndFactorPasswordChallenge -> + Sem r (Public.TeamFeatureStatus 'Public.WithoutLockStatus 'Public.TeamFeatureSndFactorPasswordChallenge) +setSndFactorPasswordChallengeInternal tid status = do + getDftLockStatus >>= guardLockStatus @'Public.TeamFeatureSndFactorPasswordChallenge tid + let pushEvent = + pushFeatureConfigEvent tid $ + Event.Event + Event.Update + Public.TeamFeatureSndFactorPasswordChallenge + ( EdFeatureWithoutConfigAndLockStatusChanged + (Public.TeamFeatureStatusNoConfigAndLockStatus (Public.tfwoStatus status) Public.Unlocked) + ) + TeamFeatures.setFeatureStatusNoConfig @'Public.TeamFeatureSndFactorPasswordChallenge tid status <* pushEvent + where + getDftLockStatus :: Sem r Public.LockStatusValue + getDftLockStatus = input <&> view (optSettings . setFeatureFlags . flagTeamFeatureSndFactorPasswordChallengeStatus . unDefaults . to Public.tfwoapsLockStatus) + -- TODO(fisx): move this function to a more suitable place / module. guardLockStatus :: forall (a :: Public.TeamFeatureName) r. diff --git a/services/galley/src/Galley/API/Update.hs b/services/galley/src/Galley/API/Update.hs index 4ab5392bbad..54b6f910639 100644 --- a/services/galley/src/Galley/API/Update.hs +++ b/services/galley/src/Galley/API/Update.hs @@ -20,12 +20,12 @@ module Galley.API.Update acceptConvH, blockConvH, unblockConvH, - checkReusableCodeH, - joinConversationByIdH, - joinConversationByReusableCodeH, - addCodeH, - rmCodeH, - getCodeH, + checkReusableCode, + joinConversationByReusableCode, + joinConversationById, + addCodeUnqualified, + rmCodeUnqualified, + getCode, updateUnqualifiedConversationName, updateConversationName, updateConversationReceiptModeUnqualified, @@ -52,16 +52,15 @@ module Galley.API.Update -- * Talking postProteusMessage, postOtrMessageUnqualified, - postOtrBroadcastH, - postProtoOtrBroadcastH, - isTypingH, + postOtrBroadcastUnqualified, + isTypingUnqualified, -- * External Services addServiceH, rmServiceH, Galley.API.Update.addBotH, rmBotH, - postBotMessageH, + postBotMessageUnqualified, ) where @@ -70,16 +69,14 @@ import Control.Lens import Control.Monad.State import Data.Code import Data.Id -import Data.Json.Util (fromBase64TextLenient, toUTCTimeMillis) +import Data.Json.Util import Data.List1 import qualified Data.Map.Strict as Map import Data.Qualified -import Data.Range import qualified Data.Set as Set import Data.Time import Galley.API.Action import Galley.API.Error -import Galley.API.LegalHold.Conflicts import Galley.API.Mapping import Galley.API.Message import qualified Galley.API.Query as Query @@ -88,7 +85,6 @@ import qualified Galley.Data.Conversation as Data import Galley.Data.Services as Data import Galley.Data.Types hiding (Conversation) import Galley.Effects -import qualified Galley.Effects.BrigAccess as E import qualified Galley.Effects.ClientStore as E import qualified Galley.Effects.CodeStore as E import qualified Galley.Effects.ConversationStore as E @@ -97,19 +93,14 @@ 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.ServiceStore as E -import qualified Galley.Effects.TeamStore as E import Galley.Effects.WaiRoutes import Galley.Intra.Push import Galley.Options import Galley.Types import Galley.Types.Bot hiding (addBot) -import Galley.Types.Clients (Clients) -import qualified Galley.Types.Clients as Clients -import Galley.Types.Conversations.Members (newMember) import Galley.Types.Conversations.Roles (Action (..), roleNameWireMember) import Galley.Types.Teams hiding (Event, EventData (..), EventType (..), self) import Galley.Types.UserList -import Gundeck.Types.Push.V2 (RecipientClients (..)) import Imports hiding (forkIO) import Network.HTTP.Types import Network.Wai @@ -119,15 +110,14 @@ import Polysemy import Polysemy.Error import Polysemy.Input import Polysemy.TinyLog -import qualified Wire.API.Conversation as Public -import qualified Wire.API.Conversation.Code as Public +import Wire.API.Conversation hiding (Member) import Wire.API.Conversation.Role (roleNameWireAdmin) import Wire.API.ErrorDescription -import qualified Wire.API.Event.Conversation as Public +import Wire.API.Event.Conversation import Wire.API.Federation.API import Wire.API.Federation.API.Galley import Wire.API.Federation.Error -import qualified Wire.API.Message as Public +import Wire.API.Message import Wire.API.Routes.Public.Util (UpdateResult (..)) import Wire.API.ServantProto (RawProto (..)) import Wire.API.User.Client @@ -277,7 +267,7 @@ updateConversationAccess :: Local UserId -> ConnId -> Qualified ConvId -> - Public.ConversationAccessData -> + ConversationAccessData -> Sem r (UpdateResult Event) updateConversationAccess lusr con qcnv update = do let doUpdate = @@ -308,7 +298,7 @@ updateConversationAccessUnqualified :: Local UserId -> ConnId -> ConvId -> - Public.ConversationAccessData -> + ConversationAccessData -> Sem r (UpdateResult Event) updateConversationAccessUnqualified lusr zcon cnv update = do let lcnv = qualifyAs lusr cnv @@ -335,7 +325,7 @@ updateLocalConversationAccess :: Local ConvId -> Local UserId -> ConnId -> - Public.ConversationAccessData -> + ConversationAccessData -> Sem r (UpdateResult Event) updateLocalConversationAccess lcnv lusr con = getUpdateResult @@ -346,7 +336,7 @@ updateRemoteConversationAccess :: Remote ConvId -> Local UserId -> ConnId -> - Public.ConversationAccessData -> + ConversationAccessData -> Sem r (UpdateResult Event) updateRemoteConversationAccess _ _ _ _ = throw FederationNotImplemented @@ -366,7 +356,7 @@ updateConversationReceiptMode :: Local UserId -> ConnId -> Qualified ConvId -> - Public.ConversationReceiptModeUpdate -> + ConversationReceiptModeUpdate -> Sem r (UpdateResult Event) updateConversationReceiptMode lusr zcon qcnv update = do let doUpdate = @@ -391,7 +381,7 @@ updateConversationReceiptModeUnqualified :: Local UserId -> ConnId -> ConvId -> - Public.ConversationReceiptModeUpdate -> + ConversationReceiptModeUpdate -> Sem r (UpdateResult Event) updateConversationReceiptModeUnqualified lusr zcon cnv update = do let lcnv = qualifyAs lusr cnv @@ -412,7 +402,7 @@ updateLocalConversationReceiptMode :: Local ConvId -> Local UserId -> ConnId -> - Public.ConversationReceiptModeUpdate -> + ConversationReceiptModeUpdate -> Sem r (UpdateResult Event) updateLocalConversationReceiptMode lcnv lusr con update = getUpdateResult $ @@ -423,7 +413,7 @@ updateRemoteConversationReceiptMode :: Remote ConvId -> Local UserId -> ConnId -> - Public.ConversationReceiptModeUpdate -> + ConversationReceiptModeUpdate -> Sem r (UpdateResult Event) updateRemoteConversationReceiptMode _ _ _ _ = throw FederationNotImplemented @@ -442,7 +432,7 @@ updateConversationMessageTimerUnqualified :: Local UserId -> ConnId -> ConvId -> - Public.ConversationMessageTimerUpdate -> + ConversationMessageTimerUpdate -> Sem r (UpdateResult Event) updateConversationMessageTimerUnqualified lusr zcon cnv update = do let lcnv = qualifyAs lusr cnv @@ -464,7 +454,7 @@ updateConversationMessageTimer :: Local UserId -> ConnId -> Qualified ConvId -> - Public.ConversationMessageTimerUpdate -> + ConversationMessageTimerUpdate -> Sem r (UpdateResult Event) updateConversationMessageTimer lusr zcon qcnv update = do foldQualified @@ -489,7 +479,7 @@ updateLocalConversationMessageTimer :: Local UserId -> ConnId -> Local ConvId -> - Public.ConversationMessageTimerUpdate -> + ConversationMessageTimerUpdate -> Sem r (UpdateResult Event) updateLocalConversationMessageTimer lusr con lcnv update = getUpdateResult $ @@ -522,11 +512,12 @@ deleteLocalConversation lusr con lcnv = getUpdateResult :: Sem (Error NoChanges ': r) a -> Sem r (UpdateResult a) getUpdateResult = fmap (either (const Unchanged) Updated) . runError -addCodeH :: +addCodeUnqualified :: forall r. ( Member CodeStore r, Member ConversationStore r, Member (Error ConversationError) r, + Member (Error CodeError) r, Member ExternalAccess r, Member GundeckAccess r, Member (Input (Local ())) r, @@ -534,24 +525,21 @@ addCodeH :: Member (Input Opts) r, Member TeamFeatureStore r ) => - UserId ::: ConnId ::: ConvId -> - Sem r Response -addCodeH (usr ::: zcon ::: cnv) = do + UserId -> + ConnId -> + ConvId -> + Sem r AddCodeResult +addCodeUnqualified usr zcon cnv = do lusr <- qualifyLocal usr lcnv <- qualifyLocal cnv - addCode lusr zcon lcnv <&> \case - CodeAdded event -> json event & setStatus status201 - CodeAlreadyExisted conversationCode -> json conversationCode & setStatus status200 - -data AddCodeResult - = CodeAdded Public.Event - | CodeAlreadyExisted Public.ConversationCode + addCode lusr zcon lcnv addCode :: forall r. ( Member CodeStore r, Member ConversationStore r, Member (Error ConversationError) r, + Member (Error CodeError) r, Member ExternalAccess r, Member GundeckAccess r, Member (Input UTCTime) r, @@ -564,9 +552,10 @@ addCode :: Sem r AddCodeResult addCode lusr zcon lcnv = do conv <- E.getConversation (tUnqualified lcnv) >>= note ConvNotFound - Query.ensureGuestLinksEnabled conv + Query.ensureGuestLinksEnabled (convTeam conv) ensureConvMember (Data.convLocalMembers conv) (tUnqualified lusr) ensureAccess conv CodeAccess + ensureGuestsOrNonTeamMembersAllowed conv let (bots, users) = localBotsAndUsers $ Data.convLocalMembers conv key <- E.makeKey (tUnqualified lcnv) mCode <- E.getCode key ReusableCode @@ -586,8 +575,11 @@ addCode lusr zcon lcnv = do createCode :: Code -> Sem r ConversationCode createCode code = do mkConversationCode (codeKey code) (codeValue code) <$> E.getConversationCodeURI + ensureGuestsOrNonTeamMembersAllowed :: Data.Conversation -> Sem r () + ensureGuestsOrNonTeamMembersAllowed conv = + unless (GuestAccessRole `Set.member` convAccessRoles conv || NonTeamMemberAccessRole `Set.member` convAccessRoles conv) $ throw ConvAccessDenied -rmCodeH :: +rmCodeUnqualified :: Members '[ CodeStore, ConversationStore, @@ -598,12 +590,13 @@ rmCodeH :: Input UTCTime ] r => - UserId ::: ConnId ::: ConvId -> - Sem r Response -rmCodeH (usr ::: zcon ::: cnv) = do - lusr <- qualifyLocal usr + Local UserId -> + ConnId -> + ConvId -> + Sem r Event +rmCodeUnqualified lusr zcon cnv = do lcnv <- qualifyLocal cnv - setStatus status200 . json <$> rmCode lusr zcon lcnv + rmCode lusr zcon lcnv rmCode :: Members @@ -618,7 +611,7 @@ rmCode :: Local UserId -> ConnId -> Local ConvId -> - Sem r Public.Event + Sem r Event rmCode lusr zcon lcnv = do conv <- E.getConversation (tUnqualified lcnv) >>= note ConvNotFound @@ -632,20 +625,6 @@ rmCode lusr zcon lcnv = do pushConversationEvent (Just zcon) event (qualifyAs lusr (map lmId users)) bots pure event -getCodeH :: - forall r. - ( Member CodeStore r, - Member ConversationStore r, - Member (Error CodeError) r, - Member (Error ConversationError) r, - Member (Input Opts) r, - Member TeamFeatureStore r - ) => - UserId ::: ConvId -> - Sem r Response -getCodeH (usr ::: cnv) = - setStatus status200 . json <$> getCode usr cnv - getCode :: forall r. ( Member CodeStore r, @@ -655,65 +634,39 @@ getCode :: Member (Input Opts) r, Member TeamFeatureStore r ) => - UserId -> + Local UserId -> ConvId -> - Sem r Public.ConversationCode -getCode usr cnv = do + Sem r ConversationCode +getCode lusr cnv = do conv <- E.getConversation cnv >>= note ConvNotFound - Query.ensureGuestLinksEnabled conv + Query.ensureGuestLinksEnabled (convTeam conv) ensureAccess conv CodeAccess - ensureConvMember (Data.convLocalMembers conv) usr + ensureConvMember (Data.convLocalMembers conv) (tUnqualified lusr) key <- E.makeKey cnv c <- E.getCode key ReusableCode >>= note CodeNotFound returnCode c -returnCode :: Member CodeStore r => Code -> Sem r Public.ConversationCode +returnCode :: Member CodeStore r => Code -> Sem r ConversationCode returnCode c = do - Public.mkConversationCode (codeKey c) (codeValue c) <$> E.getConversationCodeURI - -checkReusableCodeH :: - Members '[CodeStore, Error CodeError, WaiRoutes] r => - JsonRequest Public.ConversationCode -> - Sem r Response -checkReusableCodeH req = do - convCode <- fromJsonBody req - checkReusableCode convCode - pure empty + mkConversationCode (codeKey c) (codeValue c) <$> E.getConversationCodeURI checkReusableCode :: - Members '[CodeStore, Error CodeError] r => - Public.ConversationCode -> - Sem r () -checkReusableCode convCode = - void $ verifyReusableCode convCode - -joinConversationByReusableCodeH :: Members - '[ BrigAccess, - CodeStore, + '[ CodeStore, ConversationStore, - FederatorAccess, - Error ActionError, - Error CodeError, + TeamFeatureStore, Error ConversationError, - Error NotATeamMember, - ExternalAccess, - GundeckAccess, - Input (Local ()), - Input Opts, - Input UTCTime, - MemberStore, - TeamStore, - WaiRoutes + Error CodeError, + Input Opts ] r => - UserId ::: ConnId ::: JsonRequest Public.ConversationCode -> - Sem r Response -joinConversationByReusableCodeH (zusr ::: zcon ::: req) = do - lusr <- qualifyLocal zusr - convCode <- fromJsonBody req - handleUpdateResult <$> joinConversationByReusableCode lusr zcon convCode + ConversationCode -> + Sem r () +checkReusableCode convCode = do + code <- verifyReusableCode convCode + conv <- E.getConversation (codeConversation code) >>= note ConvNotFound + Query.ensureGuestLinksEnabledWithError (Right CodeNotFound) (convTeam conv) joinConversationByReusableCode :: Members @@ -730,39 +683,19 @@ joinConversationByReusableCode :: Input Opts, Input UTCTime, MemberStore, - TeamStore + TeamStore, + TeamFeatureStore ] r => Local UserId -> ConnId -> - Public.ConversationCode -> + ConversationCode -> Sem r (UpdateResult Event) joinConversationByReusableCode lusr zcon convCode = do c <- verifyReusableCode convCode - joinConversation lusr zcon (codeConversation c) CodeAccess - -joinConversationByIdH :: - Members - '[ BrigAccess, - FederatorAccess, - ConversationStore, - Error ActionError, - Error ConversationError, - Error NotATeamMember, - ExternalAccess, - GundeckAccess, - Input (Local ()), - Input Opts, - Input UTCTime, - MemberStore, - TeamStore - ] - r => - UserId ::: ConnId ::: ConvId ::: JSON -> - Sem r Response -joinConversationByIdH (zusr ::: zcon ::: cnv ::: _) = do - lusr <- qualifyLocal zusr - handleUpdateResult <$> joinConversationById lusr zcon cnv + conv <- E.getConversation (codeConversation c) >>= note ConvNotFound + Query.ensureGuestLinksEnabled (convTeam conv) + joinConversation lusr zcon conv CodeAccess joinConversationById :: Members @@ -777,15 +710,17 @@ joinConversationById :: Input Opts, Input UTCTime, MemberStore, - TeamStore + TeamStore, + TeamFeatureStore ] r => Local UserId -> ConnId -> ConvId -> Sem r (UpdateResult Event) -joinConversationById lusr zcon cnv = - joinConversation lusr zcon cnv LinkAccess +joinConversationById lusr zcon cnv = do + conv <- E.getConversation cnv >>= note ConvNotFound + joinConversation lusr zcon conv LinkAccess joinConversation :: Members @@ -800,18 +735,19 @@ joinConversation :: Input Opts, Input UTCTime, MemberStore, - TeamStore + TeamStore, + TeamFeatureStore ] r => Local UserId -> ConnId -> - ConvId -> + Data.Conversation -> Access -> Sem r (UpdateResult Event) -joinConversation lusr zcon cnv access = do - let lcnv = qualifyAs lusr cnv - conv <- ensureConversationAccess (tUnqualified lusr) cnv access - ensureGroupConversation $ conv +joinConversation lusr zcon conv access = do + let lcnv = qualifyAs lusr (convId conv) + ensureConversationAccess (tUnqualified lusr) conv access + ensureGroupConversation conv -- FUTUREWORK: remote users? ensureMemberLimit (toList $ Data.convLocalMembers conv) [tUnqualified lusr] getUpdateResult $ do @@ -851,11 +787,11 @@ addMembersUnqualified :: Local UserId -> ConnId -> ConvId -> - Public.Invite -> + Invite -> Sem r (UpdateResult Event) -addMembersUnqualified lusr zcon cnv (Public.Invite users role) = do +addMembersUnqualified lusr zcon cnv (Invite users role) = do let qusers = fmap (qUntagged . qualifyAs lusr) (toNonEmpty users) - addMembers lusr zcon cnv (Public.InviteQualified qusers role) + addMembers lusr zcon cnv (InviteQualified qusers role) addMembers :: Members @@ -880,9 +816,9 @@ addMembers :: Local UserId -> ConnId -> ConvId -> - Public.InviteQualified -> + InviteQualified -> Sem r (UpdateResult Event) -addMembers lusr zcon cnv (Public.InviteQualified users role) = do +addMembers lusr zcon cnv (InviteQualified users role) = do let lcnv = qualifyAs lusr cnv getUpdateResult $ updateLocalConversation lcnv (qUntagged lusr) (Just zcon) $ @@ -901,7 +837,7 @@ updateSelfMember :: Local UserId -> ConnId -> Qualified ConvId -> - Public.MemberUpdate -> + MemberUpdate -> Sem r () updateSelfMember lusr zcon qcnv update = do exists <- foldQualified lusr checkLocalMembership checkRemoteMembership qcnv @@ -950,7 +886,7 @@ updateUnqualifiedSelfMember :: Local UserId -> ConnId -> ConvId -> - Public.MemberUpdate -> + MemberUpdate -> Sem r () updateUnqualifiedSelfMember lusr zcon cnv update = do let lcnv = qualifyAs lusr cnv @@ -973,7 +909,7 @@ updateOtherMemberUnqualified :: ConnId -> ConvId -> UserId -> - Public.OtherMemberUpdate -> + OtherMemberUpdate -> Sem r () updateOtherMemberUnqualified lusr zcon cnv victim update = do let lcnv = qualifyAs lusr cnv @@ -998,7 +934,7 @@ updateOtherMember :: ConnId -> Qualified ConvId -> Qualified UserId -> - Public.OtherMemberUpdate -> + OtherMemberUpdate -> Sem r () updateOtherMember lusr zcon qcnv qvictim update = do let doUpdate = foldQualified lusr updateOtherMemberLocalConv updateOtherMemberRemoteConv @@ -1021,7 +957,7 @@ updateOtherMemberLocalConv :: Local UserId -> ConnId -> Qualified UserId -> - Public.OtherMemberUpdate -> + OtherMemberUpdate -> Sem r () updateOtherMemberLocalConv lcnv lusr con qvictim update = void . getUpdateResult $ do when (qUntagged lusr == qvictim) $ @@ -1035,7 +971,7 @@ updateOtherMemberRemoteConv :: Local UserId -> ConnId -> Qualified UserId -> - Public.OtherMemberUpdate -> + OtherMemberUpdate -> Sem r () updateOtherMemberRemoteConv _ _ _ _ _ = throw FederationNotImplemented @@ -1150,77 +1086,6 @@ removeMemberFromLocalConv lcnv lusr con = -- OTR -data OtrResult - = OtrSent !Public.ClientMismatch - | OtrMissingRecipients !Public.ClientMismatch - | OtrUnknownClient !UnknownClient - | OtrConversationNotFound !ConvNotFound - -handleOtrResult :: - Members - '[ Error ClientError, - Error ConversationError - ] - r => - OtrResult -> - Sem r Response -handleOtrResult = - \case - OtrSent m -> pure $ json m & setStatus status201 - OtrMissingRecipients m -> pure $ json m & setStatus status412 - OtrUnknownClient _ -> throw UnknownClient - OtrConversationNotFound _ -> throw ConvNotFound - -postBotMessageH :: - Members - '[ BrigAccess, - ClientStore, - ConversationStore, - Error ClientError, - Error ConversationError, - Error LegalHoldError, - GundeckAccess, - ExternalAccess, - Input (Local ()), - Input Opts, - Input UTCTime, - MemberStore, - TeamStore, - TinyLog, - WaiRoutes - ] - r => - BotId ::: ConvId ::: Public.OtrFilterMissing ::: JsonRequest Public.NewOtrMessage ::: JSON -> - Sem r Response -postBotMessageH (zbot ::: cnv ::: val ::: req ::: _) = do - lbot <- qualifyLocal zbot - lcnv <- qualifyLocal cnv - message <- fromJsonBody req - let val' = allowOtrFilterMissingInBody val message - handleOtrResult =<< postBotMessage lbot lcnv val' message - -postBotMessage :: - Members - '[ BrigAccess, - ClientStore, - ConversationStore, - Error LegalHoldError, - ExternalAccess, - GundeckAccess, - Input Opts, - Input UTCTime, - MemberStore, - TeamStore, - TinyLog - ] - r => - Local BotId -> - Local ConvId -> - Public.OtrFilterMissing -> - Public.NewOtrMessage -> - Sem r OtrResult -postBotMessage zbot = postNewOtrMessage Bot (fmap botUserId zbot) Nothing - postProteusMessage :: Members '[ BotAccess, @@ -1240,8 +1105,8 @@ postProteusMessage :: Local UserId -> ConnId -> Qualified ConvId -> - RawProto Public.QualifiedNewOtrMessage -> - Sem r (Public.PostOtrResponse Public.MessageSendingStatus) + RawProto QualifiedNewOtrMessage -> + Sem r (PostOtrResponse MessageSendingStatus) postProteusMessage sender zcon conv msg = runLocalInput sender $ do foldQualified sender @@ -1249,114 +1114,74 @@ postProteusMessage sender zcon conv msg = runLocalInput sender $ do (\c -> postRemoteOtrMessage (qUntagged sender) c (rpRaw msg)) conv -postOtrMessageUnqualified :: - Members - '[ BotAccess, - BrigAccess, - ClientStore, - ConversationStore, - FederatorAccess, - GundeckAccess, - ExternalAccess, - MemberStore, - Input Opts, - Input UTCTime, - TeamStore, - TinyLog - ] - r => - Local UserId -> - ConnId -> - ConvId -> - Maybe Public.IgnoreMissing -> - Maybe Public.ReportMissing -> - Public.NewOtrMessage -> - Sem r (Public.PostOtrResponse Public.ClientMismatch) -postOtrMessageUnqualified sender zcon cnv ignoreMissing reportMissing message = do - let lcnv = qualifyAs sender cnv - localDomain = tDomain sender +unqualifyEndpoint :: + Functor f => + Local x -> + (QualifiedNewOtrMessage -> f (PostOtrResponse MessageSendingStatus)) -> + Maybe IgnoreMissing -> + Maybe ReportMissing -> + NewOtrMessage -> + f (PostOtrResponse ClientMismatch) +unqualifyEndpoint loc f ignoreMissing reportMissing message = do let qualifiedRecipients = - Public.QualifiedOtrRecipients + QualifiedOtrRecipients . QualifiedUserClientMap - . Map.singleton localDomain + . Map.singleton (tDomain loc) . userClientMap . fmap fromBase64TextLenient - . Public.otrRecipientsMap - . Public.newOtrRecipients + . otrRecipientsMap + . newOtrRecipients $ message - clientMismatchStrategy = legacyClientMismatchStrategy localDomain (newOtrReportMissing message) ignoreMissing reportMissing + clientMismatchStrategy = legacyClientMismatchStrategy (tDomain loc) (newOtrReportMissing message) ignoreMissing reportMissing qualifiedMessage = - Public.QualifiedNewOtrMessage - { Public.qualifiedNewOtrSender = newOtrSender message, - Public.qualifiedNewOtrRecipients = qualifiedRecipients, - Public.qualifiedNewOtrNativePush = newOtrNativePush message, - Public.qualifiedNewOtrTransient = newOtrTransient message, - Public.qualifiedNewOtrNativePriority = newOtrNativePriority message, - Public.qualifiedNewOtrData = maybe mempty fromBase64TextLenient (newOtrData message), - Public.qualifiedNewOtrClientMismatchStrategy = clientMismatchStrategy + QualifiedNewOtrMessage + { qualifiedNewOtrSender = newOtrSender message, + qualifiedNewOtrRecipients = qualifiedRecipients, + qualifiedNewOtrNativePush = newOtrNativePush message, + qualifiedNewOtrTransient = newOtrTransient message, + qualifiedNewOtrNativePriority = newOtrNativePriority message, + qualifiedNewOtrData = maybe mempty fromBase64TextLenient (newOtrData message), + qualifiedNewOtrClientMismatchStrategy = clientMismatchStrategy } - runLocalInput sender $ - unqualify localDomain - <$> postQualifiedOtrMessage User (qUntagged sender) (Just zcon) lcnv qualifiedMessage + unqualify (tDomain loc) <$> f qualifiedMessage -postProtoOtrBroadcastH :: +postBotMessageUnqualified :: Members '[ BrigAccess, ClientStore, - Error ActionError, - Error ClientError, - Error ConversationError, - Error LegalHoldError, - Error TeamError, - GundeckAccess, - Input (Local ()), - Input Opts, - Input UTCTime, - TeamStore, - TinyLog, - WaiRoutes - ] - r => - UserId ::: ConnId ::: Public.OtrFilterMissing ::: Request ::: JSON -> - Sem r Response -postProtoOtrBroadcastH (zusr ::: zcon ::: val ::: req ::: _) = do - lusr <- qualifyLocal zusr - message <- Public.protoToNewOtrMessage <$> fromProtoBody req - let val' = allowOtrFilterMissingInBody val message - handleOtrResult =<< postOtrBroadcast lusr zcon val' message - -postOtrBroadcastH :: - Members - '[ BrigAccess, - ClientStore, - Error ActionError, - Error ClientError, - Error ConversationError, - Error LegalHoldError, - Error TeamError, + ConversationStore, + ExternalAccess, + FederatorAccess, GundeckAccess, Input (Local ()), Input Opts, Input UTCTime, + MemberStore, TeamStore, - TinyLog, - WaiRoutes + TinyLog ] r => - UserId ::: ConnId ::: Public.OtrFilterMissing ::: JsonRequest Public.NewOtrMessage -> - Sem r Response -postOtrBroadcastH (zusr ::: zcon ::: val ::: req) = do - lusr <- qualifyLocal zusr - message <- fromJsonBody req - let val' = allowOtrFilterMissingInBody val message - handleOtrResult =<< postOtrBroadcast lusr zcon val' message + BotId -> + ConvId -> + Maybe IgnoreMissing -> + Maybe ReportMissing -> + NewOtrMessage -> + Sem r (PostOtrResponse ClientMismatch) +postBotMessageUnqualified sender cnv ignoreMissing reportMissing message = do + lusr <- qualifyLocal (botUserId sender) + lcnv <- qualifyLocal cnv + unqualifyEndpoint + lusr + (runLocalInput lusr . postQualifiedOtrMessage Bot (qUntagged lusr) Nothing lcnv) + ignoreMissing + reportMissing + message -postOtrBroadcast :: +postOtrBroadcastUnqualified :: Members '[ BrigAccess, ClientStore, Error ActionError, - Error LegalHoldError, Error TeamError, GundeckAccess, Input Opts, @@ -1367,114 +1192,43 @@ postOtrBroadcast :: r => Local UserId -> ConnId -> - Public.OtrFilterMissing -> - Public.NewOtrMessage -> - Sem r OtrResult -postOtrBroadcast lusr zcon = postNewOtrBroadcast lusr (Just zcon) - --- internal OTR helpers - --- This is a work-around for the fact that we sometimes want to send larger lists of user ids --- in the filter query than fits the url length limit. for details, see --- https://github.com/zinfra/backend-issues/issues/1248 -allowOtrFilterMissingInBody :: OtrFilterMissing -> NewOtrMessage -> OtrFilterMissing -allowOtrFilterMissingInBody val (NewOtrMessage _ _ _ _ _ _ mrepmiss) = case mrepmiss of - Nothing -> val - Just uids -> OtrReportMissing $ Set.fromList uids - --- | bots are not supported on broadcast -postNewOtrBroadcast :: - Members - '[ BrigAccess, - ClientStore, - Error ActionError, - Error LegalHoldError, - Error TeamError, - Input Opts, - Input UTCTime, - GundeckAccess, - TeamStore, - TinyLog - ] - r => - Local UserId -> - Maybe ConnId -> - OtrFilterMissing -> + Maybe IgnoreMissing -> + Maybe ReportMissing -> NewOtrMessage -> - Sem r OtrResult -postNewOtrBroadcast lusr con val msg = do - let sender = newOtrSender msg - recvrs = newOtrRecipients msg - now <- input - withValidOtrBroadcastRecipients (tUnqualified lusr) sender recvrs val now $ \rs -> do - let (_, toUsers) = foldr (newMessage (qUntagged lusr) con Nothing msg now) ([], []) rs - E.push (catMaybes toUsers) + Sem r (PostOtrResponse ClientMismatch) +postOtrBroadcastUnqualified sender zcon = + unqualifyEndpoint + sender + (postBroadcast sender (Just zcon)) -postNewOtrMessage :: +postOtrMessageUnqualified :: Members - '[ BrigAccess, + '[ BotAccess, + BrigAccess, ClientStore, ConversationStore, - Error LegalHoldError, - ExternalAccess, + FederatorAccess, GundeckAccess, + ExternalAccess, + MemberStore, Input Opts, Input UTCTime, - MemberStore, TeamStore, TinyLog ] r => - UserType -> Local UserId -> - Maybe ConnId -> - Local ConvId -> - OtrFilterMissing -> - NewOtrMessage -> - Sem r OtrResult -postNewOtrMessage utype lusr con lcnv val msg = do - let sender = newOtrSender msg - recvrs = newOtrRecipients msg - now <- input - withValidOtrRecipients utype (tUnqualified lusr) sender (tUnqualified lcnv) recvrs val now $ \rs -> do - let (toBots, toUsers) = foldr (newMessage (qUntagged lusr) con (Just (qUntagged lcnv)) msg now) ([], []) rs - E.push (catMaybes toUsers) - E.deliverAndDeleteAsync (tUnqualified lcnv) toBots - -newMessage :: - Qualified UserId -> - Maybe ConnId -> - -- | Conversation Id (if Nothing, recipient's self conversation is used) - Maybe (Qualified ConvId) -> + ConnId -> + ConvId -> + Maybe IgnoreMissing -> + Maybe ReportMissing -> NewOtrMessage -> - UTCTime -> - (LocalMember, ClientId, Text) -> - ([(BotMember, Event)], [Maybe Push]) -> - ([(BotMember, Event)], [Maybe Push]) -newMessage qusr con qcnv msg now (m, c, t) ~(toBots, toUsers) = - let o = - OtrMessage - { otrSender = newOtrSender msg, - otrRecipient = c, - otrCiphertext = t, - otrData = newOtrData msg - } - -- use recipient's client's self conversation on broadcast - -- (with federation, this might not work for remote members) - -- FUTUREWORK: for remote recipients, set the domain correctly here - qconv = fromMaybe ((`Qualified` qDomain qusr) . selfConv $ lmId m) qcnv - e = Event OtrMessageAdd qconv qusr now (EdOtrMessage o) - r = recipient m & recipientClients .~ RecipientClientsSome (singleton c) - in case newBotMember m of - Just b -> ((b, e) : toBots, toUsers) - Nothing -> - let p = - newPushLocal ListComplete (qUnqualified (evtFrom e)) (ConvEvent e) [r] - <&> set pushConn con - . set pushNativePriority (newOtrNativePriority msg) - . set pushRoute (bool RouteDirect RouteAny (newOtrNativePush msg)) - . set pushTransient (newOtrTransient msg) - in (toBots, p : toUsers) + Sem r (PostOtrResponse ClientMismatch) +postOtrMessageUnqualified sender zcon cnv = + let lcnv = qualifyAs sender cnv + in unqualifyEndpoint + sender + (runLocalInput sender . postQualifiedOtrMessage User (qUntagged sender) (Just zcon) lcnv) updateConversationName :: Members @@ -1492,8 +1246,8 @@ updateConversationName :: Local UserId -> ConnId -> Qualified ConvId -> - Public.ConversationRename -> - Sem r (Maybe Public.Event) + ConversationRename -> + Sem r (Maybe Event) updateConversationName lusr zcon qcnv convRename = do foldQualified lusr @@ -1517,8 +1271,8 @@ updateUnqualifiedConversationName :: Local UserId -> ConnId -> ConvId -> - Public.ConversationRename -> - Sem r (Maybe Public.Event) + ConversationRename -> + Sem r (Maybe Event) updateUnqualifiedConversationName lusr zcon cnv rename = do let lcnv = qualifyAs lusr cnv updateLocalConversationName lusr zcon lcnv rename @@ -1538,8 +1292,8 @@ updateLocalConversationName :: Local UserId -> ConnId -> Local ConvId -> - Public.ConversationRename -> - Sem r (Maybe Public.Event) + ConversationRename -> + Sem r (Maybe Event) updateLocalConversationName lusr zcon lcnv convRename = do alive <- E.isConversationAlive (tUnqualified lcnv) if alive @@ -1561,13 +1315,13 @@ updateLiveLocalConversationName :: Local UserId -> ConnId -> Local ConvId -> - Public.ConversationRename -> - Sem r (Maybe Public.Event) + ConversationRename -> + Sem r (Maybe Event) updateLiveLocalConversationName lusr con lcnv rename = fmap hush . runError @NoChanges $ updateLocalConversation lcnv (qUntagged lusr) (Just con) rename -isTypingH :: +isTypingUnqualified :: Members '[ Error ConversationError, GundeckAccess, @@ -1577,21 +1331,21 @@ isTypingH :: WaiRoutes ] r => - UserId ::: ConnId ::: ConvId ::: JsonRequest Public.TypingData -> - Sem r Response -isTypingH (zusr ::: zcon ::: cnv ::: req) = do - lusr <- qualifyLocal zusr + Local UserId -> + ConnId -> + ConvId -> + TypingData -> + Sem r () +isTypingUnqualified lusr zcon cnv typingData = do lcnv <- qualifyLocal cnv - typingData <- fromJsonBody req isTyping lusr zcon lcnv typingData - pure empty isTyping :: Members '[Error ConversationError, GundeckAccess, Input UTCTime, MemberStore] r => Local UserId -> ConnId -> Local ConvId -> - Public.TypingData -> + TypingData -> Sem r () isTyping lusr zcon lcnv typingData = do mm <- E.getLocalMembers (tUnqualified lcnv) @@ -1673,7 +1427,6 @@ addBot lusr zcon b = do c <- E.getConversation (b ^. addBotConv) >>= note ConvNotFound -- Check some preconditions on adding bots to a conversation - for_ (Data.convTeam c) $ teamConvChecks (b ^. addBotConv) (bots, users) <- regularConvChecks c t <- input E.createClient (botUserId (b ^. addBotId)) (b ^. addBotClient) @@ -1707,11 +1460,6 @@ addBot lusr zcon b = do let botId = qualifyAs lusr (botUserId (b ^. addBotId)) ensureMemberLimit (toList $ Data.convLocalMembers c) [qUntagged botId] return (bots, users) - teamConvChecks :: ConvId -> TeamId -> Sem r () - teamConvChecks cid tid = do - tcv <- E.getTeamConversation tid cid - when (maybe True (view managedConversation) tcv) $ - throw NoAddToManaged rmBotH :: Members @@ -1775,240 +1523,3 @@ rmBot lusr zcon b = do ensureConvMember :: Member (Error ConversationError) r => [LocalMember] -> UserId -> Sem r () ensureConvMember users usr = unless (usr `isMember` users) $ throw ConvNotFound - -------------------------------------------------------------------------------- --- OtrRecipients Validation - -data CheckedOtrRecipients - = -- | Valid sender (user and client) and no missing recipients, - -- or missing recipients have been willfully ignored. - ValidOtrRecipients !ClientMismatch [(LocalMember, ClientId, Text)] - | -- | Missing recipients. - MissingOtrRecipients !ClientMismatch - | -- | Invalid sender (user). - InvalidOtrSenderUser - | -- | Invalid sender (client). - InvalidOtrSenderClient - --- | bots are not supported on broadcast -withValidOtrBroadcastRecipients :: - forall r. - Members - '[ BrigAccess, - ClientStore, - Error ActionError, - Error LegalHoldError, - Error TeamError, - Input Opts, - TeamStore, - TinyLog - ] - r => - UserId -> - ClientId -> - OtrRecipients -> - OtrFilterMissing -> - UTCTime -> - ([(LocalMember, ClientId, Text)] -> Sem r ()) -> - Sem r OtrResult -withValidOtrBroadcastRecipients usr clt rcps val now go = withBindingTeam usr $ \tid -> do - limit <- fromIntegral . fromRange <$> E.fanoutLimit - -- If we are going to fan this out to more than limit, we want to fail early - unless (Map.size (userClientMap (otrRecipientsMap rcps)) <= limit) $ - throw BroadcastLimitExceeded - -- In large teams, we may still use the broadcast endpoint but only if `report_missing` - -- is used and length `report_missing` < limit since we cannot fetch larger teams than - -- that. - tMembers <- - fmap (view userId) <$> case val of - OtrReportMissing us -> maybeFetchLimitedTeamMemberList limit tid us - _ -> maybeFetchAllMembersInTeam tid - contacts <- E.getContactList usr - let users = Set.toList $ Set.union (Set.fromList tMembers) (Set.fromList contacts) - isInternal <- E.useIntraClientListing - clts <- - if isInternal - then Clients.fromUserClients <$> E.lookupClients users - else E.getClients users - let membs = newMember <$> users - handleOtrResponse User usr clt rcps membs clts val now go - where - maybeFetchLimitedTeamMemberList limit tid uListInFilter = do - -- Get the users in the filter (remote ids are not in a local team) - let localUserIdsInFilter = toList uListInFilter - let localUserIdsInRcps = Map.keys $ userClientMap (otrRecipientsMap rcps) - let localUserIdsToLookup = Set.toList $ Set.union (Set.fromList localUserIdsInFilter) (Set.fromList localUserIdsInRcps) - unless (length localUserIdsToLookup <= limit) $ - throw BroadcastLimitExceeded - E.selectTeamMembers tid localUserIdsToLookup - maybeFetchAllMembersInTeam :: TeamId -> Sem r [TeamMember] - maybeFetchAllMembersInTeam tid = do - mems <- getTeamMembersForFanout tid - when (mems ^. teamMemberListType == ListTruncated) $ - throw BroadcastLimitExceeded - pure (mems ^. teamMembers) - -withValidOtrRecipients :: - Members - '[ BrigAccess, - ClientStore, - ConversationStore, - Error LegalHoldError, - Input Opts, - MemberStore, - TeamStore, - TinyLog - ] - r => - UserType -> - UserId -> - ClientId -> - ConvId -> - OtrRecipients -> - OtrFilterMissing -> - UTCTime -> - ([(LocalMember, ClientId, Text)] -> Sem r ()) -> - Sem r OtrResult -withValidOtrRecipients utype usr clt cnv rcps val now go = do - alive <- E.isConversationAlive cnv - if not alive - then do - E.deleteConversation cnv - pure $ OtrConversationNotFound mkErrorDescription - else do - localMembers <- E.getLocalMembers cnv - let localMemberIds = lmId <$> localMembers - isInternal <- E.useIntraClientListing - clts <- - if isInternal - then Clients.fromUserClients <$> E.lookupClients localMemberIds - else E.getClients localMemberIds - handleOtrResponse utype usr clt rcps localMembers clts val now go - -handleOtrResponse :: - Members '[BrigAccess, Error LegalHoldError, Input Opts, TeamStore, TinyLog] r => - -- | Type of proposed sender (user / bot) - UserType -> - -- | Proposed sender (user) - UserId -> - -- | Proposed sender (client) - ClientId -> - -- | Proposed recipients (users & clients). - OtrRecipients -> - -- | Members to consider as valid recipients. - [LocalMember] -> - -- | Clients to consider as valid recipients. - Clients -> - -- | How to filter missing clients. - OtrFilterMissing -> - -- | The current timestamp. - UTCTime -> - -- | Callback if OtrRecipients are valid - ([(LocalMember, ClientId, Text)] -> Sem r ()) -> - Sem r OtrResult -handleOtrResponse utype usr clt rcps membs clts val now go = case checkOtrRecipients usr clt rcps membs clts val now of - ValidOtrRecipients m r -> go r >> pure (OtrSent m) - MissingOtrRecipients m -> mapError @LegalholdConflicts (const MissingLegalholdConsent) $ do - guardLegalholdPolicyConflicts (userToProtectee utype usr) (missingClients m) - pure (OtrMissingRecipients m) - InvalidOtrSenderUser -> pure $ OtrConversationNotFound mkErrorDescription - InvalidOtrSenderClient -> pure $ OtrUnknownClient mkErrorDescription - --- | Check OTR sender and recipients for validity and completeness --- against a given list of valid members and clients, optionally --- ignoring missing clients. Returns 'ValidOtrRecipients' on success --- for further processing. -checkOtrRecipients :: - -- | Proposed sender (user) - UserId -> - -- | Proposed sender (client) - ClientId -> - -- | Proposed recipients (users & clients). - OtrRecipients -> - -- | Members to consider as valid recipients. - [LocalMember] -> - -- | Clients to consider as valid recipients. - Clients -> - -- | How to filter missing clients. - OtrFilterMissing -> - -- | The current timestamp. - UTCTime -> - CheckedOtrRecipients -checkOtrRecipients usr sid prs vms vcs val now - | not (Map.member usr vmembers) = InvalidOtrSenderUser - | not (Clients.contains usr sid vcs) = InvalidOtrSenderClient - | not (Clients.null missing) = MissingOtrRecipients mismatch - | otherwise = ValidOtrRecipients mismatch yield - where - yield :: [(LocalMember, ClientId, Text)] - yield = foldrOtrRecipients next [] prs - - next :: r ~ [(LocalMember, ClientId, c)] => UserId -> ClientId -> c -> r -> r - next u c t rs - | Just m <- member u c = (m, c, t) : rs - | otherwise = rs - - member :: UserId -> ClientId -> Maybe LocalMember - member u c - | Just m <- Map.lookup u vmembers, - Clients.contains u c vclients = - Just m - | otherwise = Nothing - - -- Valid recipient members & clients - vmembers :: Map UserId LocalMember - vmembers = Map.fromList $ map (\m -> (lmId m, m)) vms - - vclients :: Clients - vclients = Clients.rmClient usr sid vcs - - -- Proposed (given) recipients - recipients :: Map UserId (Map ClientId Text) - recipients = userClientMap (otrRecipientsMap prs) - - given :: Clients - given = Clients.fromMap (Map.map Map.keysSet recipients) - - -- Differences between valid and proposed recipients - missing, unknown, deleted, redundant :: Clients - missing = filterMissing (Clients.diff vclients given) - unknown = Clients.diff given vcs - deleted = Clients.filter (`Map.member` vmembers) unknown - redundant = - Clients.diff unknown deleted - & if Clients.contains usr sid given - then Clients.insert usr sid - else id - - mismatch :: ClientMismatch - mismatch = - ClientMismatch - { cmismatchTime = toUTCTimeMillis now, - missingClients = UserClients (Clients.toMap missing), - redundantClients = UserClients (Clients.toMap redundant), - deletedClients = UserClients (Clients.toMap deleted) - } - - filterMissing :: Clients -> Clients - filterMissing miss = case val of - OtrReportAllMissing -> miss - OtrIgnoreAllMissing -> Clients.nil - OtrReportMissing us -> Clients.filter (`Set.member` us) miss - OtrIgnoreMissing us -> Clients.filter (`Set.notMember` us) miss - --- Copied from 'Galley.API.Team' to break import cycles -withBindingTeam :: - Members - '[ Error TeamError, - TeamStore - ] - r => - UserId -> - (TeamId -> Sem r b) -> - Sem r b -withBindingTeam zusr callback = do - tid <- E.getOneUserTeam zusr >>= note TeamNotFound - binding <- E.getTeamBinding tid >>= note TeamNotFound - case binding of - Binding -> callback tid - NonBinding -> throw NotABindingTeamMember diff --git a/services/galley/src/Galley/API/Util.hs b/services/galley/src/Galley/API/Util.hs index bbd5611ac1f..b3e35326c5b 100644 --- a/services/galley/src/Galley/API/Util.hs +++ b/services/galley/src/Galley/API/Util.hs @@ -587,17 +587,13 @@ ensureConversationAccess :: ] r => UserId -> - ConvId -> + Data.Conversation -> Access -> - Sem r Data.Conversation -ensureConversationAccess zusr cnv access = do - conv <- - getConversation cnv >>= note ConvNotFound + Sem r () +ensureConversationAccess zusr conv access = do ensureAccess conv access - zusrMembership <- - maybe (pure Nothing) (`getTeamMember` zusr) (Data.convTeam conv) + zusrMembership <- maybe (pure Nothing) (`getTeamMember` zusr) (Data.convTeam conv) ensureAccessRole (Data.convAccessRoles conv) [(zusr, zusrMembership)] - pure conv ensureAccess :: Member (Error ConversationError) r => diff --git a/services/galley/src/Galley/Cassandra.hs b/services/galley/src/Galley/Cassandra.hs index f0b39e870fd..b7ee15df559 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 = 59 +schemaVersion = 60 diff --git a/services/galley/src/Galley/Cassandra/Conversation.hs b/services/galley/src/Galley/Cassandra/Conversation.hs index 27c61631877..99c5ff4fcc7 100644 --- a/services/galley/src/Galley/Cassandra/Conversation.hs +++ b/services/galley/src/Galley/Cassandra/Conversation.hs @@ -62,7 +62,7 @@ createConversation (NewConversation ty usr acc arole name mtid mtimer recpt user setType BatchLogged setConsistency LocalQuorum addPrepQuery Cql.insertConv (conv, ty, usr, Cql.Set (toList acc), Cql.Set (toList arole), fmap fromRange name, Just tid, mtimer, recpt) - addPrepQuery Cql.insertTeamConv (tid, conv, False) + addPrepQuery Cql.insertTeamConv (tid, conv) let newUsers = fmap (,role) (fromConvSize users) (lmems, rmems) <- addMembers conv (ulAddLocal (usr, roleNameWireAdmin) newUsers) pure $ @@ -169,7 +169,7 @@ createOne2OneConversation conv self other name mtid = do setType BatchLogged setConsistency LocalQuorum addPrepQuery Cql.insertConv (conv, One2OneConv, tUnqualified self, privateOnly, Cql.Set [], fromRange <$> name, Just tid, Nothing, Nothing) - addPrepQuery Cql.insertTeamConv (tid, conv, False) + addPrepQuery Cql.insertTeamConv (tid, conv) (lmems, rmems) <- addMembers conv (toUserList self [qUntagged self, other]) pure Conversation diff --git a/services/galley/src/Galley/Cassandra/Instances.hs b/services/galley/src/Galley/Cassandra/Instances.hs index 8301c46ffb5..0e3ebaf5ec1 100644 --- a/services/galley/src/Galley/Cassandra/Instances.hs +++ b/services/galley/src/Galley/Cassandra/Instances.hs @@ -106,12 +106,11 @@ instance Cql AccessRoleV2 where instance Cql ConvTeamInfo where ctype = Tagged $ UdtColumn "teaminfo" [("teamid", UuidColumn), ("managed", BooleanColumn)] - toCql t = CqlUdt [("teamid", toCql (cnvTeamId t)), ("managed", toCql (cnvManaged t))] + toCql t = CqlUdt [("teamid", toCql (cnvTeamId t)), ("managed", toCql False)] fromCql (CqlUdt u) = do t <- note "missing 'teamid' in teaminfo" ("teamid" `lookup` u) >>= fromCql - m <- note "missing 'managed' in teaminfo" ("managed" `lookup` u) >>= fromCql - pure (ConvTeamInfo t m) + pure (ConvTeamInfo t) fromCql _ = Left "teaminfo: udt expected" instance Cql TeamBinding where diff --git a/services/galley/src/Galley/Cassandra/Queries.hs b/services/galley/src/Galley/Cassandra/Queries.hs index 68b646a11cd..4d199920a53 100644 --- a/services/galley/src/Galley/Cassandra/Queries.hs +++ b/services/galley/src/Galley/Cassandra/Queries.hs @@ -51,14 +51,14 @@ selectTeamBinding = "select binding from team where team = ?" selectTeamBindingWritetime :: PrepQuery R (Identity TeamId) (Identity (Maybe Int64)) selectTeamBindingWritetime = "select writetime(binding) from team where team = ?" -selectTeamConv :: PrepQuery R (TeamId, ConvId) (Identity Bool) -selectTeamConv = "select managed from team_conv where team = ? and conv = ?" +selectTeamConv :: PrepQuery R (TeamId, ConvId) (Identity ConvId) +selectTeamConv = "select conv from team_conv where team = ? and conv = ?" -selectTeamConvs :: PrepQuery R (Identity TeamId) (ConvId, Bool) -selectTeamConvs = "select conv, managed from team_conv where team = ? order by conv" +selectTeamConvs :: PrepQuery R (Identity TeamId) (Identity ConvId) +selectTeamConvs = "select conv from team_conv where team = ? order by conv" -selectTeamConvsFrom :: PrepQuery R (TeamId, ConvId) (ConvId, Bool) -selectTeamConvsFrom = "select conv, managed from team_conv where team = ? and conv > ? order by conv" +selectTeamConvsFrom :: PrepQuery R (TeamId, ConvId) (Identity ConvId) +selectTeamConvsFrom = "select conv from team_conv where team = ? and conv > ? order by conv" selectTeamMember :: PrepQuery @@ -138,8 +138,8 @@ selectUserTeamsFrom = "select team from user_team where user = ? and team > ? or insertTeam :: PrepQuery W (TeamId, UserId, Text, Text, Maybe Text, TeamStatus, TeamBinding) () insertTeam = "insert into team (team, creator, name, icon, icon_key, deleted, status, binding) values (?, ?, ?, ?, ?, false, ?, ?)" -insertTeamConv :: PrepQuery W (TeamId, ConvId, Bool) () -insertTeamConv = "insert into team_conv (team, conv, managed) values (?, ?, ?)" +insertTeamConv :: PrepQuery W (TeamId, ConvId) () +insertTeamConv = "insert into team_conv (team, conv) values (?, ?)" deleteTeamConv :: PrepQuery W (TeamId, ConvId) () deleteTeamConv = "delete from team_conv where team = ? and conv = ?" diff --git a/services/galley/src/Galley/Cassandra/Team.hs b/services/galley/src/Galley/Cassandra/Team.hs index 2e6171bf8b0..c0ae2938f10 100644 --- a/services/galley/src/Galley/Cassandra/Team.hs +++ b/services/galley/src/Galley/Cassandra/Team.hs @@ -29,12 +29,14 @@ import Control.Exception (ErrorCall (ErrorCall)) import Control.Lens hiding ((<|)) import Control.Monad.Catch (throwM) import Control.Monad.Extra (ifM) +import Data.ByteString.Conversion (toByteString') import Data.Id as Id import Data.Json.Util (UTCTimeMillis (..)) import Data.LegalHold (UserLegalHoldStatus (..), defUserLegalHoldStatus) import qualified Data.Map.Strict as Map import Data.Range import qualified Data.Set as Set +import Data.Text.Encoding import Data.UUID.V4 (nextRandom) import qualified Galley.Aws as Aws import qualified Galley.Cassandra.Conversation as C @@ -159,12 +161,12 @@ getTeamName tid = teamConversation :: TeamId -> ConvId -> Client (Maybe TeamConversation) teamConversation t c = - fmap (newTeamConversation c . runIdentity) + fmap (newTeamConversation . runIdentity) <$> retry x1 (query1 Cql.selectTeamConv (params LocalQuorum (t, c))) getTeamConversations :: TeamId -> Client [TeamConversation] getTeamConversations t = - map (uncurry newTeamConversation) + map (newTeamConversation . runIdentity) <$> retry x1 (query Cql.selectTeamConvs (params LocalQuorum (Identity t))) teamIdsFrom :: UserId -> Maybe TeamId -> Range 1 100 Int32 -> Client (ResultSet TeamId) @@ -375,7 +377,7 @@ updateTeam tid u = retry x5 . batch $ do for_ (u ^. nameUpdate) $ \n -> addPrepQuery Cql.updateTeamName (fromRange n, tid) for_ (u ^. iconUpdate) $ \i -> - addPrepQuery Cql.updateTeamIcon (fromRange i, tid) + addPrepQuery Cql.updateTeamIcon (decodeUtf8 . toByteString' $ i, tid) for_ (u ^. iconKeyUpdate) $ \k -> addPrepQuery Cql.updateTeamIconKey (fromRange k, tid) @@ -416,7 +418,7 @@ newTeamMember' lh tid (uid, perms, minvu, minvt, fromMaybe defUserLegalHoldStatu teamConversationsForPagination :: TeamId -> Maybe ConvId -> Range 1 HardTruncationLimit Int32 -> Client (Page TeamConversation) teamConversationsForPagination tid start (fromRange -> max) = - fmap (uncurry newTeamConversation) <$> case start of + fmap (newTeamConversation . runIdentity) <$> case start of Just c -> paginate Cql.selectTeamConvsFrom (paramsP LocalQuorum (tid, c) max) Nothing -> paginate Cql.selectTeamConvs (paramsP LocalQuorum (Identity tid) max) diff --git a/services/galley/src/Galley/Data/TeamFeatures.hs b/services/galley/src/Galley/Data/TeamFeatures.hs index 99f100215a9..86daac19ff9 100644 --- a/services/galley/src/Galley/Data/TeamFeatures.hs +++ b/services/galley/src/Galley/Data/TeamFeatures.hs @@ -51,6 +51,8 @@ instance HasStatusCol 'TeamFeatureSelfDeletingMessages where statusCol = "self_d instance HasStatusCol 'TeamFeatureGuestLinks where statusCol = "guest_links_status" +instance HasStatusCol 'TeamFeatureSndFactorPasswordChallenge where statusCol = "snd_factor_password_challenge_status" + ---------------------------------------------------------------------- class HasLockStatusCol (a :: TeamFeatureName) where lockStatusCol :: String @@ -71,6 +73,9 @@ instance HasLockStatusCol 'TeamFeatureGuestLinks where instance HasLockStatusCol 'TeamFeatureFileSharing where lockStatusCol = "file_sharing_lock_status" +instance HasLockStatusCol 'TeamFeatureSndFactorPasswordChallenge where + lockStatusCol = "snd_factor_password_challenge_lock_status" + instance MaybeHasLockStatusCol 'TeamFeatureLegalHold where maybeLockStatusCol = Nothing instance MaybeHasLockStatusCol 'TeamFeatureSSO where maybeLockStatusCol = Nothing diff --git a/services/galley/src/Galley/Effects/TeamStore.hs b/services/galley/src/Galley/Effects/TeamStore.hs index 7521bc0d7de..9fa4dd42d63 100644 --- a/services/galley/src/Galley/Effects/TeamStore.hs +++ b/services/galley/src/Galley/Effects/TeamStore.hs @@ -37,6 +37,7 @@ module Galley.Effects.TeamStore getUserTeams, getUsersTeams, getOneUserTeam, + lookupBindingTeam, -- ** Update teams deleteTeamConversation, @@ -75,12 +76,14 @@ where import Data.Id import Data.Range +import Galley.API.Error import Galley.Effects.ListItems import Galley.Effects.Paging import Galley.Types.Teams import Galley.Types.Teams.Intra import Imports import Polysemy +import Polysemy.Error import qualified Proto.TeamEvents as E data TeamStore m a where @@ -128,3 +131,18 @@ listTeams :: PagingBounds p TeamId -> Sem r (Page p TeamId) listTeams = listItems + +lookupBindingTeam :: + Members + '[ Error TeamError, + TeamStore + ] + r => + UserId -> + Sem r TeamId +lookupBindingTeam zusr = do + tid <- getOneUserTeam zusr >>= note TeamNotFound + binding <- getTeamBinding tid >>= note TeamNotFound + case binding of + Binding -> return tid + NonBinding -> throw NotABindingTeamMember diff --git a/services/galley/src/Galley/Run.hs b/services/galley/src/Galley/Run.hs index fcf0b8839ed..92262039a9e 100644 --- a/services/galley/src/Galley/Run.hs +++ b/services/galley/src/Galley/Run.hs @@ -38,7 +38,7 @@ import Data.String.Conversions (cs) import Data.Text (unpack) import qualified Galley.API as API import Galley.API.Federation (FederationAPI, federationSitemap) -import qualified Galley.API.Internal as Internal +import Galley.API.Internal import Galley.App import qualified Galley.App as App import Galley.Cassandra @@ -56,6 +56,7 @@ import Servant hiding (route) import qualified System.Logger as Log import Util.Options import qualified Wire.API.Routes.Public.Galley as GalleyAPI +import Wire.API.Routes.Version.Wai run :: Opts -> IO () run o = do @@ -68,7 +69,7 @@ run o = do (portNumber $ fromIntegral $ o ^. optGalley . epPort) l (e ^. monitor) - deleteQueueThread <- Async.async $ runApp e Internal.deleteLoop + deleteQueueThread <- Async.async $ runApp e deleteLoop refreshMetricsThread <- Async.async $ runApp e refreshMetrics runSettingsWithShutdown s app 5 `finally` do Async.cancel deleteQueueThread @@ -92,6 +93,7 @@ mkApp o = do . GZip.gunzip . GZip.gzip GZip.def . catchErrors l [Right m] + . versionMiddleware return (middlewares $ servantApp e, e, finalizer) where rtree = compile API.sitemap @@ -106,7 +108,7 @@ mkApp o = do :. Servant.EmptyContext ) ( hoistServer' @GalleyAPI.ServantAPI (toServantHandler e) API.servantSitemap - :<|> hoistServer' @Internal.ServantAPI (toServantHandler e) Internal.servantSitemap + :<|> hoistServer' @InternalAPI (toServantHandler e) internalAPI :<|> hoistServer' @FederationAPI (toServantHandler e) federationSitemap :<|> Servant.Tagged (app e) ) @@ -152,7 +154,7 @@ bodyParserErrorFormatter' _ _ errMsg = type CombinedAPI = GalleyAPI.ServantAPI - :<|> Internal.ServantAPI + :<|> InternalAPI :<|> FederationAPI :<|> Servant.Raw @@ -160,7 +162,7 @@ refreshMetrics :: App () refreshMetrics = do m <- view monitor q <- view deleteQueue - Internal.safeForever "refreshMetrics" $ do + safeForever "refreshMetrics" $ do n <- Q.len q M.gaugeSet (fromIntegral n) (M.path "galley.deletequeue.len") m threadDelay 1000000 diff --git a/services/galley/test/integration/API.hs b/services/galley/test/integration/API.hs index ecde034202a..a6405832a30 100644 --- a/services/galley/test/integration/API.hs +++ b/services/galley/test/integration/API.hs @@ -36,6 +36,7 @@ import qualified API.Teams.LegalHold.DisabledByDefault import API.Util import qualified API.Util as Util import API.Util.TeamFeature as TeamFeatures +import qualified API.Util.TeamFeature as Util import Bilge hiding (timeout) import Bilge.Assert import Brig.Types @@ -155,6 +156,8 @@ tests s = test s "add members" postMembersOk, test s "add existing members" postMembersOk2, test s "add past members" postMembersOk3, + test s "add guest forbidden when no guest access role" postMembersFailNoGuestAccess, + test s "generate guest link forbidden when no guest or non-team-member access role" generateGuestLinkFailIfNoNonTeamMemberOrNoGuestAccess, test s "fail to add members when not connected" postMembersFail, test s "fail to add too many members" postTooManyMembersFail, test s "add remote members" testAddRemoteMember, @@ -1209,13 +1212,13 @@ testGetCodeRejectedIfGuestLinksDisabled :: TestM () testGetCodeRejectedIfGuestLinksDisabled = do galley <- view tsGalley (owner, teamId, []) <- Util.createBindingTeamWithNMembers 0 - Right accessRoles <- liftIO $ genAccessRolesV2 [TeamMemberAccessRole] [GuestAccessRole] + Right accessRoles <- liftIO $ genAccessRolesV2 [TeamMemberAccessRole, GuestAccessRole] [] let createConvWithGuestLink = do convId <- decodeConvId <$> postTeamConv teamId owner [] (Just "testConversation") [CodeAccess] (Just accessRoles) Nothing void $ decodeConvCodeEvent <$> postConvCode owner convId pure convId convId <- createConvWithGuestLink - let checkGetCode expectedStatus = getConvCode owner convId !!! statusCode === const expectedStatus + let checkGetCode expectedStatus = getConvCode owner convId !!! const expectedStatus === statusCode let setStatus tfStatus = TeamFeatures.putTeamFeatureFlagWithGalley @'Public.TeamFeatureGuestLinks galley owner teamId (Public.TeamFeatureStatusNoConfig tfStatus) !!! do const 200 === statusCode @@ -1248,32 +1251,55 @@ testJoinTeamConvGuestLinksDisabled = do galley <- view tsGalley let convName = "testConversation" (owner, teamId, []) <- Util.createBindingTeamWithNMembers 0 - userNotInTeam <- randomUser - Right accessRoles <- liftIO $ genAccessRolesV2 [TeamMemberAccessRole, NonTeamMemberAccessRole] [GuestAccessRole] - convId <- decodeConvId <$> postTeamConv teamId owner [] (Just convName) [CodeAccess] (Just accessRoles) Nothing + eve <- ephemeralUser + 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 - -- works by default - getJoinCodeConv userNotInTeam (conversationKey cCode) (conversationCode cCode) !!! do + let checkFeatureStatus fstatus = + Util.getTeamFeatureFlagWithGalley Public.TeamFeatureGuestLinks galley owner teamId !!! do + const 200 === statusCode + const (Right (Public.TeamFeatureStatusNoConfigAndLockStatus fstatus Public.Unlocked)) === responseJsonEither + + -- guest can join if guest link feature is enabled + checkFeatureStatus Public.TeamFeatureEnabled + getJoinCodeConv eve (conversationKey cCode) (conversationCode cCode) !!! do const (Right (ConversationCoverView convId (Just convName))) === responseJsonEither const 200 === statusCode - - -- fails if disabled - let tfStatus = Public.TeamFeatureStatusNoConfig Public.TeamFeatureDisabled - TeamFeatures.putTeamFeatureFlagWithGalley @'Public.TeamFeatureGuestLinks galley owner teamId tfStatus !!! do + postConvCodeCheck cCode !!! const 200 === statusCode + postJoinCodeConv eve cCode !!! const 200 === statusCode + -- non-team-members can join as well + postJoinCodeConv bob cCode !!! const 200 === statusCode + + -- disabled guest links feature + let disabled = Public.TeamFeatureStatusNoConfig Public.TeamFeatureDisabled + TeamFeatures.putTeamFeatureFlagWithGalley @'Public.TeamFeatureGuestLinks galley owner teamId disabled !!! do const 200 === statusCode - getJoinCodeConv userNotInTeam (conversationKey cCode) (conversationCode cCode) !!! do + -- guest can't join if guest link feature is disabled + eve' <- ephemeralUser + bob' <- randomUser + getJoinCodeConv eve' (conversationKey cCode) (conversationCode cCode) !!! do const 409 === statusCode + postConvCodeCheck cCode !!! const 404 === statusCode + postJoinCodeConv eve' cCode !!! const 409 === statusCode + -- non-team-members can't join either + postJoinCodeConv bob' cCode !!! const 409 === statusCode + -- check feature status is still disabled + checkFeatureStatus Public.TeamFeatureDisabled -- after re-enabling, the old link is still valid - let tfStatus' = Public.TeamFeatureStatusNoConfig Public.TeamFeatureEnabled - TeamFeatures.putTeamFeatureFlagWithGalley @'Public.TeamFeatureGuestLinks galley owner teamId tfStatus' !!! do + let enabled = Public.TeamFeatureStatusNoConfig Public.TeamFeatureEnabled + TeamFeatures.putTeamFeatureFlagWithGalley @'Public.TeamFeatureGuestLinks galley owner teamId enabled !!! do const 200 === statusCode - - getJoinCodeConv userNotInTeam (conversationKey cCode) (conversationCode cCode) !!! do + getJoinCodeConv eve' (conversationKey cCode) (conversationCode cCode) !!! do const (Right (ConversationCoverView convId (Just convName))) === responseJsonEither const 200 === statusCode + postConvCodeCheck cCode !!! const 200 === statusCode + postJoinCodeConv eve' cCode !!! const 200 === statusCode + postJoinCodeConv bob' cCode !!! const 200 === statusCode + checkFeatureStatus Public.TeamFeatureEnabled testJoinNonTeamConvGuestLinksDisabled :: TestM () testJoinNonTeamConvGuestLinksDisabled = do @@ -1464,7 +1490,7 @@ testAccessUpdateGuestRemoved = do (qUnqualified alice) defNewConv { newConvQualifiedUsers = [bob, charlie, dee], - newConvTeam = Just (ConvTeamInfo tid False) + newConvTeam = Just (ConvTeamInfo tid) } postO2OConv alice bob (Just "gossip1") + cnv1 <- responseJsonError =<< postO2OConv alice bob (Just "gossip1") postConv alice [bob, carl] (Just "gossip2") [] Nothing Nothing + cnv2 <- + responseJsonError =<< postConv alice [bob, carl] (Just "gossip2") [] Nothing Nothing + (Request -> Request) -> UserId -> [Qualified UserId] -> m ResponseLBS postConvHelper g zusr newUsers = do - let conv = NewConvUnmanaged $ NewConv [] newUsers (Just "gossip") (Set.fromList []) Nothing Nothing Nothing Nothing roleNameWireAdmin + let conv = NewConv [] newUsers (Just "gossip") (Set.fromList []) Nothing Nothing Nothing Nothing roleNameWireAdmin post $ g . path "/conversations" . zUser zusr . zConn "conn" . zType "access" . json conv postSelfConvOk :: TestM () @@ -1929,7 +1957,7 @@ postConvO2OFailWithSelf :: TestM () postConvO2OFailWithSelf = do g <- view tsGalley alice <- randomUser - let inv = NewConvUnmanaged (NewConv [alice] [] Nothing mempty Nothing Nothing Nothing Nothing roleNameWireAdmin) + let inv = NewConv [alice] [] Nothing mempty Nothing Nothing Nothing Nothing roleNameWireAdmin post (g . path "/conversations/one2one" . zUser alice . zConn "conn" . zType "access" . json inv) !!! do const 403 === statusCode const (Just "invalid-op") === fmap label . responseJsonUnsafe @@ -2537,6 +2565,26 @@ postMembersOk3 = do -- Fetch bob again getSelfMember bob conv !!! const 200 === statusCode +postMembersFailNoGuestAccess :: TestM () +postMembersFailNoGuestAccess = do + alice <- randomUser + bob <- randomUser + peter <- randomUser + eve <- ephemeralUser + connectUsers alice (list1 bob [peter]) + Right noGuestsAccess <- liftIO $ genAccessRolesV2 [TeamMemberAccessRole, NonTeamMemberAccessRole] [GuestAccessRole] + conv <- decodeConvId <$> postConv alice [bob, peter] (Just "gossip") [] (Just noGuestsAccess) Nothing + postMembers alice (singleton eve) conv !!! const 403 === statusCode + +generateGuestLinkFailIfNoNonTeamMemberOrNoGuestAccess :: TestM () +generateGuestLinkFailIfNoNonTeamMemberOrNoGuestAccess = do + alice <- randomUser + bob <- randomUser + connectUsers alice (singleton bob) + Right noGuestsAccess <- liftIO $ genAccessRolesV2 [TeamMemberAccessRole] [GuestAccessRole, NonTeamMemberAccessRole] + convId <- decodeConvId <$> postConv alice [bob] (Just "gossip") [CodeAccess] (Just noGuestsAccess) Nothing + postConvCode alice convId !!! const 403 === statusCode + postMembersFail :: TestM () postMembersFail = do alice <- randomUser diff --git a/services/galley/test/integration/API/Teams.hs b/services/galley/test/integration/API/Teams.hs index 6ee4dc27b6a..0cd2a8d9297 100644 --- a/services/galley/test/integration/API/Teams.hs +++ b/services/galley/test/integration/API/Teams.hs @@ -39,6 +39,7 @@ import Data.ByteString.Lazy (fromStrict) import Data.Csv (FromNamedRecord (..), decodeByName) import qualified Data.Currency as Currency import Data.Id +import Data.Json.Util hiding ((#)) import qualified Data.LegalHold as LH import Data.List1 import qualified Data.List1 as List1 @@ -118,7 +119,6 @@ tests s = test s "add team conversation (no role as argument)" testAddTeamConvLegacy, test s "add team conversation with role" testAddTeamConvWithRole, test s "add team conversation as partner (fail)" testAddTeamConvAsExternalPartner, - test s "add managed conversation through public endpoint (fail)" testAddManagedConv, -- Queue is emptied here to ensure that lingering events do not affect other tests test s "add team member to conversation without connection" (testAddTeamMemberToConv >> ensureQueueEmpty), test s "update conversation as member" (testUpdateTeamConv RoleMember roleNameWireAdmin), @@ -128,6 +128,7 @@ tests s = test s "delete binding team (owner has no passwd)" (testDeleteBindingTeam False), test s "delete team conversation" testDeleteTeamConv, test s "update team data" testUpdateTeam, + test s "update team data icon validation" testUpdateTeamIconValidation, test s "update team member" testUpdateTeamMember, test s "update team status" testUpdateTeamStatus, -- Queue is emptied here to ensure that lingering events do not affect other tests @@ -881,25 +882,6 @@ testAddTeamConvAsExternalPartner = do const 403 === statusCode const "operation-denied" === (Error.label . responseJsonUnsafeWithMsg "error label") -testAddManagedConv :: TestM () -testAddManagedConv = do - g <- view tsGalley - owner <- Util.randomUser - tid <- Util.createNonBindingTeam "foo" owner [] - let tinfo = ConvTeamInfo tid True - let conv = - NewConvManaged $ - NewConv [owner] [] (Just "blah") (Set.fromList []) Nothing (Just tinfo) Nothing Nothing roleNameWireAdmin - post - ( g - . path "/conversations" - . zUser owner - . zConn "conn" - . zType "access" - . json conv - ) - !!! const 400 === statusCode - testAddTeamMemberToConv :: TestM () testAddTeamMemberToConv = do personalUser <- Util.randomUser @@ -1195,6 +1177,35 @@ testDeleteTeamConv = do Util.assertNotConvMember u x postConvCodeCheck code !!! const 404 === statusCode +testUpdateTeamIconValidation :: TestM () +testUpdateTeamIconValidation = do + g <- view tsGalley + owner <- Util.randomUser + let p = Util.symmPermissions [DoNotUseDeprecatedDeleteConversation] + member <- newTeamMember' p <$> Util.randomUser + Util.connectUsers owner (list1 (member ^. userId) []) + tid <- Util.createNonBindingTeam "foo" owner [member] + let update payload expectedStatusCode = + put + ( g + . paths ["teams", toByteString' tid] + . zUser owner + . zConn "conn" + . json payload + ) + !!! const expectedStatusCode + === statusCode + let payloadWithInvalidIcon = object ["name" .= String "name", "icon" .= String "invalid"] + update payloadWithInvalidIcon 400 + let payloadWithValidIcon = + object + [ "name" .= String "name", + "icon" .= String "3-1-47de4580-ae51-4650-acbb-d10c028cb0ac" + ] + update payloadWithValidIcon 200 + let payloadSetIconToDefault = object ["icon" .= String "default"] + update payloadSetIconToDefault 200 + testUpdateTeam :: TestM () testUpdateTeam = do g <- view tsGalley @@ -1217,7 +1228,7 @@ testUpdateTeam = do let u = newTeamUpdateData & nameUpdate .~ (Just $ unsafeRange "bar") - & iconUpdate .~ (Just $ unsafeRange "xxx") + & iconUpdate .~ fromByteString "3-1-47de4580-ae51-4650-acbb-d10c028cb0ac" & iconKeyUpdate .~ (Just $ unsafeRange "yyy") WS.bracketR2 c owner (member ^. userId) $ \(wsOwner, wsMember) -> do put @@ -1624,7 +1635,12 @@ postCryptoBroadcastMessageJson = do ac2 <- randomClient alice (someLastPrekeys !! 4) -- Complete: Alice broadcasts a message to Bob,Charlie,Dan and herself let t = 1 # Second -- WS receive timeout - let msg = [(alice, ac2, "ciphertext0"), (bob, bc, "ciphertext1"), (charlie, cc, "ciphertext2"), (dan, dc, "ciphertext3")] + let msg = + [ (alice, ac2, toBase64Text "ciphertext0"), + (bob, bc, toBase64Text "ciphertext1"), + (charlie, cc, toBase64Text "ciphertext2"), + (dan, dc, toBase64Text "ciphertext3") + ] WS.bracketRN c [bob, charlie, dan] $ \[wsB, wsC, wsD] -> -- Alice's clients 1 and 2 listen to their own messages only WS.bracketR (c . queryItem "client" (toByteString' ac2)) alice $ \wsA2 -> @@ -1633,15 +1649,19 @@ postCryptoBroadcastMessageJson = do const 201 === statusCode assertMismatch [] [] [] -- Bob should get the broadcast (team member of alice) - void . liftIO $ WS.assertMatch t wsB (wsAssertOtr (q (selfConv bob)) (q alice) ac bc "ciphertext1") + void . liftIO $ + WS.assertMatch t wsB (wsAssertOtr (q (selfConv bob)) (q alice) ac bc (toBase64Text "ciphertext1")) -- Charlie should get the broadcast (contact of alice and user of teams feature) - void . liftIO $ WS.assertMatch t wsC (wsAssertOtr (q (selfConv charlie)) (q alice) ac cc "ciphertext2") + void . liftIO $ + WS.assertMatch t wsC (wsAssertOtr (q (selfConv charlie)) (q alice) ac cc (toBase64Text "ciphertext2")) -- Dan should get the broadcast (contact of alice and not user of teams feature) - void . liftIO $ WS.assertMatch t wsD (wsAssertOtr (q (selfConv dan)) (q alice) ac dc "ciphertext3") + void . liftIO $ + WS.assertMatch t wsD (wsAssertOtr (q (selfConv dan)) (q alice) ac dc (toBase64Text "ciphertext3")) -- Alice's first client should not get the broadcast - assertNoMsg wsA1 (wsAssertOtr (q (selfConv alice)) (q alice) ac ac "ciphertext0") + assertNoMsg wsA1 (wsAssertOtr (q (selfConv alice)) (q alice) ac ac (toBase64Text "ciphertext0")) -- Alice's second client should get the broadcast - void . liftIO $ WS.assertMatch t wsA2 (wsAssertOtr (q (selfConv alice)) (q alice) ac ac2 "ciphertext0") + void . liftIO $ + WS.assertMatch t wsA2 (wsAssertOtr (q (selfConv alice)) (q alice) ac ac2 (toBase64Text "ciphertext0")) postCryptoBroadcastMessageJsonFilteredTooLargeTeam :: TestM () postCryptoBroadcastMessageJsonFilteredTooLargeTeam = do @@ -1672,7 +1692,12 @@ postCryptoBroadcastMessageJsonFilteredTooLargeTeam = do ac2 <- randomClient alice (someLastPrekeys !! 4) -- Complete: Alice broadcasts a message to Bob,Charlie,Dan and herself let t = 1 # Second -- WS receive timeout - let msg = [(alice, ac2, "ciphertext0"), (bob, bc, "ciphertext1"), (charlie, cc, "ciphertext2"), (dan, dc, "ciphertext3")] + let msg = + [ (alice, ac2, toBase64Text "ciphertext0"), + (bob, bc, toBase64Text "ciphertext1"), + (charlie, cc, toBase64Text "ciphertext2"), + (dan, dc, toBase64Text "ciphertext3") + ] WS.bracketRN c [bob, charlie, dan] $ \[wsB, wsC, wsD] -> -- Alice's clients 1 and 2 listen to their own messages only WS.bracketR (c . queryItem "client" (toByteString' ac2)) alice $ \wsA2 -> @@ -1692,15 +1717,19 @@ postCryptoBroadcastMessageJsonFilteredTooLargeTeam = do const 201 === statusCode assertMismatch [] [] [] -- Bob should get the broadcast (team member of alice) - void . liftIO $ WS.assertMatch t wsB (wsAssertOtr (q (selfConv bob)) (q alice) ac bc "ciphertext1") + void . liftIO $ + WS.assertMatch t wsB (wsAssertOtr (q (selfConv bob)) (q alice) ac bc (toBase64Text "ciphertext1")) -- Charlie should get the broadcast (contact of alice and user of teams feature) - void . liftIO $ WS.assertMatch t wsC (wsAssertOtr (q (selfConv charlie)) (q alice) ac cc "ciphertext2") + void . liftIO $ + WS.assertMatch t wsC (wsAssertOtr (q (selfConv charlie)) (q alice) ac cc (toBase64Text "ciphertext2")) -- Dan should get the broadcast (contact of alice and not user of teams feature) - void . liftIO $ WS.assertMatch t wsD (wsAssertOtr (q (selfConv dan)) (q alice) ac dc "ciphertext3") + void . liftIO $ + WS.assertMatch t wsD (wsAssertOtr (q (selfConv dan)) (q alice) ac dc (toBase64Text "ciphertext3")) -- Alice's first client should not get the broadcast - assertNoMsg wsA1 (wsAssertOtr (q (selfConv alice)) (q alice) ac ac "ciphertext0") + assertNoMsg wsA1 (wsAssertOtr (q (selfConv alice)) (q alice) ac ac (toBase64Text "ciphertext0")) -- Alice's second client should get the broadcast - void . liftIO $ WS.assertMatch t wsA2 (wsAssertOtr (q (selfConv alice)) (q alice) ac ac2 "ciphertext0") + void . liftIO $ + WS.assertMatch t wsA2 (wsAssertOtr (q (selfConv alice)) (q alice) ac ac2 (toBase64Text "ciphertext0")) postCryptoBroadcastMessageJsonReportMissingBody :: TestM () postCryptoBroadcastMessageJsonReportMissingBody = do @@ -1736,38 +1765,47 @@ postCryptoBroadcastMessageJson2 = do connectUsers alice (list1 charlie []) let t = 3 # Second -- WS receive timeout -- Missing charlie - let m1 = [(bob, bc, "ciphertext1")] + let m1 = [(bob, bc, toBase64Text "ciphertext1")] Util.postOtrBroadcastMessage id alice ac m1 !!! do const 412 === statusCode assertMismatchWithMessage (Just "1: Only Charlie and his device") [(charlie, Set.singleton cc)] [] [] -- Complete WS.bracketR2 c bob charlie $ \(wsB, wsE) -> do - let m2 = [(bob, bc, "ciphertext2"), (charlie, cc, "ciphertext2")] + let m2 = [(bob, bc, toBase64Text "ciphertext2"), (charlie, cc, toBase64Text "ciphertext2")] Util.postOtrBroadcastMessage id alice ac m2 !!! do const 201 === statusCode assertMismatchWithMessage (Just "No devices expected") [] [] [] - void . liftIO $ WS.assertMatch t wsB (wsAssertOtr (q (selfConv bob)) (q alice) ac bc "ciphertext2") - void . liftIO $ WS.assertMatch t wsE (wsAssertOtr (q (selfConv charlie)) (q alice) ac cc "ciphertext2") + void . liftIO $ + WS.assertMatch t wsB (wsAssertOtr (q (selfConv bob)) (q alice) ac bc (toBase64Text "ciphertext2")) + void . liftIO $ + WS.assertMatch t wsE (wsAssertOtr (q (selfConv charlie)) (q alice) ac cc (toBase64Text "ciphertext2")) -- Redundant self WS.bracketR3 c alice bob charlie $ \(wsA, wsB, wsE) -> do - let m3 = [(alice, ac, "ciphertext3"), (bob, bc, "ciphertext3"), (charlie, cc, "ciphertext3")] + let m3 = + [ (alice, ac, toBase64Text "ciphertext3"), + (bob, bc, toBase64Text "ciphertext3"), + (charlie, cc, toBase64Text "ciphertext3") + ] Util.postOtrBroadcastMessage id alice ac m3 !!! do const 201 === statusCode assertMismatchWithMessage (Just "2: Only Alice and her device") [] [(alice, Set.singleton ac)] [] - void . liftIO $ WS.assertMatch t wsB (wsAssertOtr (q (selfConv bob)) (q alice) ac bc "ciphertext3") - void . liftIO $ WS.assertMatch t wsE (wsAssertOtr (q (selfConv charlie)) (q alice) ac cc "ciphertext3") + void . liftIO $ + WS.assertMatch t wsB (wsAssertOtr (q (selfConv bob)) (q alice) ac bc (toBase64Text "ciphertext3")) + void . liftIO $ + WS.assertMatch t wsE (wsAssertOtr (q (selfConv charlie)) (q alice) ac cc (toBase64Text "ciphertext3")) -- Alice should not get it - assertNoMsg wsA (wsAssertOtr (q (selfConv alice)) (q alice) ac ac "ciphertext3") + assertNoMsg wsA (wsAssertOtr (q (selfConv alice)) (q alice) ac ac (toBase64Text "ciphertext3")) -- Deleted charlie WS.bracketR2 c bob charlie $ \(wsB, wsE) -> do deleteClient charlie cc (Just defPassword) !!! const 200 === statusCode - let m4 = [(bob, bc, "ciphertext4"), (charlie, cc, "ciphertext4")] + let m4 = [(bob, bc, toBase64Text "ciphertext4"), (charlie, cc, toBase64Text "ciphertext4")] Util.postOtrBroadcastMessage id alice ac m4 !!! do const 201 === statusCode assertMismatchWithMessage (Just "3: Only Charlie and his device") [] [] [(charlie, Set.singleton cc)] - void . liftIO $ WS.assertMatch t wsB (wsAssertOtr (q (selfConv bob)) (q alice) ac bc "ciphertext4") + void . liftIO $ + WS.assertMatch t wsB (wsAssertOtr (q (selfConv bob)) (q alice) ac bc (toBase64Text "ciphertext4")) -- charlie should not get it - assertNoMsg wsE (wsAssertOtr (q (selfConv charlie)) (q alice) ac cc "ciphertext4") + assertNoMsg wsE (wsAssertOtr (q (selfConv charlie)) (q alice) ac cc (toBase64Text "ciphertext4")) postCryptoBroadcastMessageProto :: TestM () postCryptoBroadcastMessageProto = do @@ -1791,18 +1829,18 @@ postCryptoBroadcastMessageProto = do connectUsers alice (list1 charlie [dan]) -- Complete: Alice broadcasts a message to Bob,Charlie,Dan let t = 1 # Second -- WS receive timeout - let ciphertext = encodeCiphertext "hello bob" + let ciphertext = toBase64Text "hello bob" WS.bracketRN c [alice, bob, charlie, dan] $ \ws@[_, wsB, wsC, wsD] -> do let msg = otrRecipients [(bob, [(bc, ciphertext)]), (charlie, [(cc, ciphertext)]), (dan, [(dc, ciphertext)])] Util.postProtoOtrBroadcast alice ac msg !!! do const 201 === statusCode assertMismatch [] [] [] -- Bob should get the broadcast (team member of alice) - void . liftIO $ WS.assertMatch t wsB (wsAssertOtr' (encodeCiphertext "data") (q (selfConv bob)) (q alice) ac bc ciphertext) + void . liftIO $ WS.assertMatch t wsB (wsAssertOtr' (toBase64Text "data") (q (selfConv bob)) (q alice) ac bc ciphertext) -- Charlie should get the broadcast (contact of alice and user of teams feature) - void . liftIO $ WS.assertMatch t wsC (wsAssertOtr' (encodeCiphertext "data") (q (selfConv charlie)) (q alice) ac cc ciphertext) + void . liftIO $ WS.assertMatch t wsC (wsAssertOtr' (toBase64Text "data") (q (selfConv charlie)) (q alice) ac cc ciphertext) -- Dan should get the broadcast (contact of alice and not user of teams feature) - void . liftIO $ WS.assertMatch t wsD (wsAssertOtr' (encodeCiphertext "data") (q (selfConv dan)) (q alice) ac dc ciphertext) + void . liftIO $ WS.assertMatch t wsD (wsAssertOtr' (toBase64Text "data") (q (selfConv dan)) (q alice) ac dc ciphertext) -- Alice should not get her own broadcast WS.assertNoEvent timeout ws let inbody = Just [bob] -- body triggers report @@ -1816,7 +1854,7 @@ postCryptoBroadcastMessageNoTeam = do (alice, ac) <- randomUserWithClient (someLastPrekeys !! 0) (bob, bc) <- randomUserWithClient (someLastPrekeys !! 1) connectUsers alice (list1 bob []) - let msg = [(bob, bc, "ciphertext1")] + let msg = [(bob, bc, toBase64Text "ciphertext1")] Util.postOtrBroadcastMessage id alice ac msg !!! const 404 === statusCode postCryptoBroadcastMessage100OrMaxConns :: TestM () @@ -1831,16 +1869,17 @@ postCryptoBroadcastMessage100OrMaxConns = do connectUsers alice (list1 bob (fst <$> others)) let t = 3 # Second -- WS receive timeout WS.bracketRN c (bob : (fst <$> others)) $ \ws -> do - let f (u, clt) = (u, clt, "ciphertext") - let msg = (bob, bc, "ciphertext") : (f <$> others) + let f (u, clt) = (u, clt, toBase64Text "ciphertext") + let msg = (bob, bc, toBase64Text "ciphertext") : (f <$> others) Util.postOtrBroadcastMessage id alice ac msg !!! do const 201 === statusCode assertMismatch [] [] [] let qbobself = Qualified (selfConv bob) localDomain - void . liftIO $ WS.assertMatch t (Imports.head ws) (wsAssertOtr qbobself qalice ac bc "ciphertext") + void . liftIO $ + WS.assertMatch t (Imports.head ws) (wsAssertOtr qbobself qalice ac bc (toBase64Text "ciphertext")) for_ (zip (tail ws) others) $ \(wsU, (u, clt)) -> do let qself = Qualified (selfConv u) localDomain - liftIO $ WS.assertMatch t wsU (wsAssertOtr qself qalice ac clt "ciphertext") + liftIO $ WS.assertMatch t wsU (wsAssertOtr qself qalice ac clt (toBase64Text "ciphertext")) where createAndConnectUserWhileLimitNotReached alice remaining acc pk = do (uid, cid) <- randomUserWithClient pk diff --git a/services/galley/test/integration/API/Teams/Feature.hs b/services/galley/test/integration/API/Teams/Feature.hs index 7bd706536b6..700ecd965ed 100644 --- a/services/galley/test/integration/API/Teams/Feature.hs +++ b/services/galley/test/integration/API/Teams/Feature.hs @@ -62,7 +62,7 @@ tests s = test s "LegalHold" testLegalHold, test s "SearchVisibility" testSearchVisibility, test s "DigitalSignatures" $ testSimpleFlag @'Public.TeamFeatureDigitalSignatures Public.TeamFeatureDisabled, - test s "ValidateSAMLEmails" $ testSimpleFlag @'Public.TeamFeatureValidateSAMLEmails Public.TeamFeatureDisabled, + test s "ValidateSAMLEmails" $ testSimpleFlag @'Public.TeamFeatureValidateSAMLEmails Public.TeamFeatureEnabled, test s "FileSharing with lock status" $ testSimpleFlagWithLockStatus @'Public.TeamFeatureFileSharing Public.TeamFeatureEnabled Public.Unlocked, test s "Classified Domains (enabled)" testClassifiedDomainsEnabled, test s "Classified Domains (disabled)" testClassifiedDomainsDisabled, @@ -72,7 +72,8 @@ tests s = test s "SelfDeletingMessages" testSelfDeletingMessages, test s "ConversationGuestLinks - public API" testGuestLinksPublic, test s "ConversationGuestLinks - internal API" testGuestLinksInternal, - test s "ConversationGuestLinks - lock status" $ testSimpleFlagWithLockStatus @'Public.TeamFeatureGuestLinks Public.TeamFeatureEnabled Public.Unlocked + test s "ConversationGuestLinks - lock status" $ testSimpleFlagWithLockStatus @'Public.TeamFeatureGuestLinks Public.TeamFeatureEnabled Public.Unlocked, + test s "SndFactorPasswordChallenge - lock status" $ testSimpleFlagWithLockStatus @'Public.TeamFeatureSndFactorPasswordChallenge Public.TeamFeatureDisabled Public.Unlocked ] testSSO :: TestM () @@ -677,7 +678,10 @@ testAllFeatures = do toS TeamFeatureGuestLinks .= Public.TeamFeatureStatusNoConfigAndLockStatus TeamFeatureEnabled - Public.Unlocked + Public.Unlocked, + toS TeamFeatureValidateSAMLEmails .= Public.TeamFeatureStatusNoConfig TeamFeatureEnabled, + toS TeamFeatureGuestLinks .= Public.TeamFeatureStatusNoConfigAndLockStatus TeamFeatureEnabled Public.Unlocked, + toS TeamFeatureSndFactorPasswordChallenge .= Public.TeamFeatureStatusNoConfigAndLockStatus TeamFeatureDisabled Public.Unlocked ] toS :: TeamFeatureName -> Text toS = TE.decodeUtf8 . toByteString' diff --git a/services/galley/test/integration/API/Teams/LegalHold.hs b/services/galley/test/integration/API/Teams/LegalHold.hs index 40ff822f854..22c7e9398cf 100644 --- a/services/galley/test/integration/API/Teams/LegalHold.hs +++ b/services/galley/test/integration/API/Teams/LegalHold.hs @@ -712,6 +712,9 @@ testInWhitelist = do "requestLegalHoldDevice when already pending should leave status as Pending" UserLegalHoldPending userStatus + do + -- owner cannot approve legalhold device + withLHWhitelist tid (approveLegalHoldDevice' g (Just defPassword) owner member tid) !!! testResponse 403 (Just "access-denied") do -- approve works withLHWhitelist tid (approveLegalHoldDevice' g (Just defPassword) member member tid) !!! testResponse 200 Nothing @@ -761,8 +764,6 @@ testOldClientsBlockDeviceHandshake = do putLHWhitelistTeam tid !!! const 200 === statusCode withDummyTestServiceForTeam legalholder tid $ \_chan -> do - putLHWhitelistTeam tid !!! const 200 === statusCode - legalholderLHDevice <- doEnableLH legalholder legalholder _legalholder2LHDevice <- doEnableLH legalholder legalholder2 @@ -816,6 +817,8 @@ testNoConsentBlockOne2OneConv :: HasCallStack => Bool -> Bool -> Bool -> Bool -> testNoConsentBlockOne2OneConv connectFirst teamPeer approveLH testPendingConnection = do -- FUTUREWORK: maybe regular user for legalholder? (legalholder :: UserId, tid) <- createBindingTeam + regularClient <- randomClient legalholder (head someLastPrekeys) + peer :: UserId <- if teamPeer then fst <$> createBindingTeam else randomUser galley <- view tsGalley @@ -850,8 +853,12 @@ testNoConsentBlockOne2OneConv connectFirst teamPeer approveLH testPendingConnect cannon <- view tsCannon WS.bracketR2 cannon legalholder peer $ \(legalholderWs, peerWs) -> withDummyTestServiceForTeam legalholder tid $ \_chan -> do - if connectFirst + if not connectFirst then do + void doEnableLH + postConnection legalholder peer !!! do testResponse 403 (Just "missing-legalhold-consent") + postConnection peer legalholder !!! do testResponse 403 (Just "missing-legalhold-consent") + else do postConnection legalholder peer !!! const 201 === statusCode mbConn :: Maybe UserConnection <- @@ -890,7 +897,8 @@ testNoConsentBlockOne2OneConv connectFirst teamPeer approveLH testPendingConnect peer peerClient (qUnqualified convId) - [ (legalholder, legalholderLHDevice, "cipher") + [ (legalholder, legalholderLHDevice, "cipher"), + (legalholder, regularClient, "cipher") ] !!! do const 404 === statusCode @@ -923,14 +931,16 @@ testNoConsentBlockOne2OneConv connectFirst teamPeer approveLH testPendingConnect peer peerClient (qUnqualified convId) - [ (legalholder, legalholderLHDevice, "cipher") + [ (legalholder, legalholderLHDevice, "cipher"), + (legalholder, regularClient, "cipher") ] !!! do const 201 === statusCode - else do - void doEnableLH - postConnection legalholder peer !!! do testResponse 403 (Just "missing-legalhold-consent") - postConnection peer legalholder !!! do testResponse 403 (Just "missing-legalhold-consent") + assertMismatchWithMessage + (Just "legalholderLHDevice is deleted") + [] + [] + [(legalholder, Set.singleton legalholderLHDevice)] data GroupConvAdmin = LegalholderIsAdmin diff --git a/services/galley/test/integration/API/Util.hs b/services/galley/test/integration/API/Util.hs index bc920d97035..d18f5d28365 100644 --- a/services/galley/test/integration/API/Util.hs +++ b/services/galley/test/integration/API/Util.hs @@ -35,7 +35,6 @@ import Control.Retry (constantDelay, exponentialBackoff, limitRetries, retrying) import Data.Aeson hiding (json) import Data.Aeson.Lens (key, _String) import qualified Data.ByteString as BS -import qualified Data.ByteString.Base64 as B64 import qualified Data.ByteString.Char8 as C import Data.ByteString.Conversion import qualified Data.ByteString.Lazy as Lazy @@ -61,7 +60,6 @@ import Data.Range import Data.Serialize (runPut) import qualified Data.Set as Set import Data.String.Conversions (ST, cs) -import Data.Text.Encoding (decodeUtf8) import qualified Data.Text.Encoding as Text import qualified Data.Text.Lazy.Encoding as T import Data.Time (getCurrentTime) @@ -483,7 +481,7 @@ getInvitationCode t ref = do createTeamConvLegacy :: HasCallStack => UserId -> TeamId -> [UserId] -> Maybe Text -> TestM ConvId createTeamConvLegacy u tid us name = do g <- view tsGalley - let tinfo = ConvTeamInfo tid False + let tinfo = ConvTeamInfo tid let convPayload = object [ "users" .= us, @@ -514,10 +512,9 @@ createTeamConvAccess u tid us name acc role mtimer convRole = do createTeamConvAccessRaw :: UserId -> TeamId -> [UserId] -> Maybe Text -> Maybe (Set Access) -> Maybe (Set AccessRoleV2) -> Maybe Milliseconds -> Maybe RoleName -> TestM ResponseLBS createTeamConvAccessRaw u tid us name acc role mtimer convRole = do g <- view tsGalley - let tinfo = ConvTeamInfo tid False + let tinfo = ConvTeamInfo tid let conv = - NewConvUnmanaged $ - NewConv us [] name (fromMaybe (Set.fromList []) acc) role (Just tinfo) mtimer Nothing (fromMaybe roleNameWireAdmin convRole) + NewConv us [] name (fromMaybe (Set.fromList []) acc) role (Just tinfo) mtimer Nothing (fromMaybe roleNameWireAdmin convRole) post ( g . path "/conversations" @@ -543,8 +540,7 @@ createOne2OneTeamConv :: UserId -> UserId -> Maybe Text -> TeamId -> TestM Respo createOne2OneTeamConv u1 u2 n tid = do g <- view tsGalley let conv = - NewConvUnmanaged $ - NewConv [u2] [] n mempty Nothing (Just $ ConvTeamInfo tid False) Nothing Nothing roleNameWireAdmin + NewConv [u2] [] n mempty Nothing (Just $ ConvTeamInfo tid) Nothing Nothing roleNameWireAdmin post $ g . path "/conversations/one2one" . zUser u1 . zConn "conn" . zType "access" . json conv postConv :: UserId -> [UserId] -> Maybe Text -> [Access] -> Maybe (Set AccessRoleV2) -> Maybe Milliseconds -> TestM ResponseLBS @@ -566,7 +562,7 @@ postConvQualified u n = do . zUser u . zConn "conn" . zType "access" - . json (NewConvUnmanaged n) + . json n postConvWithRemoteUsers :: HasCallStack => @@ -586,7 +582,7 @@ postConvWithRemoteUsers u n = postTeamConv :: TeamId -> UserId -> [UserId] -> Maybe Text -> [Access] -> Maybe (Set AccessRoleV2) -> Maybe Milliseconds -> TestM ResponseLBS postTeamConv tid u us name a r mtimer = do g <- view tsGalley - let conv = NewConvUnmanaged $ NewConv us [] name (Set.fromList a) r (Just (ConvTeamInfo tid False)) mtimer Nothing roleNameWireAdmin + let conv = NewConv us [] name (Set.fromList a) r (Just (ConvTeamInfo tid)) mtimer Nothing roleNameWireAdmin post $ g . path "/conversations" . zUser u . zConn "conn" . zType "access" . json conv deleteTeamConv :: (HasGalley m, MonadIO m, MonadHttp m) => TeamId -> ConvId -> UserId -> m ResponseLBS @@ -615,7 +611,7 @@ postConvWithRole u members name access arole timer role = postConvWithReceipt :: UserId -> [UserId] -> Maybe Text -> [Access] -> Maybe (Set AccessRoleV2) -> Maybe Milliseconds -> ReceiptMode -> TestM ResponseLBS postConvWithReceipt u us name a r mtimer rcpt = do g <- view tsGalley - let conv = NewConvUnmanaged $ NewConv us [] name (Set.fromList a) r Nothing mtimer (Just rcpt) roleNameWireAdmin + let conv = NewConv us [] name (Set.fromList a) r Nothing mtimer (Just rcpt) roleNameWireAdmin post $ g . path "/conversations" . zUser u . zConn "conn" . zType "access" . json conv postSelfConv :: UserId -> TestM ResponseLBS @@ -626,7 +622,7 @@ postSelfConv u = do postO2OConv :: UserId -> UserId -> Maybe Text -> TestM ResponseLBS postO2OConv u1 u2 n = do g <- view tsGalley - let conv = NewConvUnmanaged $ NewConv [u2] [] n mempty Nothing Nothing Nothing Nothing roleNameWireAdmin + let conv = NewConv [u2] [] n mempty Nothing Nothing Nothing Nothing roleNameWireAdmin post $ g . path "/conversations/one2one" . zUser u1 . zConn "conn" . zType "access" . json conv postConnectConv :: UserId -> UserId -> Text -> Text -> Maybe Text -> TestM ResponseLBS @@ -1394,10 +1390,27 @@ assertConvQualifiedWithRole r t c s us n mt role = do _ -> return () return cId -wsAssertOtr :: Qualified ConvId -> Qualified UserId -> ClientId -> ClientId -> Text -> Notification -> IO () +wsAssertOtr :: + HasCallStack => + Qualified ConvId -> + Qualified UserId -> + ClientId -> + ClientId -> + Text -> + Notification -> + IO () wsAssertOtr = wsAssertOtr' "data" -wsAssertOtr' :: Text -> Qualified ConvId -> Qualified UserId -> ClientId -> ClientId -> Text -> Notification -> IO () +wsAssertOtr' :: + HasCallStack => + Text -> + Qualified ConvId -> + Qualified UserId -> + ClientId -> + ClientId -> + Text -> + Notification -> + IO () wsAssertOtr' evData conv usr from to txt n = do let e = List1.head (WS.unpackPayload n) ntfTransient n @?= False @@ -1956,9 +1969,6 @@ otrRecipients = OtrRecipients . UserClientMap . buildMap where buildMap = fmap Map.fromList . Map.fromList -encodeCiphertext :: ByteString -> Text -encodeCiphertext = decodeUtf8 . B64.encode - genRandom :: (Q.Arbitrary a, MonadIO m) => m a genRandom = liftIO . Q.generate $ Q.arbitrary diff --git a/services/galley/test/unit/Test/Galley/API/Message.hs b/services/galley/test/unit/Test/Galley/API/Message.hs index f5aec8a2304..6dae3d08b34 100644 --- a/services/galley/test/unit/Test/Galley/API/Message.hs +++ b/services/galley/test/unit/Test/Galley/API/Message.hs @@ -17,10 +17,12 @@ module Test.Galley.API.Message where +import Control.Lens import Data.Domain (Domain) import Data.Id (ClientId, UserId) import qualified Data.Map as Map import qualified Data.Set as Set +import Data.Set.Lens import Galley.API.Message import Imports import Test.Tasty @@ -41,6 +43,13 @@ tests = ] ] +flatten :: Map Domain (Map UserId (Set ClientId)) -> Set (Domain, UserId, ClientId) +flatten = + setOf $ + (itraversed <.> itraversed <. folded) + . withIndex + . to (\((d, u), c) -> (d, u, c)) + type QualifiedUserClient = (Domain, UserId, ClientId) recipientSetToMap :: Set QualifiedUserClient -> Map (Domain, UserId) (Set ClientId) diff --git a/services/gundeck/src/Gundeck/Push/Native.hs b/services/gundeck/src/Gundeck/Push/Native.hs index d0baae81b5b..6a97efd6e15 100644 --- a/services/gundeck/src/Gundeck/Push/Native.hs +++ b/services/gundeck/src/Gundeck/Push/Native.hs @@ -24,7 +24,6 @@ where import Control.Lens (view, (.~), (^.)) import Control.Monad.Catch -import Data.Aeson (encode) import Data.ByteString.Conversion.To import Data.Id import Data.List1 @@ -125,8 +124,6 @@ publish m a = flip catches pushException $ do ~~ field "arn" (toText (a ^. addrEndpoint)) ~~ field "notificationId" (toText (npNotificationid m)) ~~ field "prio" (show (npPriority m)) - ~~ field "apsData" (encode (npApsData m)) - ~~ field "payload" (show txt) ~~ Log.msg (val "Native push") case txt of Left f -> return $! Failure f a diff --git a/services/gundeck/src/Gundeck/Push/Websocket.hs b/services/gundeck/src/Gundeck/Push/Websocket.hs index 86671d0a0c8..03716d0dbfa 100644 --- a/services/gundeck/src/Gundeck/Push/Websocket.hs +++ b/services/gundeck/src/Gundeck/Push/Websocket.hs @@ -151,7 +151,8 @@ bulkSend :: MonadMask m, HasRequestId m, MonadHttp m, - MonadUnliftIO m + MonadUnliftIO m, + Log.MonadLogger m ) => URI -> BulkPushRequest -> @@ -166,12 +167,21 @@ bulkSend' :: MonadMask m, HasRequestId m, MonadHttp m, - MonadUnliftIO m + MonadUnliftIO m, + Log.MonadLogger m ) => URI -> BulkPushRequest -> m BulkPushResponse -bulkSend' uri (encode -> jsbody) = do +bulkSend' uri bulkPushRequest = do + forM_ (fromBulkPushRequest bulkPushRequest) $ \(notification, targets) -> + Log.debug $ + Log.msg ("Bulk sending notification to Cannon." :: Text) + . Log.field "ntf_id" (show (ntfId notification)) + . Log.field "user_ids" (show (map ptUserId targets)) + . Log.field "conn_ids" (show (map ptConnId targets)) + + let jsbody = encode bulkPushRequest req <- check . method POST @@ -387,5 +397,5 @@ send n pp = logPresence :: Presence -> Log.Msg -> Log.Msg logPresence p = - Log.field "user" (toByteString (userId p)) - ~~ Log.field "zconn" (toByteString (connId p)) + Log.field "user_id" (toByteString (userId p)) + ~~ Log.field "conn_id" (toByteString (connId p)) diff --git a/services/gundeck/src/Gundeck/Run.hs b/services/gundeck/src/Gundeck/Run.hs index 02251b6d3fb..8c02e5a9db7 100644 --- a/services/gundeck/src/Gundeck/Run.hs +++ b/services/gundeck/src/Gundeck/Run.hs @@ -39,6 +39,7 @@ import Network.Wai.Utilities.Server hiding (serverPort) import qualified System.Logger as Log import qualified UnliftIO.Async as Async import Util.Options +import Wire.API.Routes.Version.Wai run :: Opts -> IO () run o = do @@ -64,6 +65,7 @@ run o = do . GZip.gunzip . GZip.gzip GZip.def . catchErrors (e ^. applog) [Right $ e ^. monitor] + . versionMiddleware app :: Env -> Wai.Application app e r k = runGundeck e r (route routes r k) routes = compile sitemap diff --git a/services/gundeck/test/integration/API.hs b/services/gundeck/test/integration/API.hs index 32e509661b7..4dad766a03b 100644 --- a/services/gundeck/test/integration/API.hs +++ b/services/gundeck/test/integration/API.hs @@ -76,6 +76,7 @@ tests s = test s "Replace presence" replacePresence, test s "Remove stale presence" removeStalePresence, test s "Single user push" singleUserPush, + test s "Single user push with large message" singleUserPushLargeMessage, test s "Push many to Cannon via bulkpush (via gundeck; group notif)" $ bulkPush False 50 8, test s "Push many to Cannon via bulkpush (via gundeck; e2e notif)" $ bulkPush True 50 8, test s "Send a push, ensure origin does not receive it" sendSingleUserNoPiggyback, @@ -195,7 +196,13 @@ removeStalePresence = do push u us = newPush u (toRecipients us) pload & pushOriginConnection .~ Just (ConnId "dev") singleUserPush :: TestM () -singleUserPush = do +singleUserPush = testSingleUserPush smallMsgPayload + where + -- JSON: {"foo":42} + smallMsgPayload = List1.singleton $ HashMap.fromList ["foo" .= (42 :: Int)] + +testSingleUserPush :: List1 Object -> TestM () +testSingleUserPush msgPayload = do ca <- view tsCannon uid <- randomId ch <- connectUser ca uid =<< randomConnId @@ -205,12 +212,18 @@ singleUserPush = do assertBool "No push message received" (isJust msg) assertEqual "Payload altered during transmission" - (Just pload) + (Just msgPayload) (ntfPayload <$> (decode . fromStrict . fromJust) msg) where - pload = List1.singleton $ HashMap.fromList ["foo" .= (42 :: Int)] - push u us = newPush (Just u) (toRecipients us) pload & pushOriginConnection .~ Just (ConnId "dev") + push u us = newPush (Just u) (toRecipients us) msgPayload & pushOriginConnection .~ Just (ConnId "dev") + +singleUserPushLargeMessage :: TestM () +singleUserPushLargeMessage = testSingleUserPush largeMsgPayload + where + -- JSON: {"list":["1","2", ... ,"10000"]} + largeMsgPayload = List1.singleton $ HashMap.fromList ["list" .= [show i | i <- [1 .. 10000] :: [Int]]] +-- | Create a number of users with a number of connections each, and connect each user's connections -- | Create a number of users with a number of connections each, and connect each user's connections -- to one of two cannons at random. Push either encrypted notifications (@isE2E == True@) or -- notifications from server (@isE2E == False@) to all connections, and make sure they all arrive at diff --git a/services/proxy/package.yaml b/services/proxy/package.yaml index 1ab643b678d..2bb1686209e 100644 --- a/services/proxy/package.yaml +++ b/services/proxy/package.yaml @@ -39,6 +39,7 @@ library: - wai-routing >=0.12 - wai-utilities >=0.14.3 - warp >=3.0 + - wire-api - unliftio-core executables: proxy: diff --git a/services/proxy/proxy.cabal b/services/proxy/proxy.cabal index 0aa5d2d426a..55ac7910011 100644 --- a/services/proxy/proxy.cabal +++ b/services/proxy/proxy.cabal @@ -4,7 +4,7 @@ cabal-version: 1.12 -- -- see: https://github.com/sol/hpack -- --- hash: 4b0eeea49373b98eff5aa94f2c7ec6e198b9db932ceeb3ff4311ffc25ab44b86 +-- hash: c31f9c4911b4326363bf2d4063f32f056fd40c97d528cbbec21cbe97ccdf1833 name: proxy version: 0.9.0 @@ -103,6 +103,7 @@ library , wai-routing >=0.12 , wai-utilities >=0.14.3 , warp >=3.0 + , wire-api default-language: Haskell2010 executable proxy diff --git a/services/proxy/src/Proxy/Run.hs b/services/proxy/src/Proxy/Run.hs index 7bd39b4fcc1..dab3700d5ba 100644 --- a/services/proxy/src/Proxy/Run.hs +++ b/services/proxy/src/Proxy/Run.hs @@ -31,6 +31,7 @@ import Proxy.API (sitemap) import Proxy.Env import Proxy.Options import Proxy.Proxy +import Wire.API.Routes.Version.Wai run :: Opts -> IO () run o = do @@ -42,4 +43,5 @@ run o = do let middleware = waiPrometheusMiddleware (sitemap e) . catchErrors (e ^. applog) [Right m] + . versionMiddleware runSettings s (middleware app) `finally` destroyEnv e diff --git a/services/spar/src/Spar/Run.hs b/services/spar/src/Spar/Run.hs index 3e956b73bfb..a1c2807c3d4 100644 --- a/services/spar/src/Spar/Run.hs +++ b/services/spar/src/Spar/Run.hs @@ -55,6 +55,7 @@ import Spar.Sem.Logger.TinyLog (toLevel) import System.Logger.Class (Logger) import qualified System.Logger.Extended as Log import Util.Options (casEndpoint, casFilterNodesByDatacentre, casKeyspace, epHost, epPort) +import Wire.API.Routes.Version.Wai import Wire.API.User.Saml as Types ---------------------------------------------------------------------- @@ -124,6 +125,7 @@ mkApp sparCtxOpts = do -- still here for errors outside the power of the 'Application', like network -- outages. . SAML.setHttpCachePolicy + . versionMiddleware . lookupRequestIdMiddleware $ \sparCtxRequestId -> app Env {..} heavyLogOnly :: (Wai.Request, LByteString) -> Maybe (Wai.Request, LByteString) diff --git a/services/spar/test-integration/Test/Spar/APISpec.hs b/services/spar/test-integration/Test/Spar/APISpec.hs index 1ddf267512f..7d129a03ceb 100644 --- a/services/spar/test-integration/Test/Spar/APISpec.hs +++ b/services/spar/test-integration/Test/Spar/APISpec.hs @@ -336,10 +336,14 @@ specFinalizeLogin = do let newUserRef@(UserRef _ subj) = either (error . show) (^. userRefL) $ parseFromDocument (fromSignedAuthnResponse newUserAuthnResp) + newUserId <- getUserIdViaRef newUserRef + + do + checkChangeRoleOfTeamMember teamid ownerid newUserId + -- remove user from team settings do env <- ask - newUserId <- getUserIdViaRef newUserRef _ <- call . get $ ( (env ^. teGalley) @@ -359,9 +363,8 @@ specFinalizeLogin = do liftIO $ threadDelay 100000 -- make sure deletion is done. if we don't want to take -- the time, we should find another way to robustly -- confirm that deletion has completed in the background. - - -- second login do + -- second login authnreq <- negotiateAuthnRequest idp authnresp <- runSimpleSP $ mkAuthnResponseWithSubj subj privcreds idp spmeta authnreq True loginSuccess =<< submitAuthnResponse teamid authnresp @@ -768,7 +771,7 @@ mkSsoOwner firstOwner tid idp privcreds = do authnresp <- runSimpleSP $ mkAuthnResponse privcreds idp spmeta authnreq True loginresp <- submitAuthnResponse tid authnresp liftIO $ responseStatus loginresp `shouldBe` status200 - [ssoOwner] <- filter (/= firstOwner) <$> getTeamMembers firstOwner tid + [ssoOwner] <- filter (/= firstOwner) <$> getTeamMemberIds firstOwner tid promoteTeamMember firstOwner tid ssoOwner pure ssoOwner diff --git a/services/spar/test-integration/Test/Spar/Scim/AuthSpec.hs b/services/spar/test-integration/Test/Spar/Scim/AuthSpec.hs index eb642d86d7a..f66ec2296a4 100644 --- a/services/spar/test-integration/Test/Spar/Scim/AuthSpec.hs +++ b/services/spar/test-integration/Test/Spar/Scim/AuthSpec.hs @@ -85,7 +85,8 @@ testCreateToken = do owner CreateScimToken { createScimTokenDescr = "testCreateToken", - createScimTokenPassword = Just defPassword + createScimTokenPassword = Just defPassword, + createScimTokenCode = Nothing } -- Try to do @GET /Users@ and check that it succeeds let fltr = filterBy "externalId" "67c196a0-cd0e-11ea-93c7-ef550ee48502" @@ -105,21 +106,24 @@ testTokenLimit = do owner CreateScimToken { createScimTokenDescr = "testTokenLimit / #1", - createScimTokenPassword = Just defPassword + createScimTokenPassword = Just defPassword, + createScimTokenCode = Nothing } _ <- createToken owner CreateScimToken { createScimTokenDescr = "testTokenLimit / #2", - createScimTokenPassword = Just defPassword + createScimTokenPassword = Just defPassword, + createScimTokenCode = Nothing } -- Try to create the third token and see that it fails createToken_ owner CreateScimToken { createScimTokenDescr = "testTokenLimit / #3", - createScimTokenPassword = Just defPassword + createScimTokenPassword = Just defPassword, + createScimTokenCode = Nothing } (env ^. teSpar) !!! checkErr 403 (Just "token-limit-reached") @@ -138,13 +142,13 @@ testNumIdPs = do SAML.SampleIdP metadata _ _ _ <- SAML.makeSampleIdPMetadata void $ call $ Util.callIdpCreate apiversion spar (Just owner) metadata - createToken owner (CreateScimToken "eins" (Just defPassword)) + createToken owner (CreateScimToken "eins" (Just defPassword) Nothing) >>= deleteToken owner . stiId . createScimTokenResponseInfo addSomeIdP - createToken owner (CreateScimToken "zwei" (Just defPassword)) + createToken owner (CreateScimToken "zwei" (Just defPassword) Nothing) >>= deleteToken owner . stiId . createScimTokenResponseInfo addSomeIdP - createToken_ owner (CreateScimToken "drei" (Just defPassword)) (env ^. teSpar) + createToken_ owner (CreateScimToken "drei" (Just defPassword) Nothing) (env ^. teSpar) !!! checkErr 400 (Just "more-than-one-idp") -- @SF.Provisioning @TSFI.RESTfulAPI @S2 @@ -168,7 +172,8 @@ testCreateTokenAuthorizesOnlyAdmins = do uid CreateScimToken { createScimTokenDescr = "testCreateToken", - createScimTokenPassword = Just defPassword + createScimTokenPassword = Just defPassword, + createScimTokenCode = Nothing } (env ^. teSpar) @@ -194,7 +199,8 @@ testCreateTokenRequiresPassword = do owner CreateScimToken { createScimTokenDescr = "testCreateTokenRequiresPassword", - createScimTokenPassword = Nothing + createScimTokenPassword = Nothing, + createScimTokenCode = Nothing } (env ^. teSpar) !!! checkErr 403 (Just "access-denied") @@ -203,7 +209,8 @@ testCreateTokenRequiresPassword = do owner CreateScimToken { createScimTokenDescr = "testCreateTokenRequiresPassword", - createScimTokenPassword = Just (PlainTextPassword "wrong password") + createScimTokenPassword = Just (PlainTextPassword "wrong password"), + createScimTokenCode = Nothing } (env ^. teSpar) !!! checkErr 403 (Just "access-denied") @@ -227,14 +234,16 @@ testListTokens = do owner CreateScimToken { createScimTokenDescr = "testListTokens / #1", - createScimTokenPassword = Just defPassword + createScimTokenPassword = Just defPassword, + createScimTokenCode = Nothing } _ <- createToken owner CreateScimToken { createScimTokenDescr = "testListTokens / #2", - createScimTokenPassword = Just defPassword + createScimTokenPassword = Just defPassword, + createScimTokenCode = Nothing } -- Check that the token is on the list list <- scimTokenListTokens <$> listTokens owner @@ -331,7 +340,8 @@ testDeletedTokensAreUnusable = do owner CreateScimToken { createScimTokenDescr = "testDeletedTokensAreUnusable", - createScimTokenPassword = Just defPassword + createScimTokenPassword = Just defPassword, + createScimTokenCode = Nothing } -- An operation with the token should succeed let fltr = filterBy "externalId" "67c196a0-cd0e-11ea-93c7-ef550ee48502" @@ -353,7 +363,8 @@ testDeletedTokensAreUnlistable = do owner CreateScimToken { createScimTokenDescr = "testDeletedTokensAreUnlistable", - createScimTokenPassword = Just defPassword + createScimTokenPassword = Just defPassword, + createScimTokenCode = Nothing } -- Delete the token deleteToken owner (stiId tokenInfo) diff --git a/services/spar/test-integration/Test/Spar/Scim/UserSpec.hs b/services/spar/test-integration/Test/Spar/Scim/UserSpec.hs index 3296cea35a5..a2efd0acfdd 100644 --- a/services/spar/test-integration/Test/Spar/Scim/UserSpec.hs +++ b/services/spar/test-integration/Test/Spar/Scim/UserSpec.hs @@ -76,6 +76,7 @@ import qualified Web.Scim.Schema.Meta as Scim import qualified Web.Scim.Schema.PatchOp as PatchOp import qualified Web.Scim.Schema.User as Scim.User import qualified Wire.API.Team.Export as CsvExport +import qualified Wire.API.Team.Feature as Feature import Wire.API.Team.Invitation (Invitation (..)) import Wire.API.User.IdentityProvider (IdP) import qualified Wire.API.User.IdentityProvider as User @@ -363,8 +364,10 @@ testCreateUserNoIdP = do -- members table contains an entry -- (this really shouldn't be tested here, but by the type system!) - members <- getTeamMembers userid tid + members <- getTeamMemberIds userid tid liftIO $ members `shouldContain` [userid] + + checkChangeRoleOfTeamMember tid owner userid where -- cloned from brig's integration tests @@ -439,9 +442,11 @@ testCreateUserWithSamlIdP = do -- members table contains an entry -- (this really shouldn't be tested here, but by the type system!) - members <- getTeamMembers userid tid + members <- getTeamMemberIds userid tid liftIO $ members `shouldContain` [userid] + checkChangeRoleOfTeamMember tid owner userid + -- | Test that Wire-specific schemas are added to the SCIM user record, even if the schemas -- were not present in the original record during creation. testSchemaIsAdded :: TestSpar () @@ -1691,7 +1696,9 @@ specEmailValidation = do setup :: HasCallStack => Bool -> TestSpar (UserId, Email) setup enabled = do (tok, (_ownerid, teamid, idp)) <- registerIdPAndScimToken - when enabled $ enableSamlEmailValidation teamid + if enabled + then setSamlEmailValidation teamid Feature.TeamFeatureEnabled + else setSamlEmailValidation teamid Feature.TeamFeatureDisabled (user, email) <- randomScimUserWithEmail scimStoredUser <- createUser tok user uref :: SAML.UserRef <- @@ -1754,7 +1761,7 @@ specSCIMManaged = do let brig = env ^. teBrig (tok, (_ownerid, teamid, idp, (_, privCreds))) <- registerIdPAndScimTokenWithMeta - enableSamlEmailValidation teamid + setSamlEmailValidation teamid Feature.TeamFeatureEnabled (user, oldEmail) <- randomScimUserWithEmail storedUser <- createUser tok user let uid :: UserId = Scim.id . Scim.thing $ storedUser diff --git a/services/spar/test-integration/Util/Core.hs b/services/spar/test-integration/Util/Core.hs index ad1f2de7df9..1ef07088d7e 100644 --- a/services/spar/test-integration/Util/Core.hs +++ b/services/spar/test-integration/Util/Core.hs @@ -63,6 +63,7 @@ module Util.Core createTeamMember, deleteUserOnBrig, getTeams, + getTeamMemberIds, getTeamMembers, promoteTeamMember, getSelfProfile, @@ -127,6 +128,8 @@ module Util.Core callDeleteDefaultSsoCode, checkErr, checkErrHspec, + updateTeamMemberRole, + checkChangeRoleOfTeamMember, ) where @@ -163,13 +166,14 @@ import Data.UUID.V4 as UUID (nextRandom) import qualified Data.Yaml as Yaml import GHC.TypeLits import qualified Galley.Types.Teams as Galley +import qualified Galley.Types.Teams as Teams import Imports hiding (head) import Network.HTTP.Client.MultipartFormData import qualified Network.Wai.Handler.Warp as Warp import qualified Network.Wai.Handler.Warp.Internal as Warp import qualified Options.Applicative as OPA import Polysemy (Sem) -import SAML2.WebSSO as SAML +import SAML2.WebSSO as SAML hiding ((<$$>)) import qualified SAML2.WebSSO.API.Example as SAML import SAML2.WebSSO.Test.Lenses (userRefL) import SAML2.WebSSO.Test.MockResponse @@ -200,6 +204,7 @@ import Wire.API.Team.Feature (TeamFeatureStatusValue (..)) import qualified Wire.API.Team.Feature as Public import qualified Wire.API.Team.Invitation as TeamInvitation import qualified Wire.API.Team.Member as Member +import qualified Wire.API.Team.Role as Role import Wire.API.User (HandleUpdate (HandleUpdate), UserUpdate) import qualified Wire.API.User as User import Wire.API.User.Identity (mkSampleUref) @@ -585,7 +590,10 @@ getTeams u gly = do ) return $ responseJsonUnsafe r -getTeamMembers :: HasCallStack => UserId -> TeamId -> TestSpar [UserId] +getTeamMemberIds :: HasCallStack => UserId -> TeamId -> TestSpar [UserId] +getTeamMemberIds usr tid = (^. Galley.userId) <$$> getTeamMembers usr tid + +getTeamMembers :: HasCallStack => UserId -> TeamId -> TestSpar [Member.TeamMember] getTeamMembers usr tid = do gly <- view teGalley resp <- @@ -594,7 +602,7 @@ getTeamMembers usr tid = do (mems ^. Galley.teamMembers) + pure $ mems ^. Galley.teamMembers promoteTeamMember :: HasCallStack => UserId -> TeamId -> UserId -> TestSpar () promoteTeamMember usr tid memid = do @@ -1302,3 +1310,21 @@ updateProfileBrig brig uid uupd = . contentJson . json uupd ) + +updateTeamMemberRole :: (MonadReader TestEnv m, MonadIO m) => TeamId -> UserId -> UserId -> Role.Role -> m () +updateTeamMemberRole tid adminUid targetUid role = do + spar <- asks (^. teGalley) + void . call . put $ + spar + . zUser adminUid + . zConn "user" + . paths ["teams", toByteString' tid, "members"] + . json (Member.mkNewTeamMember targetUid (Galley.rolePermissions role) Nothing) + . expect2xx + +-- https://wearezeta.atlassian.net/browse/SQSERVICES-1279: change role after successful creation/activation. +checkChangeRoleOfTeamMember :: TeamId -> UserId -> UserId -> TestSpar () +checkChangeRoleOfTeamMember tid adminId targetId = forM_ [minBound ..] $ \role -> do + updateTeamMemberRole tid adminId targetId role + [member'] <- filter ((== targetId) . (^. Member.userId)) <$> getTeamMembers adminId tid + liftIO $ (member' ^. Member.permissions . to Teams.permissionsRole) `shouldBe` Just role diff --git a/services/spar/test-integration/Util/Email.hs b/services/spar/test-integration/Util/Email.hs index 30bab91ed80..f3ebef1b101 100644 --- a/services/spar/test-integration/Util/Email.hs +++ b/services/spar/test-integration/Util/Email.hs @@ -54,7 +54,7 @@ changeEmailBrig brig usr newEmail = do changeEmailBrigCreds brig cky tok newEmail where emailLogin :: Email -> Misc.PlainTextPassword -> Maybe Auth.CookieLabel -> Auth.Login - emailLogin e = Auth.PasswordLogin (Auth.LoginByEmail e) + emailLogin e pw cl = Auth.PasswordLogin (Auth.LoginByEmail e) pw cl Nothing login :: Auth.Login -> Auth.CookieType -> (MonadIO m, MonadHttp m) => m ResponseLBS login l t = @@ -155,9 +155,9 @@ getActivationCode brig ep = do let acode = ActivationCode . Ascii.unsafeFromText <$> (lbs ^? key "code" . _String) return $ (,) <$> akey <*> acode -enableSamlEmailValidation :: HasCallStack => TeamId -> TestSpar () -enableSamlEmailValidation tid = do +setSamlEmailValidation :: HasCallStack => TeamId -> Feature.TeamFeatureStatusValue -> TestSpar () +setSamlEmailValidation tid status = do galley <- view teGalley - let req = put $ galley . paths p . json (Feature.TeamFeatureStatusNoConfig Feature.TeamFeatureEnabled) + let req = put $ galley . paths p . json (Feature.TeamFeatureStatusNoConfig status) p = ["/i/teams", toByteString' tid, "features", "validate-saml-emails"] call req !!! const 200 === statusCode diff --git a/services/spar/test/Arbitrary.hs b/services/spar/test/Arbitrary.hs index da0c37e79ac..6737abeb2da 100644 --- a/services/spar/test/Arbitrary.hs +++ b/services/spar/test/Arbitrary.hs @@ -60,9 +60,6 @@ instance Arbitrary ScimTokenInfo where <*> arbitrary <*> arbitrary -instance Arbitrary CreateScimToken where - arbitrary = CreateScimToken <$> arbitrary <*> arbitrary - instance Arbitrary CreateScimTokenResponse where arbitrary = CreateScimTokenResponse <$> arbitrary <*> arbitrary diff --git a/stack.yaml b/stack.yaml index 392e1c4906b..7432f78f6b3 100644 --- a/stack.yaml +++ b/stack.yaml @@ -77,6 +77,7 @@ nix: allow-newer: true extra-deps: +- bytestring-arbitrary-0.1.3@sha256:14db64d4fe126fbad2eb8d3601bfd80a693f3131e2db0e76891feffe44f10df8,1773 - git: https://github.com/fimad/prometheus-haskell commit: 2e3282e5fb27ba8d989c271a0a989823fad7ec43 subdirs: @@ -193,10 +194,10 @@ extra-deps: - git: https://github.com/haskell-servant/servant-swagger commit: bb0a84faa073fa9530f60337610d7da3d5b9393c -# For changes from https://github.com/haskell-servant/servant/pull/1420 +# For changes from https://github.com/haskell-servant/servant/pull/1502 # Not released to hackage yet -- git: https://github.com/wireapp/servant.git - commit: a4e15fe75f294816d9ead19ed8a48cae8e0b76e7 +- git: https://github.com/haskell-servant/servant.git + commit: 75db4a5327d6d04ae2460bd5ffd008fe16197ba8 subdirs: - servant - servant-server diff --git a/stack.yaml.lock b/stack.yaml.lock index 29788eece7e..b8831b6c919 100644 --- a/stack.yaml.lock +++ b/stack.yaml.lock @@ -4,6 +4,13 @@ # https://docs.haskellstack.org/en/stable/lock_files packages: +- completed: + hackage: bytestring-arbitrary-0.1.3@sha256:14db64d4fe126fbad2eb8d3601bfd80a693f3131e2db0e76891feffe44f10df8,1773 + pantry-tree: + size: 347 + sha256: ea68c43da070afaddd1758f3180140f4cb844f559d4663246757b27a72200092 + original: + hackage: bytestring-arbitrary-0.1.3@sha256:14db64d4fe126fbad2eb8d3601bfd80a693f3131e2db0e76891feffe44f10df8,1773 - completed: subdir: wai-middleware-prometheus name: wai-middleware-prometheus @@ -517,55 +524,55 @@ packages: - completed: subdir: servant name: servant - version: 0.18.2 - git: https://github.com/wireapp/servant.git + version: 0.18.3 + git: https://github.com/haskell-servant/servant.git pantry-tree: - size: 2809 - sha256: 22348fceac7bca97f5c349d9db0b157e401ed273d151d8cbcbd767f4d06791e8 - commit: a4e15fe75f294816d9ead19ed8a48cae8e0b76e7 + size: 2949 + sha256: 091d8a742ea95490b787f497bfa26eaed46733945721396158f571aea7ed6dca + commit: 75db4a5327d6d04ae2460bd5ffd008fe16197ba8 original: subdir: servant - git: https://github.com/wireapp/servant.git - commit: a4e15fe75f294816d9ead19ed8a48cae8e0b76e7 + git: https://github.com/haskell-servant/servant.git + commit: 75db4a5327d6d04ae2460bd5ffd008fe16197ba8 - completed: subdir: servant-server name: servant-server - version: 0.18.2 - git: https://github.com/wireapp/servant.git + version: 0.18.3 + git: https://github.com/haskell-servant/servant.git pantry-tree: size: 2727 - sha256: 55d3c9747550555f3861b5fabfe7cc0385c64ccaf3e5b051aa3064bddb8661ad - commit: a4e15fe75f294816d9ead19ed8a48cae8e0b76e7 + sha256: 43d23c42011f5c3ff3f298b1910d7f2b43a66144e92e2e762b0efffe63634af4 + commit: 75db4a5327d6d04ae2460bd5ffd008fe16197ba8 original: subdir: servant-server - git: https://github.com/wireapp/servant.git - commit: a4e15fe75f294816d9ead19ed8a48cae8e0b76e7 + git: https://github.com/haskell-servant/servant.git + commit: 75db4a5327d6d04ae2460bd5ffd008fe16197ba8 - completed: subdir: servant-client name: servant-client - version: 0.18.2 - git: https://github.com/wireapp/servant.git + version: 0.18.3 + git: https://github.com/haskell-servant/servant.git pantry-tree: - size: 1346 - sha256: 9245621a9097c0b4d5ecbd61616d00c69112e1539db8803a0fda010de484e7ba - commit: a4e15fe75f294816d9ead19ed8a48cae8e0b76e7 + size: 1481 + sha256: 86025a0c5ae0b0da07db48eed1456011ba7c0093f2dbc04b1ef3fe99e1cc0567 + commit: 75db4a5327d6d04ae2460bd5ffd008fe16197ba8 original: subdir: servant-client - git: https://github.com/wireapp/servant.git - commit: a4e15fe75f294816d9ead19ed8a48cae8e0b76e7 + git: https://github.com/haskell-servant/servant.git + commit: 75db4a5327d6d04ae2460bd5ffd008fe16197ba8 - completed: subdir: servant-client-core name: servant-client-core - version: 0.18.2 - git: https://github.com/wireapp/servant.git + version: 0.18.3 + git: https://github.com/haskell-servant/servant.git pantry-tree: - size: 1444 - sha256: b5a7abf78d2ee0887bf05d7d4ba71e3c689b65a9b2e7386c394f4bdb6ff8e55d - commit: a4e15fe75f294816d9ead19ed8a48cae8e0b76e7 + size: 1445 + sha256: 528c07a5fe7d7482636b9e11bbb54d92930a7db3d9635f920f250fed51a8a2fd + commit: 75db4a5327d6d04ae2460bd5ffd008fe16197ba8 original: subdir: servant-client-core - git: https://github.com/wireapp/servant.git - commit: a4e15fe75f294816d9ead19ed8a48cae8e0b76e7 + git: https://github.com/haskell-servant/servant.git + commit: 75db4a5327d6d04ae2460bd5ffd008fe16197ba8 - completed: hackage: HsOpenSSL-x509-system-0.1.0.4@sha256:86be72558de4cee8f4e32f9cb8b63610d7624219910cfc205a23326078658676,1777 pantry-tree: