diff --git a/Makefile b/Makefile index 9b44c21278..affc632be2 100644 --- a/Makefile +++ b/Makefile @@ -52,13 +52,9 @@ install: init .PHONY: full-clean full-clean: clean rm -rf ~/.cache/hie-bios -ifdef CABAL_DIR - rm -rf $(CABAL_DIR)/store -else - rm -rf ~/.cabal/store -endif rm -rf ./dist-newstyle ./.env direnv reload + @echo -e "\n\n*** NOTE: you may want to also 'rm -rf ~/.cabal/store \$$CABAL_DIR/store', not sure.\n" .PHONY: clean clean: diff --git a/changelog.d/0-release-notes/chart-compatibility b/changelog.d/0-release-notes/chart-compatibility new file mode 100644 index 0000000000..54bf8bf8d0 --- /dev/null +++ b/changelog.d/0-release-notes/chart-compatibility @@ -0,0 +1 @@ +wire-server helm charts using Ingress resources are now compatible with kubernetes versions 1.22, 1.23 and 1.24 (but keep being compatible with older versions of kubernetes). diff --git a/changelog.d/1-api-changes/get-conversation-v3-leak b/changelog.d/1-api-changes/get-conversation-v3-leak new file mode 100644 index 0000000000..8f5f313314 --- /dev/null +++ b/changelog.d/1-api-changes/get-conversation-v3-leak @@ -0,0 +1 @@ +The unqualified `GET /conversations/:id` endpoint has been removed from API v3, and is restored to the previous behaviour of returning a Conversation using the v2 schema. Similarly, its qualified counterpart `GET /conversations/:domain/:id` now returns a v2 Conversation when accessed through API v2. diff --git a/changelog.d/3-bug-fixes/update-inbucket-chart-dependency b/changelog.d/3-bug-fixes/update-inbucket-chart-dependency new file mode 100644 index 0000000000..63ae2c40a9 --- /dev/null +++ b/changelog.d/3-bug-fixes/update-inbucket-chart-dependency @@ -0,0 +1 @@ +Update `inbucket` (fake smtp server) chart dependency: The prior version relied on an image that has been removed from docker hub. Thus, our own `inbucket` chart could not be deployed anymore. diff --git a/changelog.d/4-docs/copybutton b/changelog.d/4-docs/copybutton new file mode 100644 index 0000000000..cef39536e5 --- /dev/null +++ b/changelog.d/4-docs/copybutton @@ -0,0 +1 @@ +Add sphinx-copybutton plugin to make copying snippets of code from docs.wire.com easier. diff --git a/changelog.d/4-docs/hook-fedcalls-into-api-docs b/changelog.d/4-docs/hook-fedcalls-into-api-docs new file mode 100644 index 0000000000..2a6d02a8a3 --- /dev/null +++ b/changelog.d/4-docs/hook-fedcalls-into-api-docs @@ -0,0 +1 @@ +Hook federated API call documentation into docs.wire.com (manually). diff --git a/changelog.d/5-internal/pr-2694 b/changelog.d/5-internal/pr-2694 new file mode 100644 index 0000000000..7bd6984179 --- /dev/null +++ b/changelog.d/5-internal/pr-2694 @@ -0,0 +1 @@ +Add two integration tests arounds last prekeys diff --git a/changelog.d/5-internal/sqservices-1848 b/changelog.d/5-internal/sqservices-1848 new file mode 100644 index 0000000000..ed89235a80 --- /dev/null +++ b/changelog.d/5-internal/sqservices-1848 @@ -0,0 +1 @@ +Fixed flaky team user search integration test diff --git a/charts/elasticsearch-ephemeral/templates/_helpers.tpl b/charts/elasticsearch-ephemeral/templates/_helpers.tpl index e49dfab7a6..2aa4295dc8 100644 --- a/charts/elasticsearch-ephemeral/templates/_helpers.tpl +++ b/charts/elasticsearch-ephemeral/templates/_helpers.tpl @@ -15,13 +15,3 @@ We truncate at 53 chars (63 - len("-discovery")) because some Kubernetes name fi {{- printf "%s" $name | trunc 53 | trimSuffix "-" -}} {{- end -}} -{{/* -Return the appropriate apiVersion for Curactor cron job. -*/}} -{{- define "curator.cronJob.apiVersion" -}} -{{- if ge .Capabilities.KubeVersion.Minor "8" -}} -"batch/v1beta1" -{{- else -}} -"batch/v2alpha1" -{{- end -}} -{{- end -}} diff --git a/charts/inbucket/requirements.yaml b/charts/inbucket/requirements.yaml index 2478ba341c..47adf720b7 100644 --- a/charts/inbucket/requirements.yaml +++ b/charts/inbucket/requirements.yaml @@ -1,4 +1,4 @@ dependencies: - name: inbucket - version: 2.0.1 + version: 2.1.0 repository: https://inbucket.github.io/inbucket-community diff --git a/charts/inbucket/templates/_helpers.tpl b/charts/inbucket/templates/_helpers.tpl new file mode 100644 index 0000000000..c0e9c95498 --- /dev/null +++ b/charts/inbucket/templates/_helpers.tpl @@ -0,0 +1,26 @@ +{{/* Allow KubeVersion to be overridden. */}} +{{- define "kubeVersion" -}} + {{- default .Capabilities.KubeVersion.Version .Values.kubeVersionOverride -}} +{{- end -}} + +{{/* Get Ingress API Version */}} +{{- define "ingress.apiVersion" -}} + {{- if and (.Capabilities.APIVersions.Has "networking.k8s.io/v1") (semverCompare ">= 1.19-0" (include "kubeVersion" .)) -}} + {{- print "networking.k8s.io/v1" -}} + {{- else if .Capabilities.APIVersions.Has "networking.k8s.io/v1beta1" -}} + {{- print "networking.k8s.io/v1beta1" -}} + {{- else -}} + {{- print "extensions/v1beta1" -}} + {{- end -}} +{{- end -}} + +{{/* Check Ingress stability */}} +{{- define "ingress.isStable" -}} + {{- eq (include "ingress.apiVersion" .) "networking.k8s.io/v1" -}} +{{- end -}} + +{{/* Check Ingress supports pathType */}} +{{/* pathType was added to networking.k8s.io/v1beta1 in Kubernetes 1.18 */}} +{{- define "ingress.supportsPathType" -}} + {{- or (eq (include "ingress.isStable" .) "true") (and (eq (include "ingress.apiVersion" .) "networking.k8s.io/v1beta1") (semverCompare ">= 1.18-0" (include "kubeVersion" .))) -}} +{{- end -}} diff --git a/charts/inbucket/templates/ingress.yaml b/charts/inbucket/templates/ingress.yaml index 7be2a320c2..c2803b4e00 100644 --- a/charts/inbucket/templates/ingress.yaml +++ b/charts/inbucket/templates/ingress.yaml @@ -1,4 +1,6 @@ -apiVersion: extensions/v1beta1 +{{- $apiIsStable := eq (include "ingress.isStable" .) "true" -}} +{{- $ingressSupportsPathType := eq (include "ingress.supportsPathType" .) "true" -}} +apiVersion: {{ include "ingress.apiVersion" . }} kind: Ingress metadata: name: "inbucket" @@ -14,6 +16,16 @@ spec: http: paths: - path: / + {{- if $ingressSupportsPathType }} + pathType: Prefix + {{- end }} backend: + {{- if $apiIsStable }} + service: + name: {{ include "inbucket.fullname" . }} + port: + name: http + {{- else }} serviceName: {{ include "inbucket.fullname" . }} servicePort: http + {{- end }} diff --git a/charts/ldap-scim-bridge/templates/_helpers.tpl b/charts/ldap-scim-bridge/templates/_helpers.tpl new file mode 100644 index 0000000000..c288d2067d --- /dev/null +++ b/charts/ldap-scim-bridge/templates/_helpers.tpl @@ -0,0 +1,13 @@ +{{/* Allow KubeVersion to be overridden. */}} +{{- define "kubeVersion" -}} + {{- default .Capabilities.KubeVersion.Version .Values.kubeVersionOverride -}} +{{- end -}} + +{{/* Get Batch API Version */}} +{{- define "batch.apiVersion" -}} + {{- if and (.Capabilities.APIVersions.Has "batch/v1") (semverCompare ">= 1.21-0" (include "kubeVersion" .)) -}} + {{- print "batch/v1" -}} + {{- else -}} + {{- print "batch/v1beta1" -}} + {{- end -}} +{{- end -}} diff --git a/charts/ldap-scim-bridge/templates/cronjob.yaml b/charts/ldap-scim-bridge/templates/cronjob.yaml index 365fa67eca..3b41131f6b 100644 --- a/charts/ldap-scim-bridge/templates/cronjob.yaml +++ b/charts/ldap-scim-bridge/templates/cronjob.yaml @@ -1,4 +1,4 @@ -apiVersion: batch/v1beta1 +apiVersion: {{ include "batch.apiVersion" . }} kind: CronJob metadata: name: {{ .Release.Name }} diff --git a/charts/legalhold/templates/_helpers.tpl b/charts/legalhold/templates/_helpers.tpl new file mode 100644 index 0000000000..c0e9c95498 --- /dev/null +++ b/charts/legalhold/templates/_helpers.tpl @@ -0,0 +1,26 @@ +{{/* Allow KubeVersion to be overridden. */}} +{{- define "kubeVersion" -}} + {{- default .Capabilities.KubeVersion.Version .Values.kubeVersionOverride -}} +{{- end -}} + +{{/* Get Ingress API Version */}} +{{- define "ingress.apiVersion" -}} + {{- if and (.Capabilities.APIVersions.Has "networking.k8s.io/v1") (semverCompare ">= 1.19-0" (include "kubeVersion" .)) -}} + {{- print "networking.k8s.io/v1" -}} + {{- else if .Capabilities.APIVersions.Has "networking.k8s.io/v1beta1" -}} + {{- print "networking.k8s.io/v1beta1" -}} + {{- else -}} + {{- print "extensions/v1beta1" -}} + {{- end -}} +{{- end -}} + +{{/* Check Ingress stability */}} +{{- define "ingress.isStable" -}} + {{- eq (include "ingress.apiVersion" .) "networking.k8s.io/v1" -}} +{{- end -}} + +{{/* Check Ingress supports pathType */}} +{{/* pathType was added to networking.k8s.io/v1beta1 in Kubernetes 1.18 */}} +{{- define "ingress.supportsPathType" -}} + {{- or (eq (include "ingress.isStable" .) "true") (and (eq (include "ingress.apiVersion" .) "networking.k8s.io/v1beta1") (semverCompare ">= 1.18-0" (include "kubeVersion" .))) -}} +{{- end -}} diff --git a/charts/legalhold/templates/ingress.yaml b/charts/legalhold/templates/ingress.yaml index c6ae51758e..24cfcd9813 100644 --- a/charts/legalhold/templates/ingress.yaml +++ b/charts/legalhold/templates/ingress.yaml @@ -1,4 +1,6 @@ -apiVersion: extensions/v1beta1 +{{- $apiIsStable := eq (include "ingress.isStable" .) "true" -}} +{{- $ingressSupportsPathType := eq (include "ingress.supportsPathType" .) "true" -}} +apiVersion: {{ include "ingress.apiVersion" . }} kind: Ingress metadata: name: hold @@ -16,6 +18,16 @@ spec: http: paths: - path: / + {{- if $ingressSupportsPathType }} + pathType: Prefix + {{- end }} backend: + {{- if $apiIsStable }} + service: + name: "{{ .Release.Name }}-hold" + port: + number: 8080 + {{- else }} serviceName: "{{ .Release.Name }}-hold" servicePort: 8080 + {{- end }} diff --git a/charts/nginx-ingress-services/templates/_helpers.tpl b/charts/nginx-ingress-services/templates/_helpers.tpl index e6480a5834..32f4467617 100644 --- a/charts/nginx-ingress-services/templates/_helpers.tpl +++ b/charts/nginx-ingress-services/templates/_helpers.tpl @@ -59,3 +59,30 @@ Returns the Letsencrypt API server URL based on whether testMode is enabled or d {{- $hostnameParts = append $hostnameParts "v02" -}} {{- join "-" $hostnameParts | printf "https://%s.api.letsencrypt.org/directory" -}} {{- end -}} + +{{/* Allow KubeVersion to be overridden. */}} +{{- define "kubeVersion" -}} + {{- default .Capabilities.KubeVersion.Version .Values.kubeVersionOverride -}} +{{- end -}} + +{{/* Get Ingress API Version */}} +{{- define "ingress.apiVersion" -}} + {{- if and (.Capabilities.APIVersions.Has "networking.k8s.io/v1") (semverCompare ">= 1.19-0" (include "kubeVersion" .)) -}} + {{- print "networking.k8s.io/v1" -}} + {{- else if .Capabilities.APIVersions.Has "networking.k8s.io/v1beta1" -}} + {{- print "networking.k8s.io/v1beta1" -}} + {{- else -}} + {{- print "extensions/v1beta1" -}} + {{- end -}} +{{- end -}} + +{{/* Check Ingress stability */}} +{{- define "ingress.isStable" -}} + {{- eq (include "ingress.apiVersion" .) "networking.k8s.io/v1" -}} +{{- end -}} + +{{/* Check Ingress supports pathType */}} +{{/* pathType was added to networking.k8s.io/v1beta1 in Kubernetes 1.18 */}} +{{- define "ingress.supportsPathType" -}} + {{- or (eq (include "ingress.isStable" .) "true") (and (eq (include "ingress.apiVersion" .) "networking.k8s.io/v1beta1") (semverCompare ">= 1.18-0" (include "kubeVersion" .))) -}} +{{- end -}} diff --git a/charts/nginx-ingress-services/templates/ingress.yaml b/charts/nginx-ingress-services/templates/ingress.yaml index 6cc9d019e4..0314ee9513 100644 --- a/charts/nginx-ingress-services/templates/ingress.yaml +++ b/charts/nginx-ingress-services/templates/ingress.yaml @@ -1,4 +1,6 @@ -apiVersion: extensions/v1beta1 +{{- $apiIsStable := eq (include "ingress.isStable" .) "true" -}} +{{- $ingressSupportsPathType := eq (include "ingress.supportsPathType" .) "true" -}} +apiVersion: {{ include "ingress.apiVersion" . }} kind: Ingress metadata: name: nginx-ingress @@ -31,51 +33,111 @@ spec: http: paths: - path: / + {{- if $ingressSupportsPathType }} + pathType: Prefix + {{- end }} backend: + {{- if $apiIsStable }} + service: + name: nginz + port: + name: http + {{- else }} serviceName: nginz servicePort: http + {{- end }} {{- if .Values.websockets.enabled }} - host: {{ .Values.config.dns.ssl }} http: paths: - path: / + {{- if $ingressSupportsPathType }} + pathType: Prefix + {{- end }} backend: + {{- if $apiIsStable }} + service: + name: nginz + port: + name: ws + {{- else }} serviceName: nginz servicePort: ws + {{- end }} {{- end }} {{- if .Values.webapp.enabled }} - host: {{ .Values.config.dns.webapp }} http: paths: - path: / + {{- if $ingressSupportsPathType }} + pathType: Prefix + {{- end }} backend: + {{- if $apiIsStable }} + service: + name: webapp-http + port: + number: {{ .Values.service.webapp.externalPort }} + {{- else }} serviceName: webapp-http servicePort: {{ .Values.service.webapp.externalPort }} + {{- end }} {{- end }} {{- if .Values.fakeS3.enabled }} - host: {{ .Values.config.dns.fakeS3 }} http: paths: - path: / + {{- if $ingressSupportsPathType }} + pathType: Prefix + {{- end }} backend: + {{- if $apiIsStable }} + service: + name: {{ .Values.service.s3.serviceName }} + port: + number: {{ .Values.service.s3.externalPort }} + {{- else }} serviceName: {{ .Values.service.s3.serviceName }} servicePort: {{ .Values.service.s3.externalPort }} + {{- end }} {{- end }} {{- if .Values.teamSettings.enabled }} - host: {{ .Values.config.dns.teamSettings }} http: paths: - path: / + {{- if $ingressSupportsPathType }} + pathType: Prefix + {{- end }} backend: + {{- if $apiIsStable }} + service: + name: team-settings-http + port: + number: {{ .Values.service.teamSettings.externalPort }} + {{- else }} serviceName: team-settings-http servicePort: {{ .Values.service.teamSettings.externalPort }} + {{- end }} {{- end }} {{- if .Values.accountPages.enabled }} - host: {{ .Values.config.dns.accountPages }} http: paths: - path: / + {{- if $ingressSupportsPathType }} + pathType: Prefix + {{- end }} backend: + {{- if $apiIsStable }} + service: + name: account-pages-http + port: + number: {{ .Values.service.accountPages.externalPort }} + {{- else }} serviceName: account-pages-http servicePort: {{ .Values.service.accountPages.externalPort }} + {{- end }} {{- end }} diff --git a/charts/nginx-ingress-services/templates/ingress_federator.yaml b/charts/nginx-ingress-services/templates/ingress_federator.yaml index e756d1cee2..bd9a6aa0b1 100644 --- a/charts/nginx-ingress-services/templates/ingress_federator.yaml +++ b/charts/nginx-ingress-services/templates/ingress_federator.yaml @@ -1,7 +1,9 @@ +{{- $apiIsStable := eq (include "ingress.isStable" .) "true" -}} +{{- $ingressSupportsPathType := eq (include "ingress.supportsPathType" .) "true" -}} {{- if .Values.federator.enabled }} # We use a separate ingress for federator so that we can require client # certificates only for federation requests -apiVersion: extensions/v1beta1 +apiVersion: {{ include "ingress.apiVersion" . }} kind: Ingress metadata: name: federator-ingress @@ -24,6 +26,13 @@ spec: http: paths: - backend: + {{- if $apiIsStable }} + service: + name: federator + port: + name: federator-ext + {{- else }} serviceName: federator servicePort: federator-ext # name must be below 15 chars + {{- end }} {{- end }} diff --git a/charts/sftd/templates/_helpers.tpl b/charts/sftd/templates/_helpers.tpl index 5832980497..f6e1d33113 100644 --- a/charts/sftd/templates/_helpers.tpl +++ b/charts/sftd/templates/_helpers.tpl @@ -58,3 +58,30 @@ app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/name: join-call app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} + +{{/* Allow KubeVersion to be overridden. */}} +{{- define "kubeVersion" -}} + {{- default .Capabilities.KubeVersion.Version .Values.kubeVersionOverride -}} +{{- end -}} + +{{/* Get Ingress API Version */}} +{{- define "ingress.apiVersion" -}} + {{- if and (.Capabilities.APIVersions.Has "networking.k8s.io/v1") (semverCompare ">= 1.19-0" (include "kubeVersion" .)) -}} + {{- print "networking.k8s.io/v1" -}} + {{- else if .Capabilities.APIVersions.Has "networking.k8s.io/v1beta1" -}} + {{- print "networking.k8s.io/v1beta1" -}} + {{- else -}} + {{- print "extensions/v1beta1" -}} + {{- end -}} +{{- end -}} + +{{/* Check Ingress stability */}} +{{- define "ingress.isStable" -}} + {{- eq (include "ingress.apiVersion" .) "networking.k8s.io/v1" -}} +{{- end -}} + +{{/* Check Ingress supports pathType */}} +{{/* pathType was added to networking.k8s.io/v1beta1 in Kubernetes 1.18 */}} +{{- define "ingress.supportsPathType" -}} + {{- or (eq (include "ingress.isStable" .) "true") (and (eq (include "ingress.apiVersion" .) "networking.k8s.io/v1beta1") (semverCompare ">= 1.18-0" (include "kubeVersion" .))) -}} +{{- end -}} diff --git a/charts/sftd/templates/ingress.yaml b/charts/sftd/templates/ingress.yaml index 9bf7958fa0..780c3d5cbb 100644 --- a/charts/sftd/templates/ingress.yaml +++ b/charts/sftd/templates/ingress.yaml @@ -1,4 +1,6 @@ -apiVersion: extensions/v1beta1 +{{- $apiIsStable := eq (include "ingress.isStable" .) "true" -}} +{{- $ingressSupportsPathType := eq (include "ingress.supportsPathType" .) "true" -}} +apiVersion: {{ include "ingress.apiVersion" . }} kind: Ingress metadata: name: "{{ include "sftd.fullname" . }}" @@ -18,14 +20,44 @@ spec: http: paths: - path: /sft/ + {{- if $ingressSupportsPathType }} + pathType: Prefix + {{- end }} backend: + {{- if $apiIsStable }} + service: + name: {{ include "sftd.fullname" . }} + port: + name: sft + {{- else }} serviceName: "{{ include "sftd.fullname" . }}" servicePort: sft + {{- end }} - path: /sfts/ + {{- if $ingressSupportsPathType }} + pathType: Prefix + {{- end }} backend: + {{- if $apiIsStable }} + service: + name: "{{ include "sftd.fullname" . }}-join-call" + port: + name: http + {{- else }} serviceName: "{{ include "sftd.fullname" . }}-join-call" servicePort: http + {{- end }} - path: /sft_servers_all.json + {{- if $ingressSupportsPathType }} + pathType: Exact + {{- end }} backend: + {{- if $apiIsStable }} + service: + name: "{{ include "sftd.fullname" . }}-join-call" + port: + name: http + {{- else }} serviceName: "{{ include "sftd.fullname" . }}-join-call" servicePort: http + {{- end }} diff --git a/docs/convert/shell.nix b/docs/convert/shell.nix index 130c456c6d..2016f223c2 100644 --- a/docs/convert/shell.nix +++ b/docs/convert/shell.nix @@ -1,4 +1,4 @@ -{ pkgs ? import {} }: +{ pkgs ? import { } }: (pkgs.buildFHSUserEnv { name = "pipzone"; targetPkgs = pkgs: (with pkgs; [ diff --git a/docs/src/conf.py b/docs/src/conf.py index ee4d992b35..750a09f904 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -46,6 +46,7 @@ 'rst2pdf.pdfbuilder', 'sphinx_multiversion', 'sphinx_reredirects', + 'sphinx_copybutton', ] # Grouping the document tree into PDF files. List of tuples diff --git a/docs/src/configuration-options.md b/docs/src/configuration-options.md index 647ac5f0ee..1eeee72383 100644 --- a/docs/src/configuration-options.md +++ b/docs/src/configuration-options.md @@ -431,11 +431,11 @@ helm upgrade --install --namespace metallb-system metallb wire/metallb \ Install `nginx-ingress-[controller,services]`: :: -: helm upgrade --install --namespace demo demo-nginx-ingress-controller wire/nginx-ingress-controller +: helm upgrade --install --namespace demo demo-nginx-ingress-controller wire/nginx-ingress-controller : --wait - helm upgrade --install --namespace demo demo-nginx-ingress-services wire/nginx-ingress-services + helm upgrade --install --namespace demo demo-nginx-ingress-services wire/nginx-ingress-services : -f values/nginx-ingress-services/demo-values.yaml -f values/nginx-ingress-services/demo-secrets.yaml --wait @@ -633,7 +633,7 @@ account-pages: ## Configuring authentication cookie throttling -Authentication cookies and the related throttling mechanism is described in the *Client API documentation*: +Authentication cookies and the related throttling mechanism is described in the *API documentation*: {ref}`login-cookies` The maximum number of cookies per account and type is defined by the brig option diff --git a/docs/src/developer/developer/how-to.md b/docs/src/developer/developer/how-to.md index 14c0e278d9..b900fc4f1f 100644 --- a/docs/src/developer/developer/how-to.md +++ b/docs/src/developer/developer/how-to.md @@ -16,15 +16,14 @@ Terminal 1: Terminal 2: * Compile all services: `make c` - * Note that you have to [import the public signing keys for nginx](https://github.com/wireapp/wire-server/blob/develop/services/nginz/README.md#common-problems-while-compiling) to be able to build nginz -* Run services including nginz: `./services/start-services-only.sh`. If you don't want to run nginz set `INTEGRATION_USE_NGINZ=0`. +* Run services including nginz: `./services/start-services-only.sh`. Open your browser at: -- http://localhost:8080/api/swagger-ui for the swagger 2.0 endpoints (in development as of Feb 2021 - more endpoints will be added here as time goes on) -- http://localhost:8080/swagger-ui/ for the old swagger 1.2 API (old swagger, endpoints will disappear from here (and become available in the previous link) as time progresses). Run `make -C services/nginz integration-test/conf/nginz/zwagger-ui` once to get JS libraries needed (they are not included in the repo). +- [http://localhost:8080/api/swagger-ui](http://localhost:8080/api/swagger-ui) for the swagger 2.0 endpoints (in development as of Feb 2021 - more endpoints will be added here as time goes on) +- [http://localhost:8080/swagger-ui](http://localhost:8080/swagger-ui) for the old swagger 1.2 API (old swagger, endpoints will disappear from here (and become available in the previous link) as time progresses). Run `make -C services/nginz integration-test/conf/nginz/zwagger-ui` once to get JS libraries needed (they are not included in the repo). -Swagger json (for swagger 2.0 endpoints) is available under http://localhost:8080/api/swagger.json +Swagger json (for swagger 2.0 endpoints) is available under [http://localhost:8080/api/swagger.json](http://localhost:8080/api/swagger.json) ## How to run federation tests across two backends diff --git a/docs/src/index.md b/docs/src/index.md index 135a0aa03f..da0c17e170 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -30,7 +30,7 @@ Connecting Wire Clients Optional Configuration Understanding wire-server components Single-Sign-On and user provisioning -Client API documentation +API documentation Security responses Notes for developers ``` diff --git a/docs/src/understand/api-client-perspective/index.md b/docs/src/understand/api-client-perspective/index.md index 8bf19e4290..e86c0de1f2 100644 --- a/docs/src/understand/api-client-perspective/index.md +++ b/docs/src/understand/api-client-perspective/index.md @@ -2,10 +2,6 @@ The following documentation provides information for, and takes the perspective of a Wire client developer. (wire-desktop, wire-android and wire-ios are examples of Wire Clients). This means only publicly accessible endpoints are mentioned. -```{warning} -This section of the documentation is very incomplete at the time of writing (summer 2020) - more pages on the client API will follow in the future. -``` - ```{toctree} :glob: true :maxdepth: 2 diff --git a/docs/src/understand/api-client-perspective/swagger.md b/docs/src/understand/api-client-perspective/swagger.md index 057d5beb2a..5722c95aed 100644 --- a/docs/src/understand/api-client-perspective/swagger.md +++ b/docs/src/understand/api-client-perspective/swagger.md @@ -3,24 +3,20 @@ Our staging system provides swagger documentation of our public rest API. -We are currently (as of 2021-09-29) migrating our documentation into a -new system that is automatically checked for correctness. The old -documentation still has some endpoints, but the new one is getting more and more complete. We will completely replace the old one eventually. +The swagger docs are correct by construction (compiled from the server +code), and the are complete up to bots/services and event notification +payloads (as of 2023-01-16). Please check the new docs first, and if you can't find what you're looking for, double-check the old. -## New docs - -These docs show swagger 2.0: +## New docs (swagger 2.0) [new staging swagger page](https://staging-nginz-https.zinfra.io/api/swagger-ui/) -## Old docs - -Some endpoints are only shown using swagger 1.2. +## Old docs (swagger 1.2) -At the time of writing, both swagger version 1.2 and version 2.0 are in use. If you are an employee of Wire, you can log in here and try out requests in the browser; if not, you can make use of the "List Operations" button on both 1.2 and 2.0 pages to see the possible API requests. +If you are an employee of Wire, you can log in here and try out requests in the browser; if not, you can make use of the "List Operations" button on both 1.2 and 2.0 pages to see the possible API requests. Browse to our [old staging swagger page](https://staging-nginz-https.zinfra.io/swagger-ui/) to see rendered swagger documentation for the remaining endpoints. diff --git a/docs/src/understand/federation/fedcalls.md b/docs/src/understand/federation/fedcalls.md new file mode 100644 index 0000000000..80fdb3c03e --- /dev/null +++ b/docs/src/understand/federation/fedcalls.md @@ -0,0 +1,18 @@ +# Federated API calls by client API end-point (generated) + +**Updated manually using using [the fedcalls tool](https://github.com/wireapp/wire-server/blob/8760b4978ccb039b229d458b7a08136a05e12ff9/tools/fedcalls/README.md); last change: 2023-01-16.** + +This is most likely only interesting for backend developers. + +This graph and csv file describe which public (client) API end-points trigger calls to which end-points at backends federating with the one that is called. The data is correct by construction (see [the fedcalls tool](https://github.com/wireapp/wire-server/blob/8760b4978ccb039b229d458b7a08136a05e12ff9/tools/fedcalls/README.md) for more details). + +The target can only be understood in the context of the [backend code base](https://github.com/wireapp/wire-server/). It is described by component (sub-directory in `/services`) and end-point name (use grep to find it). + +links: + +- [dot](img/wire-fedcalls.dot) +- [png](img/wire-fedcalls.png) +- [csv](img/wire-fedcalls.csv) + +```{image} img/wire-fedcalls.png +``` diff --git a/docs/src/understand/federation/img/wire-fedcalls.csv b/docs/src/understand/federation/img/wire-fedcalls.csv new file mode 100644 index 0000000000..bfc571a6d8 --- /dev/null +++ b/docs/src/understand/federation/img/wire-fedcalls.csv @@ -0,0 +1,122 @@ +source method,source path,target component,target name +get,/users/{uid_domain}/{uid},brig,get-users-by-ids +post,/list-users,brig,get-users-by-ids +put,/self,brig,on-user-deleted-connections +delete,/self,brig,on-user-deleted-connections +delete,/self/phone,brig,on-user-deleted-connections +delete,/self/email,brig,on-user-deleted-connections +put,/self/locale,brig,on-user-deleted-connections +put,/self/handle,brig,on-user-deleted-connections +post,/register,brig,on-user-deleted-connections +post,/delete,brig,on-user-deleted-connections +get,/activate,brig,on-user-deleted-connections +post,/activate,brig,on-user-deleted-connections +get,/users/{uid_domain}/{uid}/clients,brig,get-user-clients +get,/users/{uid_domain}/{uid}/clients/{client},brig,get-user-clients +post,/users/list-clients,brig,get-user-clients +get,/users/{uid_domain}/{uid}/prekeys/{client},brig,claim-prekey +get,/users/{uid_domain}/{uid}/prekeys,brig,claim-prekey-bundle +post,/users/list-prekeys,brig,claim-multi-prekey-bundle +post,/clients,brig,on-user-deleted-connections +put,/connections/{uid_domain}/{uid},brig,send-connection-action +post,/connections/{uid_domain}/{uid},brig,send-connection-action +get,/search/contacts,brig,get-users-by-ids +get,/search/contacts,brig,search-users +post,/mls/key-packages/claim/{user_domain}/{user},brig,claim-key-packages +post,/access,brig,on-user-deleted-connections +post,/login,brig,on-user-deleted-connections +get,/assets/{key_domain}/{key},cargohold,get-asset +get,/assets/{key_domain}/{key},cargohold,stream-asset +put,/conversations/{cnv},galley,on-conversation-updated +put,/conversations/{cnv},galley,on-mls-message-sent +put,/conversations/{cnv},galley,on-new-remote-conversation +get,/conversations/{cnv_domain}/{cnv},galley,get-conversations +get,/conversations/{cnv_domain}/{cnv}/groupinfo,galley,query-group-info +post,/conversations/list,galley,get-conversations +post,/conversations/join,galley,on-conversation-updated +post,/conversations/join,galley,on-new-remote-conversation +post,/conversations,galley,on-conversation-created +post,/conversations/one2one,galley,on-conversation-created +post,/conversations/{cnv_domain}/{cnv}/members,galley,on-conversation-updated +post,/conversations/{cnv_domain}/{cnv}/members,galley,on-mls-message-sent +post,/conversations/{cnv_domain}/{cnv}/members,galley,on-new-remote-conversation +post,/conversations/{cnv}/join,galley,on-conversation-updated +post,/conversations/{cnv}/join,galley,on-new-remote-conversation +post,/conversations/{cnv_domain}/{cnv}/typing,galley,on-typing-indicator-updated +put,/conversations/{cnv_domain}/{cnv}/members/{usr_domain}/{usr},galley,on-conversation-updated +put,/conversations/{cnv_domain}/{cnv}/members/{usr_domain}/{usr},galley,on-mls-message-sent +put,/conversations/{cnv_domain}/{cnv}/members/{usr_domain}/{usr},galley,on-new-remote-conversation +delete,/conversations/{cnv_domain}/{cnv}/members/{usr_domain}/{usr},galley,leave-conversation +delete,/conversations/{cnv_domain}/{cnv}/members/{usr_domain}/{usr},galley,on-conversation-updated +delete,/conversations/{cnv_domain}/{cnv}/members/{usr_domain}/{usr},galley,on-mls-message-sent +delete,/conversations/{cnv_domain}/{cnv}/members/{usr_domain}/{usr},galley,on-new-remote-conversation +put,/conversations/{cnv}/members/{usr},galley,on-conversation-updated +put,/conversations/{cnv}/members/{usr},galley,on-mls-message-sent +put,/conversations/{cnv}/members/{usr},galley,on-new-remote-conversation +put,/conversations/{cnv}/name,galley,on-conversation-updated +put,/conversations/{cnv}/name,galley,on-mls-message-sent +put,/conversations/{cnv}/name,galley,on-new-remote-conversation +put,/conversations/{cnv_domain}/{cnv}/name,galley,on-conversation-updated +put,/conversations/{cnv_domain}/{cnv}/name,galley,on-mls-message-sent +put,/conversations/{cnv_domain}/{cnv}/name,galley,on-new-remote-conversation +put,/conversations/{cnv}/message-timer,galley,on-conversation-updated +put,/conversations/{cnv}/message-timer,galley,on-mls-message-sent +put,/conversations/{cnv}/message-timer,galley,on-new-remote-conversation +put,/conversations/{cnv_domain}/{cnv}/message-timer,galley,on-conversation-updated +put,/conversations/{cnv_domain}/{cnv}/message-timer,galley,on-mls-message-sent +put,/conversations/{cnv_domain}/{cnv}/message-timer,galley,on-new-remote-conversation +put,/conversations/{cnv}/receipt-mode,galley,on-conversation-updated +put,/conversations/{cnv}/receipt-mode,galley,on-mls-message-sent +put,/conversations/{cnv}/receipt-mode,galley,on-new-remote-conversation +put,/conversations/{cnv}/receipt-mode,galley,update-conversation +put,/conversations/{cnv_domain}/{cnv}/receipt-mode,galley,on-conversation-updated +put,/conversations/{cnv_domain}/{cnv}/receipt-mode,galley,on-mls-message-sent +put,/conversations/{cnv_domain}/{cnv}/receipt-mode,galley,on-new-remote-conversation +put,/conversations/{cnv_domain}/{cnv}/receipt-mode,galley,update-conversation +put,/conversations/{cnv_domain}/{cnv}/access,galley,on-conversation-updated +put,/conversations/{cnv_domain}/{cnv}/access,galley,on-mls-message-sent +put,/conversations/{cnv_domain}/{cnv}/access,galley,on-new-remote-conversation +delete,/teams/{tid}/conversations/{cid},galley,on-conversation-updated +delete,/teams/{tid}/conversations/{cid},galley,on-mls-message-sent +delete,/teams/{tid}/conversations/{cid},galley,on-new-remote-conversation +post,/conversations/{cnv}/otr/messages,galley,on-message-sent +post,/conversations/{cnv}/otr/messages,brig,get-user-clients +post,/conversations/{cnv_domain}/{cnv}/proteus/messages,brig,get-user-clients +post,/conversations/{cnv_domain}/{cnv}/proteus/messages,galley,on-message-sent +post,/conversations/{cnv_domain}/{cnv}/proteus/messages,galley,send-message +post,/bot/messages,galley,on-message-sent +post,/bot/messages,brig,get-user-clients +put,/teams/{tid}/features/legalhold,galley,on-conversation-updated +put,/teams/{tid}/features/legalhold,galley,on-mls-message-sent +put,/teams/{tid}/features/legalhold,galley,on-new-remote-conversation +post,/mls/welcome,galley,mls-welcome +post,/mls/messages,galley,on-mls-message-sent +post,/mls/messages,galley,send-mls-message +post,/mls/messages,galley,on-conversation-updated +post,/mls/messages,galley,on-new-remote-conversation +post,/mls/messages,brig,get-mls-clients +post,/mls/commit-bundles,galley,on-mls-message-sent +post,/mls/commit-bundles,galley,mls-welcome +post,/mls/commit-bundles,galley,send-mls-commit-bundle +post,/mls/commit-bundles,galley,on-conversation-updated +post,/mls/commit-bundles,galley,on-new-remote-conversation +post,/mls/commit-bundles,brig,get-mls-clients +delete,/teams/{tid}/legalhold/settings,galley,on-conversation-updated +delete,/teams/{tid}/legalhold/settings,galley,on-mls-message-sent +delete,/teams/{tid}/legalhold/settings,galley,on-new-remote-conversation +post,/teams/{tid}/legalhold/{uid},galley,on-conversation-updated +post,/teams/{tid}/legalhold/{uid},galley,on-mls-message-sent +post,/teams/{tid}/legalhold/{uid},galley,on-new-remote-conversation +delete,/teams/{tid}/legalhold/{uid},galley,on-conversation-updated +delete,/teams/{tid}/legalhold/{uid},galley,on-mls-message-sent +delete,/teams/{tid}/legalhold/{uid},galley,on-new-remote-conversation +post,/teams/{tid}/legalhold/consent,galley,on-conversation-updated +post,/teams/{tid}/legalhold/consent,galley,on-mls-message-sent +post,/teams/{tid}/legalhold/consent,galley,on-new-remote-conversation +put,/teams/{tid}/legalhold/{uid}/approve,galley,on-conversation-updated +put,/teams/{tid}/legalhold/{uid}/approve,galley,on-mls-message-sent +put,/teams/{tid}/legalhold/{uid}/approve,galley,on-new-remote-conversation +post,/i/users,brig,on-user-deleted-connections +post,/i/users/spar,brig,on-user-deleted-connections +post,/i/legalhold-login,brig,on-user-deleted-connections +post,/i/sso-login,brig,on-user-deleted-connections \ No newline at end of file diff --git a/docs/src/understand/federation/img/wire-fedcalls.dot b/docs/src/understand/federation/img/wire-fedcalls.dot new file mode 100644 index 0000000000..77648a9d95 --- /dev/null +++ b/docs/src/understand/federation/img/wire-fedcalls.dot @@ -0,0 +1,219 @@ +strict digraph { + graph [rankdir=LR] + node [shape=rectangle] + edge [style=dashed] + subgraph { + "37: delete /conversations/{cnv_domain}/{cnv}/members/{usr_domain}/{usr}":w + "3: delete /self":w + "5: delete /self/email":w + "4: delete /self/phone":w + "46: delete /teams/{tid}/conversations/{cid}":w + "54: delete /teams/{tid}/legalhold/settings":w + "56: delete /teams/{tid}/legalhold/{uid}":w + "10: get /activate":w + "25: get /assets/{key_domain}/{key}":w + "27: get /conversations/{cnv_domain}/{cnv}":w + "28: get /conversations/{cnv_domain}/{cnv}/groupinfo":w + "21: get /search/contacts":w + "0: get /users/{uid_domain}/{uid}":w + "12: get /users/{uid_domain}/{uid}/clients":w + "13: get /users/{uid_domain}/{uid}/clients/{client}":w + "16: get /users/{uid_domain}/{uid}/prekeys":w + "15: get /users/{uid_domain}/{uid}/prekeys/{client}":w + "23: post /access":w + "11: post /activate":w + "49: post /bot/messages":w + "18: post /clients":w + "20: post /connections/{uid_domain}/{uid}":w + "31: post /conversations":w + "30: post /conversations/join":w + "29: post /conversations/list":w + "32: post /conversations/one2one":w + "33: post /conversations/{cnv_domain}/{cnv}/members":w + "48: post /conversations/{cnv_domain}/{cnv}/proteus/messages":w + "35: post /conversations/{cnv_domain}/{cnv}/typing":w + "34: post /conversations/{cnv}/join":w + "47: post /conversations/{cnv}/otr/messages":w + "9: post /delete":w + "61: post /i/legalhold-login":w + "62: post /i/sso-login":w + "59: post /i/users":w + "60: post /i/users/spar":w + "1: post /list-users":w + "24: post /login":w + "53: post /mls/commit-bundles":w + "22: post /mls/key-packages/claim/{user_domain}/{user}":w + "52: post /mls/messages":w + "51: post /mls/welcome":w + "8: post /register":w + "57: post /teams/{tid}/legalhold/consent":w + "55: post /teams/{tid}/legalhold/{uid}":w + "14: post /users/list-clients":w + "17: post /users/list-prekeys":w + "19: put /connections/{uid_domain}/{uid}":w + "45: put /conversations/{cnv_domain}/{cnv}/access":w + "36: put /conversations/{cnv_domain}/{cnv}/members/{usr_domain}/{usr}":w + "42: put /conversations/{cnv_domain}/{cnv}/message-timer":w + "40: put /conversations/{cnv_domain}/{cnv}/name":w + "44: put /conversations/{cnv_domain}/{cnv}/receipt-mode":w + "26: put /conversations/{cnv}":w + "38: put /conversations/{cnv}/members/{usr}":w + "41: put /conversations/{cnv}/message-timer":w + "39: put /conversations/{cnv}/name":w + "43: put /conversations/{cnv}/receipt-mode":w + "2: put /self":w + "7: put /self/handle":w + "6: put /self/locale":w + "50: put /teams/{tid}/features/legalhold":w + "58: put /teams/{tid}/legalhold/{uid}/approve":w + } + subgraph { + "71: [brig]:claim-key-packages":e + "68: [brig]:claim-multi-prekey-bundle":e + "66: [brig]:claim-prekey":e + "67: [brig]:claim-prekey-bundle":e + "87: [brig]:get-mls-clients":e + "65: [brig]:get-user-clients":e + "63: [brig]:get-users-by-ids":e + "64: [brig]:on-user-deleted-connections":e + "70: [brig]:search-users":e + "69: [brig]:send-connection-action":e + "72: [cargohold]:get-asset":e + "73: [cargohold]:stream-asset":e + "77: [galley]:get-conversations":e + "81: [galley]:leave-conversation":e + "85: [galley]:mls-welcome":e + "79: [galley]:on-conversation-created":e + "74: [galley]:on-conversation-updated":e + "83: [galley]:on-message-sent":e + "75: [galley]:on-mls-message-sent":e + "76: [galley]:on-new-remote-conversation":e + "80: [galley]:on-typing-indicator-updated":e + "78: [galley]:query-group-info":e + "84: [galley]:send-message":e + "88: [galley]:send-mls-commit-bundle":e + "86: [galley]:send-mls-message":e + "82: [galley]:update-conversation":e + } + "0: get /users/{uid_domain}/{uid}":w -> "63: [brig]:get-users-by-ids":e + "1: post /list-users":w -> "63: [brig]:get-users-by-ids":e + "2: put /self":w -> "64: [brig]:on-user-deleted-connections":e + "3: delete /self":w -> "64: [brig]:on-user-deleted-connections":e + "4: delete /self/phone":w -> "64: [brig]:on-user-deleted-connections":e + "5: delete /self/email":w -> "64: [brig]:on-user-deleted-connections":e + "6: put /self/locale":w -> "64: [brig]:on-user-deleted-connections":e + "7: put /self/handle":w -> "64: [brig]:on-user-deleted-connections":e + "8: post /register":w -> "64: [brig]:on-user-deleted-connections":e + "9: post /delete":w -> "64: [brig]:on-user-deleted-connections":e + "10: get /activate":w -> "64: [brig]:on-user-deleted-connections":e + "11: post /activate":w -> "64: [brig]:on-user-deleted-connections":e + "12: get /users/{uid_domain}/{uid}/clients":w -> "65: [brig]:get-user-clients":e + "13: get /users/{uid_domain}/{uid}/clients/{client}":w -> "65: [brig]:get-user-clients":e + "14: post /users/list-clients":w -> "65: [brig]:get-user-clients":e + "15: get /users/{uid_domain}/{uid}/prekeys/{client}":w -> "66: [brig]:claim-prekey":e + "16: get /users/{uid_domain}/{uid}/prekeys":w -> "67: [brig]:claim-prekey-bundle":e + "17: post /users/list-prekeys":w -> "68: [brig]:claim-multi-prekey-bundle":e + "18: post /clients":w -> "64: [brig]:on-user-deleted-connections":e + "19: put /connections/{uid_domain}/{uid}":w -> "69: [brig]:send-connection-action":e + "20: post /connections/{uid_domain}/{uid}":w -> "69: [brig]:send-connection-action":e + "21: get /search/contacts":w -> "63: [brig]:get-users-by-ids":e + "21: get /search/contacts":w -> "70: [brig]:search-users":e + "22: post /mls/key-packages/claim/{user_domain}/{user}":w -> "71: [brig]:claim-key-packages":e + "23: post /access":w -> "64: [brig]:on-user-deleted-connections":e + "24: post /login":w -> "64: [brig]:on-user-deleted-connections":e + "25: get /assets/{key_domain}/{key}":w -> "72: [cargohold]:get-asset":e + "25: get /assets/{key_domain}/{key}":w -> "73: [cargohold]:stream-asset":e + "26: put /conversations/{cnv}":w -> "74: [galley]:on-conversation-updated":e + "26: put /conversations/{cnv}":w -> "75: [galley]:on-mls-message-sent":e + "26: put /conversations/{cnv}":w -> "76: [galley]:on-new-remote-conversation":e + "27: get /conversations/{cnv_domain}/{cnv}":w -> "77: [galley]:get-conversations":e + "28: get /conversations/{cnv_domain}/{cnv}/groupinfo":w -> "78: [galley]:query-group-info":e + "29: post /conversations/list":w -> "77: [galley]:get-conversations":e + "30: post /conversations/join":w -> "74: [galley]:on-conversation-updated":e + "30: post /conversations/join":w -> "76: [galley]:on-new-remote-conversation":e + "31: post /conversations":w -> "79: [galley]:on-conversation-created":e + "32: post /conversations/one2one":w -> "79: [galley]:on-conversation-created":e + "33: post /conversations/{cnv_domain}/{cnv}/members":w -> "74: [galley]:on-conversation-updated":e + "33: post /conversations/{cnv_domain}/{cnv}/members":w -> "75: [galley]:on-mls-message-sent":e + "33: post /conversations/{cnv_domain}/{cnv}/members":w -> "76: [galley]:on-new-remote-conversation":e + "34: post /conversations/{cnv}/join":w -> "74: [galley]:on-conversation-updated":e + "34: post /conversations/{cnv}/join":w -> "76: [galley]:on-new-remote-conversation":e + "35: post /conversations/{cnv_domain}/{cnv}/typing":w -> "80: [galley]:on-typing-indicator-updated":e + "36: put /conversations/{cnv_domain}/{cnv}/members/{usr_domain}/{usr}":w -> "74: [galley]:on-conversation-updated":e + "36: put /conversations/{cnv_domain}/{cnv}/members/{usr_domain}/{usr}":w -> "75: [galley]:on-mls-message-sent":e + "36: put /conversations/{cnv_domain}/{cnv}/members/{usr_domain}/{usr}":w -> "76: [galley]:on-new-remote-conversation":e + "37: delete /conversations/{cnv_domain}/{cnv}/members/{usr_domain}/{usr}":w -> "81: [galley]:leave-conversation":e + "37: delete /conversations/{cnv_domain}/{cnv}/members/{usr_domain}/{usr}":w -> "74: [galley]:on-conversation-updated":e + "37: delete /conversations/{cnv_domain}/{cnv}/members/{usr_domain}/{usr}":w -> "75: [galley]:on-mls-message-sent":e + "37: delete /conversations/{cnv_domain}/{cnv}/members/{usr_domain}/{usr}":w -> "76: [galley]:on-new-remote-conversation":e + "38: put /conversations/{cnv}/members/{usr}":w -> "74: [galley]:on-conversation-updated":e + "38: put /conversations/{cnv}/members/{usr}":w -> "75: [galley]:on-mls-message-sent":e + "38: put /conversations/{cnv}/members/{usr}":w -> "76: [galley]:on-new-remote-conversation":e + "39: put /conversations/{cnv}/name":w -> "74: [galley]:on-conversation-updated":e + "39: put /conversations/{cnv}/name":w -> "75: [galley]:on-mls-message-sent":e + "39: put /conversations/{cnv}/name":w -> "76: [galley]:on-new-remote-conversation":e + "40: put /conversations/{cnv_domain}/{cnv}/name":w -> "74: [galley]:on-conversation-updated":e + "40: put /conversations/{cnv_domain}/{cnv}/name":w -> "75: [galley]:on-mls-message-sent":e + "40: put /conversations/{cnv_domain}/{cnv}/name":w -> "76: [galley]:on-new-remote-conversation":e + "41: put /conversations/{cnv}/message-timer":w -> "74: [galley]:on-conversation-updated":e + "41: put /conversations/{cnv}/message-timer":w -> "75: [galley]:on-mls-message-sent":e + "41: put /conversations/{cnv}/message-timer":w -> "76: [galley]:on-new-remote-conversation":e + "42: put /conversations/{cnv_domain}/{cnv}/message-timer":w -> "74: [galley]:on-conversation-updated":e + "42: put /conversations/{cnv_domain}/{cnv}/message-timer":w -> "75: [galley]:on-mls-message-sent":e + "42: put /conversations/{cnv_domain}/{cnv}/message-timer":w -> "76: [galley]:on-new-remote-conversation":e + "43: put /conversations/{cnv}/receipt-mode":w -> "74: [galley]:on-conversation-updated":e + "43: put /conversations/{cnv}/receipt-mode":w -> "75: [galley]:on-mls-message-sent":e + "43: put /conversations/{cnv}/receipt-mode":w -> "76: [galley]:on-new-remote-conversation":e + "43: put /conversations/{cnv}/receipt-mode":w -> "82: [galley]:update-conversation":e + "44: put /conversations/{cnv_domain}/{cnv}/receipt-mode":w -> "74: [galley]:on-conversation-updated":e + "44: put /conversations/{cnv_domain}/{cnv}/receipt-mode":w -> "75: [galley]:on-mls-message-sent":e + "44: put /conversations/{cnv_domain}/{cnv}/receipt-mode":w -> "76: [galley]:on-new-remote-conversation":e + "44: put /conversations/{cnv_domain}/{cnv}/receipt-mode":w -> "82: [galley]:update-conversation":e + "45: put /conversations/{cnv_domain}/{cnv}/access":w -> "74: [galley]:on-conversation-updated":e + "45: put /conversations/{cnv_domain}/{cnv}/access":w -> "75: [galley]:on-mls-message-sent":e + "45: put /conversations/{cnv_domain}/{cnv}/access":w -> "76: [galley]:on-new-remote-conversation":e + "46: delete /teams/{tid}/conversations/{cid}":w -> "74: [galley]:on-conversation-updated":e + "46: delete /teams/{tid}/conversations/{cid}":w -> "75: [galley]:on-mls-message-sent":e + "46: delete /teams/{tid}/conversations/{cid}":w -> "76: [galley]:on-new-remote-conversation":e + "47: post /conversations/{cnv}/otr/messages":w -> "83: [galley]:on-message-sent":e + "47: post /conversations/{cnv}/otr/messages":w -> "65: [brig]:get-user-clients":e + "48: post /conversations/{cnv_domain}/{cnv}/proteus/messages":w -> "65: [brig]:get-user-clients":e + "48: post /conversations/{cnv_domain}/{cnv}/proteus/messages":w -> "83: [galley]:on-message-sent":e + "48: post /conversations/{cnv_domain}/{cnv}/proteus/messages":w -> "84: [galley]:send-message":e + "49: post /bot/messages":w -> "83: [galley]:on-message-sent":e + "49: post /bot/messages":w -> "65: [brig]:get-user-clients":e + "50: put /teams/{tid}/features/legalhold":w -> "74: [galley]:on-conversation-updated":e + "50: put /teams/{tid}/features/legalhold":w -> "75: [galley]:on-mls-message-sent":e + "50: put /teams/{tid}/features/legalhold":w -> "76: [galley]:on-new-remote-conversation":e + "51: post /mls/welcome":w -> "85: [galley]:mls-welcome":e + "52: post /mls/messages":w -> "75: [galley]:on-mls-message-sent":e + "52: post /mls/messages":w -> "86: [galley]:send-mls-message":e + "52: post /mls/messages":w -> "74: [galley]:on-conversation-updated":e + "52: post /mls/messages":w -> "76: [galley]:on-new-remote-conversation":e + "52: post /mls/messages":w -> "87: [brig]:get-mls-clients":e + "53: post /mls/commit-bundles":w -> "75: [galley]:on-mls-message-sent":e + "53: post /mls/commit-bundles":w -> "85: [galley]:mls-welcome":e + "53: post /mls/commit-bundles":w -> "88: [galley]:send-mls-commit-bundle":e + "53: post /mls/commit-bundles":w -> "74: [galley]:on-conversation-updated":e + "53: post /mls/commit-bundles":w -> "76: [galley]:on-new-remote-conversation":e + "53: post /mls/commit-bundles":w -> "87: [brig]:get-mls-clients":e + "54: delete /teams/{tid}/legalhold/settings":w -> "74: [galley]:on-conversation-updated":e + "54: delete /teams/{tid}/legalhold/settings":w -> "75: [galley]:on-mls-message-sent":e + "54: delete /teams/{tid}/legalhold/settings":w -> "76: [galley]:on-new-remote-conversation":e + "55: post /teams/{tid}/legalhold/{uid}":w -> "74: [galley]:on-conversation-updated":e + "55: post /teams/{tid}/legalhold/{uid}":w -> "75: [galley]:on-mls-message-sent":e + "55: post /teams/{tid}/legalhold/{uid}":w -> "76: [galley]:on-new-remote-conversation":e + "56: delete /teams/{tid}/legalhold/{uid}":w -> "74: [galley]:on-conversation-updated":e + "56: delete /teams/{tid}/legalhold/{uid}":w -> "75: [galley]:on-mls-message-sent":e + "56: delete /teams/{tid}/legalhold/{uid}":w -> "76: [galley]:on-new-remote-conversation":e + "57: post /teams/{tid}/legalhold/consent":w -> "74: [galley]:on-conversation-updated":e + "57: post /teams/{tid}/legalhold/consent":w -> "75: [galley]:on-mls-message-sent":e + "57: post /teams/{tid}/legalhold/consent":w -> "76: [galley]:on-new-remote-conversation":e + "58: put /teams/{tid}/legalhold/{uid}/approve":w -> "74: [galley]:on-conversation-updated":e + "58: put /teams/{tid}/legalhold/{uid}/approve":w -> "75: [galley]:on-mls-message-sent":e + "58: put /teams/{tid}/legalhold/{uid}/approve":w -> "76: [galley]:on-new-remote-conversation":e + "59: post /i/users":w -> "64: [brig]:on-user-deleted-connections":e + "60: post /i/users/spar":w -> "64: [brig]:on-user-deleted-connections":e + "61: post /i/legalhold-login":w -> "64: [brig]:on-user-deleted-connections":e + "62: post /i/sso-login":w -> "64: [brig]:on-user-deleted-connections":e +} \ No newline at end of file diff --git a/docs/src/understand/federation/img/wire-fedcalls.png b/docs/src/understand/federation/img/wire-fedcalls.png new file mode 100644 index 0000000000..35fb0ed9d2 Binary files /dev/null and b/docs/src/understand/federation/img/wire-fedcalls.png differ diff --git a/libs/wire-api/src/Wire/API/OAuth.hs b/libs/wire-api/src/Wire/API/OAuth.hs index 757c9ef614..41152fb644 100644 --- a/libs/wire-api/src/Wire/API/OAuth.hs +++ b/libs/wire-api/src/Wire/API/OAuth.hs @@ -496,6 +496,26 @@ instance FromForm (Either OAuthAccessTokenRequest OAuthRefreshAccessTokenRequest choose _ (Right a) = Right (Right a) choose (Left err) _ = Left err +data OAuthRevokeRefreshTokenRequest = OAuthRevokeRefreshTokenRequest + { ortrClientId :: OAuthClientId, + ortrClientSecret :: OAuthClientPlainTextSecret, + ortrRefreshToken :: OAuthRefreshToken + } + deriving (Eq, Show, Generic) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema OAuthRevokeRefreshTokenRequest) + +instance ToSchema OAuthRevokeRefreshTokenRequest where + schema = + object "OAuthRevokeRefreshTokenRequest" $ + OAuthRevokeRefreshTokenRequest + <$> ortrClientId .= fieldWithDocModifier "clientId" clientIdDescription schema + <*> ortrClientSecret .= fieldWithDocModifier "clientSecret" clientSecretDescription schema + <*> ortrRefreshToken .= fieldWithDocModifier "refreshToken" refreshTokenDescription schema + where + clientIdDescription = description ?~ "The OAuth client's ID" + clientSecretDescription = description ?~ "The OAuth client's secret" + refreshTokenDescription = description ?~ "The refresh token" + -------------------------------------------------------------------------------- -- Errors diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Brig/OAuth.hs b/libs/wire-api/src/Wire/API/Routes/Public/Brig/OAuth.hs index dc6e9bd3a0..7df8c2793f 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Brig/OAuth.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Brig/OAuth.hs @@ -75,11 +75,27 @@ type OAuthAPI = :> CanThrow 'OAuthAuthCodeNotFound :> CanThrow 'OAuthClientNotFound :> CanThrow 'OAuthFeatureDisabled + :> CanThrow 'OAuthInvalidRefreshToken + :> CanThrow 'OAuthInvalidGrantType + :> CanThrow 'OAuthInvalidClientCredentials :> "oauth" :> "token" :> ReqBody '[FormUrlEncoded] (Either OAuthAccessTokenRequest OAuthRefreshAccessTokenRequest) :> Post '[JSON] OAuthAccessTokenResponse ) + :<|> Named + "revoke-refresh-token" + ( Summary "Revoke an OAuth refresh token" + :> Description "Revoke an access token." + :> CanThrow 'OAuthJwtError + :> CanThrow 'OAuthInvalidRefreshToken + :> CanThrow 'OAuthClientNotFound + :> CanThrow 'OAuthInvalidClientCredentials + :> "oauth" + :> "revoke" + :> ReqBody '[JSON] OAuthRevokeRefreshTokenRequest + :> Post '[JSON] () + ) swaggerDoc :: Swagger swaggerDoc = toSwagger (Proxy @OAuthAPI) diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs b/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs index 3c877fe475..65dc97b08b 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs @@ -102,12 +102,13 @@ type ConversationAPI = Named "get-unqualified-conversation" ( Summary "Get a conversation by ID" + :> Until 'V3 :> CanThrow 'ConvNotFound :> CanThrow 'ConvAccessDenied :> ZLocalUser :> "conversations" :> Capture "cnv" ConvId - :> Get '[Servant.JSON] Conversation + :> MultiVerb1 'GET '[JSON] (VersionedRespond 'V2 200 "Conversation" Conversation) ) :<|> Named "get-unqualified-conversation-legalhold-alias" @@ -120,18 +121,31 @@ type ConversationAPI = :> "legalhold" :> "conversations" :> Capture "cnv" ConvId - :> Get '[Servant.JSON] Conversation + :> MultiVerb1 'GET '[JSON] (VersionedRespond 'V2 200 "Conversation" Conversation) + ) + :<|> Named + "get-conversation@v2" + ( Summary "Get a conversation by ID" + :> Until 'V3 + :> MakesFederatedCall 'Galley "get-conversations" + :> CanThrow 'ConvNotFound + :> CanThrow 'ConvAccessDenied + :> ZLocalUser + :> "conversations" + :> QualifiedCapture "cnv" ConvId + :> MultiVerb1 'GET '[JSON] (VersionedRespond 'V2 200 "Conversation" Conversation) ) :<|> Named "get-conversation" ( Summary "Get a conversation by ID" + :> From 'V3 :> MakesFederatedCall 'Galley "get-conversations" :> CanThrow 'ConvNotFound :> CanThrow 'ConvAccessDenied :> ZLocalUser :> "conversations" :> QualifiedCapture "cnv" ConvId - :> Get '[Servant.JSON] Conversation + :> Get '[JSON] Conversation ) :<|> Named "get-conversation-roles" diff --git a/libs/wire-api/src/Wire/API/User/Client/Prekey.hs b/libs/wire-api/src/Wire/API/User/Client/Prekey.hs index 8b29e54afa..cf084c65d8 100644 --- a/libs/wire-api/src/Wire/API/User/Client/Prekey.hs +++ b/libs/wire-api/src/Wire/API/User/Client/Prekey.hs @@ -25,6 +25,7 @@ module Wire.API.User.Client.Prekey LastPrekey, lastPrekey, unpackLastPrekey, + fakeLastPrekey, lastPrekeyId, PrekeyBundle (..), ClientPrekey (..), @@ -101,6 +102,11 @@ lastPrekeyId = PrekeyId maxBound lastPrekey :: Text -> LastPrekey lastPrekey = LastPrekey . Prekey lastPrekeyId +-- for tests only +-- This fake last prekey has the wrong prekeyId +fakeLastPrekey :: LastPrekey +fakeLastPrekey = LastPrekey $ Prekey (PrekeyId 7) "pQABAQcCoQBYIDXdN8VlKb5lbgPmoDPLPyqNIEyShG4oT/DlW0peRRZUA6EAoQBYILLf1TIwSB62q69Ojs/X1tzJ+dYHNAw4QbW/7TC5vSZqBPY=" + -------------------------------------------------------------------------------- -- PrekeyBundle diff --git a/nix/default.nix b/nix/default.nix index 34f37250e0..bd35e4aaf2 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -36,6 +36,7 @@ let sphinx-multiversion sphinx_rtd_theme sphinx_reredirects + sphinx-copybutton sphinxcontrib-fulltoc sphinxcontrib-kroki ])) diff --git a/services/brig/src/Brig/API/OAuth.hs b/services/brig/src/Brig/API/OAuth.hs index 2e09554f6f..41eacea637 100644 --- a/services/brig/src/Brig/API/OAuth.hs +++ b/services/brig/src/Brig/API/OAuth.hs @@ -65,6 +65,7 @@ oauthAPI = Named @"get-oauth-client" getOAuthClient :<|> Named @"create-oauth-auth-code" createNewOAuthAuthCode :<|> Named @"create-oauth-access-token" createAccessTokenWith + :<|> Named @"revoke-refresh-token" revokeRefreshToken -------------------------------------------------------------------------------- -- Handlers @@ -112,25 +113,29 @@ createAccessTokenWithRefreshToken :: (Member Now r, Member Jwk r) => OAuthRefres createAccessTokenWithRefreshToken req = do unless (oartGrantType req == OAuthGrantTypeRefreshToken) $ throwStd $ errorToWai @'OAuthInvalidGrantType key <- signingKey - rid <- verifyRefreshToken key (oartRefreshToken req) - mInfo <- lift $ wrapClient $ lookupAndDeleteOAuthRefreshToken rid - info <- maybe (throwStd $ errorToWai @'OAuthInvalidRefreshToken) pure mInfo + info <- verifyTokenAndGetInfo key (oartRefreshToken req) let uid = oriUserId info cid = oriClientId info scope = oriScopes info - oauthClient <- getOAuthClient uid cid >>= maybe (throwStd $ errorToWai @'OAuthClientNotFound) pure + void $ getOAuthClient uid cid >>= maybe (throwStd $ errorToWai @'OAuthClientNotFound) pure unless (cid == oartClientId req) $ throwStd $ errorToWai @'OAuthInvalidClientCredentials - unlessM (verifyClientSecret (oartClientSecret req) (ocId oauthClient)) $ throwStd $ errorToWai @'OAuthInvalidClientCredentials + unlessM (verifyClientSecret (oartClientSecret req) cid) $ throwStd $ errorToWai @'OAuthInvalidClientCredentials createAccessToken key uid cid scope - where - verifyRefreshToken :: JWK -> OAuthRefreshToken -> (Handler r) OAuthRefreshTokenId - verifyRefreshToken key rt = do - eClaims <- liftIO (OAuth.verify' key (unOAuthToken rt)) - case eClaims of - Left _ -> throwStd $ errorToWai @'OAuthInvalidRefreshToken - Right claims -> maybe (throwStd $ errorToWai @'OAuthInvalidRefreshToken) pure $ hcsSub claims + +verifyTokenAndGetInfo :: JWK -> OAuthRefreshToken -> (Handler r) OAuthRefreshTokenInfo +verifyTokenAndGetInfo key = + verifyRefreshToken key + >=> lift . wrapClient . lookupAndDeleteOAuthRefreshToken + >=> maybe (throwStd $ errorToWai @'OAuthInvalidRefreshToken) pure + +verifyRefreshToken :: JWK -> OAuthRefreshToken -> (Handler r) OAuthRefreshTokenId +verifyRefreshToken key rt = do + eClaims <- liftIO (OAuth.verify' key (unOAuthToken rt)) + case eClaims of + Left _ -> throwStd $ errorToWai @'OAuthInvalidRefreshToken + Right claims -> maybe (throwStd $ errorToWai @'OAuthInvalidRefreshToken) pure $ hcsSub claims createAccessTokenWithAuthCode :: (Member Now r, Member Jwk r) => OAuthAccessTokenRequest -> (Handler r) OAuthAccessTokenResponse createAccessTokenWithAuthCode req = do @@ -142,7 +147,6 @@ createAccessTokenWithAuthCode req = do unless (uri == oatRedirectUri req) $ throwStd $ errorToWai @'OAuthRedirectUrlMissMatch unless (ocRedirectUrl oauthClient == oatRedirectUri req) $ throwStd $ errorToWai @'OAuthRedirectUrlMissMatch - unless (cid == oatClientId req) $ throwStd $ errorToWai @'OAuthInvalidClientCredentials unlessM (verifyClientSecret (oatClientSecret req) (ocId oauthClient)) $ throwStd $ errorToWai @'OAuthInvalidClientCredentials key <- signingKey @@ -226,6 +230,17 @@ verifyClientSecret secret cid = do rand32Bytes :: MonadIO m => m AsciiBase16 rand32Bytes = liftIO . fmap encodeBase16 $ randBytes 32 +revokeRefreshToken :: Member Jwk r => OAuthRevokeRefreshTokenRequest -> (Handler r) () +revokeRefreshToken req = do + key <- signingKey + info <- verifyTokenAndGetInfo key (ortrRefreshToken req) + let uid = oriUserId info + cid = oriClientId info + void $ getOAuthClient uid cid >>= maybe (throwStd $ errorToWai @'OAuthClientNotFound) pure + + unlessM (verifyClientSecret (ortrClientSecret req) cid) $ throwStd $ errorToWai @'OAuthInvalidClientCredentials + lift $ wrapClient $ deleteOAuthRefreshToken info + -------------------------------------------------------------------------------- -- DB diff --git a/services/brig/src/Brig/App.hs b/services/brig/src/Brig/App.hs index 67768e0000..4d396efaa7 100644 --- a/services/brig/src/Brig/App.hs +++ b/services/brig/src/Brig/App.hs @@ -233,8 +233,12 @@ newEnv o = do SqsQueue q -> SqsQueue <$> AWS.getQueueUrl (aws ^. AWS.amazonkaEnv) q mSFTEnv <- mapM Calling.mkSFTEnv $ Opt.sft o prekeyLocalLock <- case Opt.randomPrekeys o of - Just True -> Just <$> newMVar () - _ -> pure Nothing + Just True -> do + Log.info lgr $ Log.msg (Log.val "randomPrekeys: active") + Just <$> newMVar () + _ -> do + Log.info lgr $ Log.msg (Log.val "randomPrekeys: not active; using dynamoDB instead.") + pure Nothing kpLock <- newMVar () pure $! Env diff --git a/services/brig/src/Brig/Effects/GalleyProvider.hs b/services/brig/src/Brig/Effects/GalleyProvider.hs index e92c14e720..afbe6b5875 100644 --- a/services/brig/src/Brig/Effects/GalleyProvider.hs +++ b/services/brig/src/Brig/Effects/GalleyProvider.hs @@ -7,6 +7,7 @@ import Brig.Team.Types (ShowOrHideInvitationUrl (..)) import qualified Data.Currency as Currency import Data.Id import Data.Json.Util (UTCTimeMillis) +import Data.Qualified import qualified Galley.Types.Teams.Intra as Team import Imports import qualified Network.Wai.Utilities.Error as Wai @@ -25,7 +26,7 @@ data GalleyProvider m a where GalleyProvider m () GetConv :: UserId -> - ConvId -> + Local ConvId -> GalleyProvider m (Maybe Conversation) GetTeamConv :: UserId -> diff --git a/services/brig/src/Brig/Effects/GalleyProvider/RPC.hs b/services/brig/src/Brig/Effects/GalleyProvider/RPC.hs index e84ef002e0..a3cb6c2e37 100644 --- a/services/brig/src/Brig/Effects/GalleyProvider/RPC.hs +++ b/services/brig/src/Brig/Effects/GalleyProvider/RPC.hs @@ -18,6 +18,7 @@ import Data.Coerce (coerce) import qualified Data.Currency as Currency import Data.Id import Data.Json.Util (UTCTimeMillis) +import Data.Qualified import Data.Range import qualified Galley.Types.Teams as Team import qualified Galley.Types.Teams.Intra as Team @@ -27,8 +28,10 @@ import Network.HTTP.Types.Status import qualified Network.Wai.Utilities.Error as Wai import Polysemy import Polysemy.Error +import Servant.API (toHeader) import System.Logger (Msg, field, msg, val) import Wire.API.Conversation hiding (Member) +import Wire.API.Routes.Version import Wire.API.Team import qualified Wire.API.Team.Conversation as Conv import Wire.API.Team.Feature @@ -84,7 +87,7 @@ createSelfConv u = do void $ ServiceRPC.request @'Galley POST req where req = - path "/conversations/self" + paths ["v" <> toHeader (maxBound :: Version), "conversations", "self"] . zUser u . expect2xx @@ -97,12 +100,13 @@ getConv :: ] r => UserId -> - ConvId -> + Local ConvId -> Sem r (Maybe Conversation) -getConv usr cnv = do +getConv usr lcnv = do debug $ remote "galley" - . field "conv" (toByteString cnv) + . field "domain" (toByteString (tDomain lcnv)) + . field "conv" (toByteString (tUnqualified lcnv)) . msg (val "Getting conversation") rs <- ServiceRPC.request @'Galley GET req case Bilge.statusCode rs of @@ -110,7 +114,12 @@ getConv usr cnv = do _ -> pure Nothing where req = - paths ["conversations", toByteString' cnv] + paths + [ "v" <> toHeader (maxBound :: Version), + "conversations", + toByteString' (tDomain lcnv), + toByteString' (tUnqualified lcnv) + ] . zUser usr . expect [status200, status404] @@ -137,7 +146,13 @@ getTeamConv usr tid cnv = do _ -> pure Nothing where req = - paths ["teams", toByteString' tid, "conversations", toByteString' cnv] + paths + [ "v" <> toHeader (maxBound :: Version), + "teams", + toByteString' tid, + "conversations", + toByteString' cnv + ] . zUser usr . expect [status200, status404] diff --git a/services/brig/src/Brig/Provider/API.hs b/services/brig/src/Brig/Provider/API.hs index 84ea17a47a..529cc5a776 100644 --- a/services/brig/src/Brig/Provider/API.hs +++ b/services/brig/src/Brig/Provider/API.hs @@ -897,7 +897,8 @@ addBot zuid zcon cid add = do let pid = addBotProvider add let sid = addBotService add -- Get the conversation and check preconditions - cnv <- lift (liftSem $ GalleyProvider.getConv zuid cid) >>= maybeConvNotFound + lcid <- qualifyLocal cid + cnv <- lift (liftSem $ GalleyProvider.getConv zuid lcid) >>= maybeConvNotFound -- Check that the user is a conversation admin and therefore is allowed to add a bot to this conversation. -- Note that this precondition is also checked in the internal galley API, -- but by having this check here we prevent any (useless) data to be written to the database @@ -981,7 +982,8 @@ removeBotH (zusr ::: zcon ::: cid ::: bid) = do removeBot :: Members '[GalleyProvider] r => UserId -> ConnId -> ConvId -> BotId -> (Handler r) (Maybe Public.RemoveBotResponse) removeBot zusr zcon cid bid = do -- Get the conversation and check preconditions - cnv <- lift (liftSem $ GalleyProvider.getConv zusr cid) >>= maybeConvNotFound + lcid <- qualifyLocal cid + cnv <- lift (liftSem $ GalleyProvider.getConv zusr lcid) >>= maybeConvNotFound -- Check that the user is a conversation admin and therefore is allowed to remove a bot from the conversation. -- Note that this precondition is also checked in the internal galley API. -- However, in case we refine the roles model in the future, this check might not be granular enough. diff --git a/services/brig/test/integration/API/OAuth.hs b/services/brig/test/integration/API/OAuth.hs index 59b7bd42d4..240f672f0f 100644 --- a/services/brig/test/integration/API/OAuth.hs +++ b/services/brig/test/integration/API/OAuth.hs @@ -20,7 +20,7 @@ module API.OAuth where import Bilge import Bilge.Assert -import Brig.API.OAuth +import Brig.API.OAuth hiding (verifyRefreshToken) import Brig.Effects.Jwk (readJwk) import Brig.Options import qualified Brig.Options as Opt @@ -102,7 +102,8 @@ tests m db b n o = do test m "wrong client id - fail" $ testRefreshTokenWrongClientId b, test m "wrong client secret - fail" $ testRefreshTokenWrongClientSecret b, test m "wrong grant type - fail" $ testRefreshTokenWrongGrantType b, - test m "expired token - fail" $ testRefreshTokenExpiredToken o b + test m "expired token - fail" $ testRefreshTokenExpiredToken o b, + test m "revoked token - fail" $ testRefreshTokenRevokedToken b ] ] @@ -575,6 +576,20 @@ testRefreshTokenExpiredToken opts brig = const 403 === statusCode const "Forbidden" === statusMessage +testRefreshTokenRevokedToken :: Brig -> Http () +testRefreshTokenRevokedToken brig = do + uid <- userId <$> createUser "alice" brig + let redirectUrl = mkUrl "https://example.com" + let scopes = OAuthScopes $ Set.fromList [SelfRead] + (cid, secret, code) <- generateOAuthClientAndAuthCode brig uid scopes redirectUrl + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid secret code redirectUrl + accessToken <- createOAuthAccessToken brig accessTokenRequest + let refreshAccessTokenRequest = OAuthRefreshAccessTokenRequest OAuthGrantTypeRefreshToken cid secret (oatRefreshToken accessToken) + revokeOAuthRefreshToken brig (OAuthRevokeRefreshTokenRequest cid secret (oatRefreshToken accessToken)) !!! const 200 === statusCode + refreshOAuthAccessToken' brig refreshAccessTokenRequest !!! do + const 403 === statusCode + const "Forbidden" === statusMessage + ------------------------------------------------------------------------------- -- Util @@ -680,6 +695,9 @@ badKey = do mkUrl :: ByteString -> RedirectUrl mkUrl = fromMaybe (error "invalid url") . fromByteString +revokeOAuthRefreshToken :: (MonadIO m, MonadHttp m, HasCallStack) => Brig -> OAuthRevokeRefreshTokenRequest -> m ResponseLBS +revokeOAuthRefreshToken brig req = post (brig . paths ["oauth", "revoke"] . json req) + instance ToHttpApiData a => ToHttpApiData (Bearer a) where toHeader = (<>) "Bearer " . toHeader . unBearer toUrlPiece = T.decodeUtf8 . toHeader diff --git a/services/brig/test/integration/API/Provider.hs b/services/brig/test/integration/API/Provider.hs index 02e73d1ce5..93172e6ad6 100644 --- a/services/brig/test/integration/API/Provider.hs +++ b/services/brig/test/integration/API/Provider.hs @@ -734,7 +734,7 @@ testDeleteConvBotTeam config db brig galley cannon = withTestService config db b svcAssertConvDelete buf quid2 qcid -- Check that the conversation no longer exists forM_ [uid1, uid2] $ \uid -> - getConversation galley uid cid !!! const 404 === statusCode + getConversationQualified galley uid qcid !!! const 404 === statusCode getBotConv galley bid cid !!! const 404 === statusCode testDeleteTeamBotTeam :: Config -> DB.ClientState -> Brig -> Galley -> Cannon -> Http () @@ -758,7 +758,7 @@ testDeleteTeamBotTeam config db brig galley cannon = withTestService config db b forM_ [uid1, uid2] $ \uid -> do void $ retryWhileN 20 (/= Intra.Deleted) (getStatus brig uid) chkStatus brig uid Intra.Deleted - aFewTimes 11 (getConversation galley uid cid) ((== 404) . statusCode) + aFewTimes 11 (getConversationQualified galley uid qcid) ((== 404) . statusCode) -- Check the bot cannot see the conversation either getBotConv galley bid cid !!! const 404 === statusCode @@ -2088,7 +2088,7 @@ testMessageBotUtil quid uc cid pid sid sref buf brig galley cannon = do assertEqual "id" cid (bcnv ^. Ext.botConvId) assertEqual "members" [OtherMember quid Nothing roleNameWireAdmin] (bcnv ^. Ext.botConvMembers) -- The user can identify the bot in the member list - mems <- fmap cnvMembers . responseJsonError =<< getConversation galley uid cid + mems <- fmap cnvMembers . responseJsonError =<< getConversationQualified galley uid qcid let other = listToMaybe (cmOthers mems) liftIO $ do assertEqual "id" (Just buid) (qUnqualified . omQualifiedId <$> other) diff --git a/services/brig/test/integration/API/TeamUserSearch.hs b/services/brig/test/integration/API/TeamUserSearch.hs index a57301bb0f..10e762aa89 100644 --- a/services/brig/test/integration/API/TeamUserSearch.hs +++ b/services/brig/test/integration/API/TeamUserSearch.hs @@ -144,7 +144,7 @@ testEmptyQuerySortedWithPagination :: TestConstraints m => Brig -> m () testEmptyQuerySortedWithPagination brig = do (tid, userId -> ownerId, _) <- createPopulatedBindingTeamWithNamesAndHandles brig 20 refreshIndex brig - let teamUserSearch mPs = executeTeamUserSearchWithMaybeState brig tid ownerId (Just "") Nothing (Just SortByRole) (Just SortOrderAsc) (Just $ unsafeRange 10) mPs + let teamUserSearch mPs = executeTeamUserSearchWithMaybeState brig tid ownerId (Just "") Nothing Nothing Nothing (Just $ unsafeRange 10) mPs searchResultFirst10 <- teamUserSearch Nothing searchResultNext10 <- teamUserSearch (searchPagingState searchResultFirst10) searchResultLast1 <- teamUserSearch (searchPagingState searchResultNext10) diff --git a/services/brig/test/integration/API/User/Auth.hs b/services/brig/test/integration/API/User/Auth.hs index 834bd46a69..3005674924 100644 --- a/services/brig/test/integration/API/User/Auth.hs +++ b/services/brig/test/integration/API/User/Auth.hs @@ -46,7 +46,7 @@ import Data.Handle (Handle (Handle)) import Data.Id import Data.Misc (PlainTextPassword (..)) import Data.Proxy -import Data.Qualified (Qualified (qUnqualified)) +import Data.Qualified import Data.Range (unsafeRange) import qualified Data.Text as Text import Data.Text.Ascii (AsciiChars (validate)) @@ -256,7 +256,7 @@ testNginzLegalHold b g n = do get (apiVersion "v1" . n . paths ["legalhold", "conversations", toByteString' (qUnqualified qconv)] . header "Authorization" ("Bearer " <> toByteString' t)) !!! const 200 === statusCode - get (n . paths ["conversations", toByteString' (qUnqualified qconv)] . header "Authorization" ("Bearer " <> toByteString' t)) !!! const 200 === statusCode + get (apiVersion "v2" . n . paths ["conversations", toByteString' (qUnqualified qconv)] . header "Authorization" ("Bearer " <> toByteString' t)) !!! const 200 === statusCode -- | Corner case for 'testNginz': when upgrading a wire backend from the old behavior (setting -- cookie domain to eg. @*.wire.com@) to the new behavior (leaving cookie domain empty, diff --git a/services/brig/test/integration/API/User/Client.hs b/services/brig/test/integration/API/User/Client.hs index d3171c6ee4..77ccdb1e2f 100644 --- a/services/brig/test/integration/API/User/Client.hs +++ b/services/brig/test/integration/API/User/Client.hs @@ -50,6 +50,7 @@ import Data.Text.Ascii (AsciiChars (validate)) import qualified Data.Vector as Vec import Imports import qualified Network.Wai.Utilities.Error as Error +import qualified System.Logger as Log import Test.QuickCheck (arbitrary, generate) import Test.Tasty hiding (Timeout) import Test.Tasty.Cannon hiding (Cannon) @@ -103,6 +104,9 @@ tests _cl _at opts p db b c g = test p "get /clients - 200" $ testListClients b, test p "get /clients/:client/prekeys - 200" $ testListPrekeyIds b, test p "post /clients - 400" $ testTooManyClients opts b, + test p "client/prekeys not empty" $ testPrekeysNotEmptyRandomPrekeys opts b, + test p "lastprekeys not bogus" $ testRegularPrekeysCannotBeSentAsLastPrekeys b, + test p "lastprekeys not bogus during update" $ testRegularPrekeysCannotBeSentAsLastPrekeysDuringUpdate b, test p "delete /clients/:client - 200 (pwd)" $ testRemoveClient True b c, test p "delete /clients/:client - 200 (no pwd)" $ testRemoveClient False b c, test p "delete /clients/:client - 400 (short pwd)" $ testRemoveClientShortPwd b, @@ -689,6 +693,61 @@ testTooManyClients opts brig = do const (Just "too-many-clients") === fmap Error.label . responseJsonMaybe const (Just "application/json") === getHeader "Content-Type" +-- Ensure that the list of prekeys for a user does not become empty, and the +-- last resort prekey keeps being returned if it's the only key left. +-- Test with featureFlag randomPrekeys=true +testPrekeysNotEmptyRandomPrekeys :: Opt.Opts -> Brig -> Http () +testPrekeysNotEmptyRandomPrekeys opts brig = do + -- Run the test for randomPrekeys (not dynamoDB locking) + let newOpts = opts {Opt.randomPrekeys = Just True} + ensurePrekeysNotEmpty newOpts brig + +ensurePrekeysNotEmpty :: Opt.Opts -> Brig -> Http () +ensurePrekeysNotEmpty opts brig = withSettingsOverrides opts $ do + lgr <- Log.new Log.defSettings + uid <- userId <$> randomUser brig + -- Create a client with 1 regular prekey and 1 last resort prekey + c <- responseJsonError =<< addClient brig uid (defNewClient PermanentClientType [somePrekeys !! 10] (someLastPrekeys !! 10)) + -- Claim the first regular one + _rs1 <- getPreKey brig uid uid (clientId c) responseJsonMaybe rs2 + liftIO $ assertEqual "last prekey rs2" (Just lastPrekeyId) pId2 + liftIO $ Log.warn lgr (Log.msg (Log.val "First claim of last resort successful, claim again...")) + -- Claim again; this should (again) give the last resort one + rs3 <- getPreKey brig uid uid (clientId c) responseJsonMaybe rs3 + liftIO $ assertEqual "last prekey rs3" (Just lastPrekeyId) pId3 + +testRegularPrekeysCannotBeSentAsLastPrekeys :: Brig -> Http () +testRegularPrekeysCannotBeSentAsLastPrekeys brig = do + uid <- userId <$> randomUser brig + -- The parser should reject a normal prekey in the lastPrekey field + addClient brig uid (defNewClient PermanentClientType [head somePrekeys] fakeLastPrekey) !!! const 400 === statusCode + +testRegularPrekeysCannotBeSentAsLastPrekeysDuringUpdate :: Brig -> Http () +testRegularPrekeysCannotBeSentAsLastPrekeysDuringUpdate brig = do + uid <- userId <$> randomUser brig + c <- responseJsonError =<< addClient brig uid (defNewClient PermanentClientType [head somePrekeys] (someLastPrekeys !! 11)) >= ucConvId of Nothing -> liftIO $ assertFailure "incomplete connection" - Just (Qualified cnv _) -> do - getConversation galley uid1 cnv !!! do + Just qcnv -> do + getConversationQualified galley uid1 qcnv !!! do const 200 === statusCode const (Just One2OneConv) === fmap cnvType . responseJsonMaybe - getConversation galley uid2 cnv !!! do + getConversationQualified galley uid2 qcnv !!! do const 200 === statusCode const (Just One2OneConv) === fmap cnvType . responseJsonMaybe @@ -304,32 +304,32 @@ testCancelConnection2 brig galley = do rsp <- putConnection brig uid1 uid2 Cancelled do conv <- responseJsonMaybe rs Just (cnvType conv) -- A is a past member, cannot see the conversation - getConversation galley uid1 cnv !!! do + getConversationQualified galley uid1 qcnv !!! do const 403 === statusCode -- A finally accepts putConnection brig uid1 uid2 Accepted !!! const 200 === statusCode assertConnections brig uid1 [ConnectionStatus uid1 uid2 Accepted] assertConnections brig uid2 [ConnectionStatus uid2 uid1 Accepted] - getConversation galley uid1 cnv !!! do + getConversationQualified galley uid1 qcnv !!! do const 200 === statusCode - getConversation galley uid2 cnv !!! do + getConversationQualified galley uid2 qcnv !!! do const 200 === statusCode testCancelConnectionQualified2 :: Brig -> Galley -> Http () @@ -483,10 +483,10 @@ testBlockAndResendConnection brig galley = do assertConnections brig uid1 [ConnectionStatus uid1 uid2 Accepted] assertConnections brig uid2 [ConnectionStatus uid2 uid1 Blocked] -- B never accepted and thus does not see the conversation - let Just (Qualified cnv _) = ucConvId =<< responseJsonMaybe rsp - getConversation galley uid2 cnv !!! const 403 === statusCode + let Just qcnv = ucConvId =<< responseJsonMaybe rsp + getConversationQualified galley uid2 qcnv !!! const 403 === statusCode -- A can see the conversation and is a current member - getConversation galley uid1 cnv !!! do + getConversationQualified galley uid1 qcnv !!! do const 200 === statusCode testBlockAndResendConnectionQualified :: Brig -> Galley -> Http () diff --git a/services/brig/test/integration/Util.hs b/services/brig/test/integration/Util.hs index 649195714d..9e2b99dc8d 100644 --- a/services/brig/test/integration/Util.hs +++ b/services/brig/test/integration/Util.hs @@ -687,7 +687,13 @@ defNewClientWithVerificationCode mbCode ty pks lpk = newClientVerificationCode = mbCode } -getPreKey :: Brig -> UserId -> UserId -> ClientId -> Http ResponseLBS +getPreKey :: + (MonadIO m, MonadCatch m, MonadFail m, MonadHttp m, HasCallStack) => + Brig -> + UserId -> + UserId -> + ClientId -> + m ResponseLBS getPreKey brig zusr u c = get $ apiVersion "v1" @@ -710,13 +716,6 @@ getTeamMember u tid galley = . expect2xx ) -getConversation :: (MonadIO m, MonadHttp m) => Galley -> UserId -> ConvId -> m ResponseLBS -getConversation galley usr cnv = - get $ - galley - . paths ["conversations", toByteString' cnv] - . zAuthAccess usr "conn" - getConversationQualified :: (MonadIO m, MonadHttp m) => Galley -> UserId -> Qualified ConvId -> m ResponseLBS getConversationQualified galley usr cnv = get $ diff --git a/services/galley/src/Galley/API/Public/Conversation.hs b/services/galley/src/Galley/API/Public/Conversation.hs index c080d83b04..800c9f4654 100644 --- a/services/galley/src/Galley/API/Public/Conversation.hs +++ b/services/galley/src/Galley/API/Public/Conversation.hs @@ -32,6 +32,7 @@ conversationAPI :: API ConversationAPI GalleyEffects conversationAPI = mkNamedAPI @"get-unqualified-conversation" getUnqualifiedConversation <@> mkNamedAPI @"get-unqualified-conversation-legalhold-alias" getUnqualifiedConversation + <@> mkNamedAPI @"get-conversation@v2" (callsFed getConversation) <@> mkNamedAPI @"get-conversation" (callsFed getConversation) <@> mkNamedAPI @"get-conversation-roles" getConversationRoles <@> mkNamedAPI @"get-group-info" (callsFed getGroupInfo) diff --git a/services/galley/test/integration/API.hs b/services/galley/test/integration/API.hs index f193cbfee6..7c001655ca 100644 --- a/services/galley/test/integration/API.hs +++ b/services/galley/test/integration/API.hs @@ -98,6 +98,8 @@ import Wire.API.Message import qualified Wire.API.Message as Message import Wire.API.Routes.MultiTablePaging import Wire.API.Routes.Named +import Wire.API.Routes.Version +import Wire.API.Routes.Versioned import qualified Wire.API.Team.Feature as Public import qualified Wire.API.Team.Member as Teams import Wire.API.User @@ -125,6 +127,7 @@ tests s = "Main Conversations API" [ test s "status" status, test s "metrics" metrics, + test s "fetch conversation by qualified ID (v2)" testGetConvQualifiedV2, test s "create Proteus conversation" postProteusConvOk, test s "create conversation with remote users" postConvWithRemoteUsersOk, test s "get empty conversations" getConvsOk, @@ -270,24 +273,46 @@ metrics = do -- Should contain the request duration metric in its output const (Just "TYPE http_request_duration_seconds histogram") =~= responseBody +testGetConvQualifiedV2 :: TestM () +testGetConvQualifiedV2 = do + alice <- randomUser + bob <- randomUser + connectUsers alice (list1 bob []) + conv <- + responseJsonError + =<< postConvQualified + alice + defNewProteusConv + { newConvUsers = [bob] + } + do rsp <- postConv alice [bob, jane] (Just nameMaxSize) [] Nothing Nothing getConv usr cnv + convView cnv usr = do + r <- getConv usr cnv responseJsonError r checkWs qalice (cnv, ws) = WS.awaitMatch (5 # Second) ws $ \n -> do ntfTransient n @?= False let e = List1.head (WS.unpackPayload n) @@ -318,7 +343,8 @@ postConvWithRemoteUsersOk = do withTempMockFederator (const ()) $ postConvQualified alice defNewProteusConv {newConvName = checked nameMaxSize, newConvQualifiedUsers = [qAlex, qAmy, qChad, qCharlie, qDee]} getConv usr cnv + convView cnv usr = do + r <- getConv usr cnv responseJsonError r checkWs qalice (cnv, ws) = WS.awaitMatch (5 # Second) ws $ \n -> do ntfTransient n @?= False let e = List1.head (WS.unpackPayload n) @@ -2141,14 +2169,13 @@ postSelfConvOk = do postO2OConvOk :: TestM () postO2OConvOk = do - qalice <- randomQualifiedUser - let alice = qUnqualified qalice - bob <- randomUser + (alice, qalice) <- randomUserTuple + (bob, qbob) <- randomUserTuple connectUsers alice (singleton bob) a <- postO2OConv alice bob Nothing postConnectConv alice bob "Alice" "come to zeta!" Nothing - putConvAccept bob cnv !!! const 200 === statusCode - getConv alice cnv !!! do + qcnv <- decodeQualifiedConvId <$> postConnectConv alice bob "Alice" "come to zeta!" Nothing + putConvAccept bob (qUnqualified qcnv) !!! const 200 === statusCode + getConvQualified alice qcnv !!! do const 200 === statusCode const (Just One2OneConv) === fmap cnvType . responseJsonUnsafe - getConv bob cnv !!! do + getConvQualified bob qcnv !!! do const 200 === statusCode const (Just One2OneConv) === fmap cnvType . responseJsonUnsafe @@ -2223,7 +2250,7 @@ postMutualConnectConvOk = do getConv bob convId + cnvX <- responseJsonUnsafeWithMsg "conversation" <$> getConvQualified bob qconvId liftIO $ do ConnectConv @=? cnvType cnvX Just "B" @=? cnvName cnvX privateAccess @=? cnvAccess cnvX -- Alice accepts, finally turning it into a 1-1 putConvAccept alice convId !!! const 200 === statusCode - cnv4 <- responseJsonUnsafeWithMsg "conversation" <$> getConv alice convId + cnv4 <- responseJsonUnsafeWithMsg "conversation" <$> getConvQualified alice qconvId liftIO $ do One2OneConv @=? cnvType cnv4 Just "B" @=? cnvName cnv4 @@ -2286,29 +2314,30 @@ putBlockConvOk = do alice <- randomUser bob <- randomUser conv <- responseJsonUnsafeWithMsg "conversation" <$> postConnectConv alice bob "Alice" "connect with me!" (Just "me@me.com") - let convId = qUnqualified . cnvQualifiedId $ conv - getConv alice convId !!! const 200 === statusCode - getConv bob convId !!! const 403 === statusCode + let qconvId = cnvQualifiedId conv + let convId = qUnqualified qconvId + getConvQualified alice qconvId !!! const 200 === statusCode + getConvQualified bob qconvId !!! const 403 === statusCode put (g . paths ["/i/conversations", toByteString' convId, "block"] . zUser bob) !!! const 200 === statusCode -- A is still the only member of the 1-1 - getConv alice convId !!! do + getConvQualified alice qconvId !!! do const 200 === statusCode const (cnvMembers conv) === cnvMembers . responseJsonUnsafeWithMsg "conversation" -- B accepts the conversation by unblocking put (g . paths ["/i/conversations", toByteString' convId, "unblock"] . zUser bob) !!! const 200 === statusCode - getConv bob convId !!! const 200 === statusCode + getConvQualified bob qconvId !!! const 200 === statusCode -- B blocks A in the 1-1 put (g . paths ["/i/conversations", toByteString' convId, "block"] . zUser bob) !!! const 200 === statusCode -- B no longer sees the 1-1 - getConv bob convId !!! const 403 === statusCode + getConvQualified bob qconvId !!! const 403 === statusCode -- B unblocks A in the 1-1 put (g . paths ["/i/conversations", toByteString' convId, "unblock"] . zUser bob) !!! const 200 === statusCode -- B sees the blocked 1-1 again - getConv bob convId !!! do + getConvQualified bob qconvId !!! do const 200 === statusCode getConvOk :: TestM () @@ -2677,7 +2706,8 @@ testAddRemoteMemberFederationDisabled :: TestM () testAddRemoteMemberFederationDisabled = do alice <- randomUser remoteBob <- flip Qualified (Domain "some-remote-backend.example.com") <$> randomId - convId <- decodeConvId <$> postConv alice [] (Just "remote gossip") [] Nothing Nothing + qconvId <- decodeQualifiedConvId <$> postConv alice [] (Just "remote gossip") [] Nothing Nothing + let convId = qUnqualified qconvId connectWithRemoteUser alice remoteBob -- federator endpoint not configured is equivalent to federation being disabled @@ -2689,14 +2719,15 @@ testAddRemoteMemberFederationDisabled = do const (Right "federation-not-enabled") === fmap label . responseJsonEither -- the member is not actually added to the conversation - conv <- responseJsonError =<< getConv alice convId randomId - convId <- decodeConvId <$> postConv alice [] (Just "remote gossip") [] Nothing Nothing + qconvId <- decodeQualifiedConvId <$> postConv alice [] (Just "remote gossip") [] Nothing Nothing + let convId = qUnqualified qconvId connectWithRemoteUser alice remoteBob -- federator endpoint being configured in brig and/or galley, but not being @@ -2711,7 +2742,7 @@ testAddRemoteMemberFederationUnavailable = do -- in this case, we discover that federation is unavailable too late, and the -- member has already been added to the conversation - conv <- responseJsonError =<< getConv alice convId assertFailure $ "Unexpected event data: " ++ show x -- Verify new member state - rs <- getConv bob conv responseJsonUnsafe rs liftIO $ do assertBool "user" (isJust bob') @@ -3496,13 +3527,13 @@ putReceiptModeOk = do let qcnv = Qualified cnv (qDomain qalice) WS.bracketR3 c alice bob jane $ \(_wsA, wsB, _wsJ) -> do -- By default, nothing is set - getConv alice cnv !!! do + getConvQualified alice qcnv !!! do const 200 === statusCode const (Just Nothing) === fmap cnvReceiptMode . responseJsonUnsafe -- Set receipt mode putReceiptMode alice cnv (ReceiptMode 0) !!! const 200 === statusCode -- Ensure the field is properly set - getConv alice cnv !!! do + getConvQualified alice qcnv !!! do const 200 === statusCode const (Just $ Just (ReceiptMode 0)) === fmap cnvReceiptMode . responseJsonUnsafe void . liftIO $ checkWs qalice (qcnv, wsB) @@ -3511,11 +3542,11 @@ putReceiptModeOk = do -- No event should have been generated WS.assertNoEvent (1 # Second) [wsB] -- Ensure that the new field remains unchanged - getConv alice cnv !!! do + getConvQualified alice qcnv !!! do const 200 === statusCode const (Just $ Just (ReceiptMode 0)) === fmap cnvReceiptMode . responseJsonUnsafe - cnv' <- decodeConvId <$> postConvWithReceipt alice [bob, jane] (Just "gossip") [] Nothing Nothing (ReceiptMode 0) - getConv alice cnv' !!! do + qcnv' <- decodeQualifiedConvId <$> postConvWithReceipt alice [bob, jane] (Just "gossip") [] Nothing Nothing (ReceiptMode 0) + getConvQualified alice qcnv' !!! do const 200 === statusCode const (Just (Just (ReceiptMode 0))) === fmap cnvReceiptMode . responseJsonUnsafe where @@ -3775,11 +3806,9 @@ removeUserNoFederation = do connectUsers alice' (list1 bob' [carl']) - conv1 <- decodeConvId <$> postConv alice' [bob'] (Just "gossip") [] Nothing Nothing - conv2 <- decodeConvId <$> postConv alice' [bob', carl'] (Just "gossip2") [] Nothing Nothing - conv3 <- decodeConvId <$> postConv alice' [carl'] (Just "gossip3") [] Nothing Nothing - let qconv1 = Qualified conv1 (qDomain bob) - qconv2 = Qualified conv2 (qDomain bob) + qconv1 <- decodeQualifiedConvId <$> postConv alice' [bob'] (Just "gossip") [] Nothing Nothing + qconv2 <- decodeQualifiedConvId <$> postConv alice' [bob', carl'] (Just "gossip2") [] Nothing Nothing + qconv3 <- decodeQualifiedConvId <$> postConv alice' [carl'] (Just "gossip3") [] Nothing Nothing WS.bracketR3 c alice' bob' carl' $ \(wsA, wsB, wsC) -> do deleteUser bob' !!! const 200 === statusCode @@ -3791,9 +3820,9 @@ removeUserNoFederation = do WS.assertMatchN (5 # Second) [wsA, wsB, wsC] $ wsAssertMembersLeave qconv2 bob [bob] -- Check memberships - mems1 <- fmap cnvMembers . responseJsonUnsafe <$> getConv alice' conv1 - mems2 <- fmap cnvMembers . responseJsonUnsafe <$> getConv alice' conv2 - mems3 <- fmap cnvMembers . responseJsonUnsafe <$> getConv alice' conv3 + mems1 <- fmap cnvMembers . responseJsonUnsafe <$> getConvQualified alice' qconv1 + mems2 <- fmap cnvMembers . responseJsonUnsafe <$> getConvQualified alice' qconv2 + mems3 <- fmap cnvMembers . responseJsonUnsafe <$> getConvQualified alice' qconv3 let other u = find ((== u) . omQualifiedId) . cmOthers liftIO $ do (mems1 >>= other bob) @?= Nothing @@ -3828,17 +3857,14 @@ removeUser = do connectWithRemoteUser alice' dwight connectWithRemoteUser alexDel' dory - convA1 <- decodeConvId <$> postConv alice' [alexDel'] (Just "gossip") [] Nothing Nothing - convA2 <- decodeConvId <$> postConvWithRemoteUsers alice' defNewProteusConv {newConvQualifiedUsers = [alexDel, amy, berta, dwight]} - convA3 <- decodeConvId <$> postConv alice' [amy'] (Just "gossip3") [] Nothing Nothing - convA4 <- decodeConvId <$> postConvWithRemoteUsers alice' defNewProteusConv {newConvQualifiedUsers = [alexDel, bart, carl]} + qconvA1 <- decodeQualifiedConvId <$> postConv alice' [alexDel'] (Just "gossip") [] Nothing Nothing + qconvA2 <- decodeQualifiedConvId <$> postConvWithRemoteUsers alice' defNewProteusConv {newConvQualifiedUsers = [alexDel, amy, berta, dwight]} + qconvA3 <- decodeQualifiedConvId <$> postConv alice' [amy'] (Just "gossip3") [] Nothing Nothing + qconvA4 <- decodeQualifiedConvId <$> postConvWithRemoteUsers alice' defNewProteusConv {newConvQualifiedUsers = [alexDel, bart, carl]} convB1 <- randomId -- a remote conversation at 'bDomain' that Alice, AlexDel and Bart will be in convB2 <- randomId -- a remote conversation at 'bDomain' that AlexDel and Bart will be in convC1 <- randomId -- a remote conversation at 'cDomain' that AlexDel and Carl will be in convD1 <- randomId -- a remote conversation at 'cDomain' that AlexDel and Dory will be in - let qconvA1 = Qualified convA1 (qDomain alexDel) - qconvA2 = Qualified convA2 (qDomain alexDel) - now <- liftIO getCurrentTime fedGalleyClient <- view tsFedGalleyClient let nc cid creator quids = @@ -3912,12 +3938,12 @@ removeUser = do let bConvUpdateRPCs = filter (matchFedRequest bDomain "on-conversation-updated") fedRequests bConvUpdates <- mapM (assertRight . eitherDecode . frBody) bConvUpdateRPCs - bConvUpdatesA2 <- assertOne $ filter (\cu -> cuConvId cu == convA2) bConvUpdates + bConvUpdatesA2 <- assertOne $ filter (\cu -> cuConvId cu == qUnqualified qconvA2) bConvUpdates cuOrigUserId bConvUpdatesA2 @?= alexDel cuAction bConvUpdatesA2 @?= SomeConversationAction (sing @'ConversationLeaveTag) () cuAlreadyPresentUsers bConvUpdatesA2 @?= [qUnqualified berta] - bConvUpdatesA4 <- assertOne $ filter (\cu -> cuConvId cu == convA4) bConvUpdates + bConvUpdatesA4 <- assertOne $ filter (\cu -> cuConvId cu == qUnqualified qconvA4) bConvUpdates cuOrigUserId bConvUpdatesA4 @?= alexDel cuAction bConvUpdatesA4 @?= SomeConversationAction (sing @'ConversationLeaveTag) () cuAlreadyPresentUsers bConvUpdatesA4 @?= [qUnqualified bart] @@ -3925,7 +3951,7 @@ removeUser = do liftIO $ do cConvUpdateRPC <- assertOne $ filter (matchFedRequest cDomain "on-conversation-updated") fedRequests Right convUpdate <- pure . eitherDecode . frBody $ cConvUpdateRPC - cuConvId convUpdate @?= convA4 + cuConvId convUpdate @?= qUnqualified qconvA4 cuOrigUserId convUpdate @?= alexDel cuAction convUpdate @?= SomeConversationAction (sing @'ConversationLeaveTag) () cuAlreadyPresentUsers convUpdate @?= [qUnqualified carl] @@ -3933,16 +3959,16 @@ removeUser = do liftIO $ do dConvUpdateRPC <- assertOne $ filter (matchFedRequest dDomain "on-conversation-updated") fedRequests Right convUpdate <- pure . eitherDecode . frBody $ dConvUpdateRPC - cuConvId convUpdate @?= convA2 + cuConvId convUpdate @?= qUnqualified qconvA2 cuOrigUserId convUpdate @?= alexDel cuAction convUpdate @?= SomeConversationAction (sing @'ConversationLeaveTag) () cuAlreadyPresentUsers convUpdate @?= [qUnqualified dwight] -- Check memberships - mems1 <- fmap cnvMembers . responseJsonError =<< getConv alice' convA1 - mems2 <- fmap cnvMembers . responseJsonError =<< getConv alice' convA2 - mems3 <- fmap cnvMembers . responseJsonError =<< getConv alice' convA3 - mems4 <- fmap cnvMembers . responseJsonError =<< getConv alice' convA4 + mems1 <- fmap cnvMembers . responseJsonError =<< getConvQualified alice' qconvA1 + mems2 <- fmap cnvMembers . responseJsonError =<< getConvQualified alice' qconvA2 + mems3 <- fmap cnvMembers . responseJsonError =<< getConvQualified alice' qconvA3 + mems4 <- fmap cnvMembers . responseJsonError =<< getConvQualified alice' qconvA4 let findOther u = find ((== u) . omQualifiedId) . cmOthers liftIO $ do findOther alexDel mems1 @?= Nothing diff --git a/services/galley/test/integration/API/Federation.hs b/services/galley/test/integration/API/Federation.hs index 23bb350eed..e33915ce83 100644 --- a/services/galley/test/integration/API/Federation.hs +++ b/services/galley/test/integration/API/Federation.hs @@ -1064,8 +1064,7 @@ updateConversationByRemoteAdmin = do postConvQualified alice defNewProteusConv {newConvName = checked convName, newConvQualifiedUsers = [qbob, qcharlie]} viewFederationDomain + [(alice, qalice), (bob, qbob), (jane, qjane)] <- replicateM 3 randomUserTuple connectUsers alice (list1 bob [jane]) rsp <- postConv alice [bob, jane] Nothing [] Nothing mtimer viewFederationDomain + [(alice, qalice), (bob, qbob), (jane, qjane)] <- replicateM 3 randomUserTuple connectUsers alice (list1 bob [jane]) rsp <- postConv alice [bob, jane] Nothing [] Nothing Nothing viewFederationDomain + [(alice, qalice), (bob, qbob), (jane, qjane)] <- replicateM 3 randomUserTuple connectUsers alice (list1 bob [jane]) rsp <- postConv alice [bob, jane] Nothing [] Nothing Nothing cid -- Try to change the timer (as a non admin, guest user) and observe failure putMessageTimerUpdate guest cid (ConversationMessageTimerUpdate timer1sec) !!! do const 403 === statusCode const "action-denied" === (label . responseJsonUnsafeWithMsg "error label") - getConv guest cid + getConvQualified guest qcid !!! const Nothing === (cnvMessageTimer <=< responseJsonUnsafe) -- Try to change the timer (as a non admin, team member) and observe failure too putMessageTimerUpdate member cid (ConversationMessageTimerUpdate timer1sec) !!! do @@ -203,43 +200,40 @@ messageTimerChangeWithoutAllowedAction = do -- Finally try to change the timer (as an admin) and observe success putMessageTimerUpdate owner cid (ConversationMessageTimerUpdate timer1sec) !!! do const 200 === statusCode - getConv guest cid + getConvQualified guest qcid !!! const timer1sec === (cnvMessageTimer <=< responseJsonUnsafe) messageTimerChangeO2O :: TestM () messageTimerChangeO2O = do -- Create a 1:1 conversation - [alice, bob] <- randomUsers 2 - qAlice <- Qualified alice <$> viewFederationDomain + [(alice, qalice), (bob, qbob)] <- replicateM 2 randomUserTuple connectUsers alice (singleton bob) rsp <- postO2OConv alice bob Nothing viewFederationDomain + [(alice, qalice), (bob, qbob)] <- replicateM 2 randomUserTuple connectUsers alice (singleton bob) rsp <- postConv alice [bob] Nothing [] Nothing Nothing do let update = ConversationMessageTimerUpdate timer1sec - qcid = Qualified cid localDomain - qalice = Qualified alice localDomain putMessageTimerUpdate alice cid update !!! const 200 === statusCode void . liftIO $ diff --git a/services/galley/test/integration/API/Roles.hs b/services/galley/test/integration/API/Roles.hs index ed769f7d96..b5ed4cb27c 100644 --- a/services/galley/test/integration/API/Roles.hs +++ b/services/galley/test/integration/API/Roles.hs @@ -84,8 +84,8 @@ handleConversationRoleAdmin = do localDomain <- viewFederationDomain c <- view tsCannon (alice, qalice) <- randomUserTuple - bob <- randomUser - chuck <- randomUser + (bob, qbob) <- randomUserTuple + (chuck, qchuck) <- randomUserTuple (eve, qeve) <- randomUserTuple (jack, qjack) <- randomUserTuple connectUsers alice (list1 bob [chuck, eve, jack]) @@ -94,7 +94,7 @@ handleConversationRoleAdmin = do let role = roleNameWireAdmin cid <- WS.bracketR3 c alice bob chuck $ \(wsA, wsB, wsC) -> do rsp <- postConvWithRole alice [bob, chuck] (Just "gossip") [] Nothing Nothing role - void $ assertConvWithRole rsp RegularConv alice qalice [bob, chuck] (Just "gossip") Nothing role + void $ assertConvWithRole rsp RegularConv alice qalice [qbob, qchuck] (Just "gossip") Nothing role let cid = decodeConvId rsp qcid = Qualified cid localDomain -- Make sure everyone gets the correct event @@ -123,10 +123,9 @@ handleConversationRoleMember :: TestM () handleConversationRoleMember = do localDomain <- viewFederationDomain c <- view tsCannon - alice <- randomUser - let qalice = Qualified alice localDomain - bob <- randomUser - chuck <- randomUser + (alice, qalice) <- randomUserTuple + (bob, qbob) <- randomUserTuple + (chuck, qchuck) <- randomUserTuple eve <- randomUser let qeve = Qualified eve localDomain jack <- randomUser @@ -136,7 +135,7 @@ handleConversationRoleMember = do let role = roleNameWireMember cid <- WS.bracketR3 c alice bob chuck $ \(wsA, wsB, wsC) -> do rsp <- postConvWithRole alice [bob, chuck] (Just "gossip") [] Nothing Nothing role - void $ assertConvWithRole rsp RegularConv alice qalice [bob, chuck] (Just "gossip") Nothing role + void $ assertConvWithRole rsp RegularConv alice qalice [qbob, qchuck] (Just "gossip") Nothing role let cid = decodeConvId rsp qcid = Qualified cid localDomain -- Make sure everyone gets the correct event diff --git a/services/galley/test/integration/API/Teams.hs b/services/galley/test/integration/API/Teams.hs index 72cb13c55b..63c416f644 100644 --- a/services/galley/test/integration/API/Teams.hs +++ b/services/galley/test/integration/API/Teams.hs @@ -794,7 +794,7 @@ testCreateTeamMLSConv = do Nothing Nothing Nothing - Right conv <- responseJsonError <$> getConv owner (tUnqualified lConvId) + Right conv <- responseJsonError <$> getConvQualified owner (tUntagged lConvId) liftIO $ do assertEqual "protocol mismatch" ProtocolMLSTag (protocolTag (cnvProtocol conv)) checkConvCreateEvent (tUnqualified lConvId) wsOwner diff --git a/services/galley/test/integration/API/Util.hs b/services/galley/test/integration/API/Util.hs index adfc6d1b29..11255a97e0 100644 --- a/services/galley/test/integration/API/Util.hs +++ b/services/galley/test/integration/API/Util.hs @@ -1033,12 +1033,43 @@ listConvs u req = do . zType "access" . json req -getConv :: (MonadIO m, MonadHttp m, HasGalley m, HasCallStack) => UserId -> ConvId -> m ResponseLBS +getConv :: + ( MonadIO m, + MonadHttp m, + MonadReader TestSetup m, + HasCallStack + ) => + UserId -> + ConvId -> + m ResponseLBS getConv u c = do - g <- viewGalley + g <- view tsUnversionedGalley + get $ + g + . paths ["v2", "conversations", toByteString' c] + . zUser u + . zConn "conn" + . zType "access" + +getConvQualifiedV2 :: + ( Monad m, + MonadReader TestSetup m, + MonadHttp m, + MonadIO m + ) => + UserId -> + Qualified ConvId -> + m ResponseLBS +getConvQualifiedV2 u qcnv = do + g <- view tsUnversionedGalley get $ g - . paths ["conversations", toByteString' c] + . paths + [ "v2", + "conversations", + toByteString' (qDomain qcnv), + toByteString' (qUnqualified qcnv) + ] . zUser u . zConn "conn" . zType "access" @@ -1531,61 +1562,13 @@ assertConv :: ConvType -> UserId -> Qualified UserId -> - [UserId] -> + [Qualified UserId] -> Maybe Text -> Maybe Milliseconds -> - TestM ConvId + TestM (Qualified ConvId) assertConv r t c s us n mt = assertConvWithRole r t c s us n mt roleNameWireAdmin assertConvWithRole :: - HasCallStack => - Response (Maybe Lazy.ByteString) -> - ConvType -> - UserId -> - Qualified UserId -> - [UserId] -> - Maybe Text -> - Maybe Milliseconds -> - RoleName -> - TestM ConvId -assertConvWithRole r t c s us n mt role = do - cId <- fromBS $ getHeader' "Location" r - let cnv = responseJsonMaybe @Conversation r - let _self = cmSelf . cnvMembers <$> cnv - let others = cmOthers . cnvMembers <$> cnv - liftIO $ do - assertEqual "id" (Just cId) (qUnqualified . cnvQualifiedId <$> cnv) - assertEqual "name" n (cnv >>= cnvName) - assertEqual "type" (Just t) (cnvType <$> cnv) - assertEqual "creator" (Just c) (cnvCreator <$> cnv) - assertEqual "message_timer" (Just mt) (cnvMessageTimer <$> cnv) - assertEqual "self" (Just s) (memId <$> _self) - assertEqual "others" (Just . Set.fromList $ us) (Set.fromList . map (qUnqualified . omQualifiedId) . toList <$> others) - assertEqual "creator is always and admin" (Just roleNameWireAdmin) (memConvRoleName <$> _self) - assertBool "others role" (all (== role) $ maybe (error "Cannot be null") (map omConvRoleName . toList) others) - assertBool "otr muted ref not empty" (isNothing (memOtrMutedRef =<< _self)) - assertBool "otr archived not false" (Just False == (memOtrArchived <$> _self)) - assertBool "otr archived ref not empty" (isNothing (memOtrArchivedRef =<< _self)) - case t of - SelfConv -> assertEqual "access" (Just privateAccess) (cnvAccess <$> cnv) - ConnectConv -> assertEqual "access" (Just privateAccess) (cnvAccess <$> cnv) - One2OneConv -> assertEqual "access" (Just privateAccess) (cnvAccess <$> cnv) - _ -> pure () - pure cId - -assertConvQualified :: - HasCallStack => - Response (Maybe Lazy.ByteString) -> - ConvType -> - UserId -> - Qualified UserId -> - [Qualified UserId] -> - Maybe Text -> - Maybe Milliseconds -> - TestM ConvId -assertConvQualified r t c s us n mt = assertConvQualifiedWithRole r t c s us n mt roleNameWireAdmin - -assertConvQualifiedWithRole :: HasCallStack => Response (Maybe Lazy.ByteString) -> ConvType -> @@ -1595,31 +1578,31 @@ assertConvQualifiedWithRole :: Maybe Text -> Maybe Milliseconds -> RoleName -> - TestM ConvId -assertConvQualifiedWithRole r t c s us n mt role = do + TestM (Qualified ConvId) +assertConvWithRole r t c s us n mt role = do cId <- fromBS $ getHeader' "Location" r - let cnv = responseJsonMaybe @Conversation r - let _self = cmSelf . cnvMembers <$> cnv - let others = cmOthers . cnvMembers <$> cnv + cnv <- responseJsonError r + let _self = cmSelf (cnvMembers cnv) + let others = cmOthers (cnvMembers cnv) liftIO $ do - assertEqual "id" (Just cId) (qUnqualified . cnvQualifiedId <$> cnv) - assertEqual "name" n (cnv >>= cnvName) - assertEqual "type" (Just t) (cnvType <$> cnv) - assertEqual "creator" (Just c) (cnvCreator <$> cnv) - assertEqual "message_timer" (Just mt) (cnvMessageTimer <$> cnv) - assertEqual "self" (Just s) (memId <$> _self) - assertEqual "others" (Just . Set.fromList $ us) (Set.fromList . map omQualifiedId . toList <$> others) - assertEqual "creator is always and admin" (Just roleNameWireAdmin) (memConvRoleName <$> _self) - assertBool "others role" (all (== role) $ maybe (error "Cannot be null") (map omConvRoleName . toList) others) - assertBool "otr muted ref not empty" (isNothing (memOtrMutedRef =<< _self)) - assertBool "otr archived not false" (Just False == (memOtrArchived <$> _self)) - assertBool "otr archived ref not empty" (isNothing (memOtrArchivedRef =<< _self)) + assertEqual "id" cId (qUnqualified (cnvQualifiedId cnv)) + assertEqual "name" n (cnvName cnv) + assertEqual "type" t (cnvType cnv) + assertEqual "creator" c (cnvCreator cnv) + assertEqual "message_timer" mt (cnvMessageTimer cnv) + assertEqual "self" s (memId _self) + assertEqual "others" (Set.fromList $ us) (Set.fromList . map omQualifiedId . toList $ others) + assertEqual "creator is always and admin" roleNameWireAdmin (memConvRoleName _self) + assertBool "others role" (all ((== role) . omConvRoleName) (toList others)) + assertBool "otr muted ref not empty" (isNothing (memOtrMutedRef _self)) + assertBool "otr archived not false" (not (memOtrArchived _self)) + assertBool "otr archived ref not empty" (isNothing (memOtrArchivedRef _self)) case t of - SelfConv -> assertEqual "access" (Just privateAccess) (cnvAccess <$> cnv) - ConnectConv -> assertEqual "access" (Just privateAccess) (cnvAccess <$> cnv) - One2OneConv -> assertEqual "access" (Just privateAccess) (cnvAccess <$> cnv) + SelfConv -> assertEqual "access" privateAccess (cnvAccess cnv) + ConnectConv -> assertEqual "access" privateAccess (cnvAccess cnv) + One2OneConv -> assertEqual "access" privateAccess (cnvAccess cnv) _ -> pure () - pure cId + pure (cnvQualifiedId cnv) wsAssertOtr :: HasCallStack => diff --git a/services/galley/test/integration/Main.hs b/services/galley/test/integration/Main.hs index 6907d1ab45..6410cc9fb8 100644 --- a/services/galley/test/integration/Main.hs +++ b/services/galley/test/integration/Main.hs @@ -88,7 +88,7 @@ main = withOpenSSL $ runTests go "galley" [ testCase "sitemap" $ assertEqual - "inconcistent sitemap" + "inconsistent sitemap" mempty (pathsConsistencyCheck . treeToPaths . compile $ Galley.API.sitemap), API.tests setup diff --git a/tools/fedcalls/README.md b/tools/fedcalls/README.md index e43f95e14a..bb62be4be4 100644 --- a/tools/fedcalls/README.md +++ b/tools/fedcalls/README.md @@ -15,10 +15,17 @@ dot -Tpng wire-fedcalls.dot > wire-fedcalls.png ### links -- `./example.png` +for users: + +- blog post explaining the technology: https://reasonablypolymorphic.com/blog/abusing-constraints/index.html - https://sketchviz.com/new - https://graphviz.org/doc/info/lang.html -- `/libs/wire-api/src/Wire/API/MakesFederatedCall.hs` + +for developers: + +- `./example.png` +- [MakesFederatedCall.hs (as of 2023-01-16)](https://github.com/wireapp/wire-server/blob/8760b4978ccb039b229d458b7a08136a05e12ff9/libs/wire-api/src/Wire/API/MakesFederatedCall.hs) +- PRs: https://github.com/wireapp/wire-server/pull/2973, https://github.com/wireapp/wire-server/pull/2940, https://github.com/wireapp/wire-server/pull/2950, https://github.com/wireapp/wire-server/pull/2957 ### swagger-ui