diff --git a/changelog.d/1-api-changes/one2one b/changelog.d/1-api-changes/one2one new file mode 100644 index 00000000000..c22c02444c3 --- /dev/null +++ b/changelog.d/1-api-changes/one2one @@ -0,0 +1 @@ +`GET /conversations/one2one/:domain/:uid` now returns `public_keys` along with the conversation containing all MLS public keys for the backend which will host this conversation (since v6). \ No newline at end of file diff --git a/changelog.d/5-internal/federation-v1 b/changelog.d/5-internal/federation-v1 new file mode 100644 index 00000000000..01960024d5d --- /dev/null +++ b/changelog.d/5-internal/federation-v1 @@ -0,0 +1 @@ +Add federation-v1 environment for testing compatibility of the federation API with version 1 diff --git a/charts/integration/templates/configmap.yaml b/charts/integration/templates/configmap.yaml index f211ab25105..d5fcbdbad90 100644 --- a/charts/integration/templates/configmap.yaml +++ b/charts/integration/templates/configmap.yaml @@ -6,6 +6,12 @@ metadata: "helm.sh/hook": post-install "helm.sh/hook-delete-policy": before-hook-creation data: + {{- range $name, $dynamicBackend := .Values.config.dynamicBackends }} + {{ $name }}-mls-removal-key-ed25519.pem: {{ $dynamicBackend.mlsPrivateKeys.removal.ed25519 | quote }} + {{ $name }}-mls-removal-key-ecdsa_secp256r1_sha256.pem: {{ $dynamicBackend.mlsPrivateKeys.removal.ecdsa_secp256r1_sha256 | quote }} + {{ $name }}-mls-removal-key-ecdsa_secp384r1_sha384.pem: {{ $dynamicBackend.mlsPrivateKeys.removal.ecdsa_secp384r1_sha384 | quote }} + {{ $name }}-mls-removal-key-ecdsa_secp521r1_sha512.pem: {{ $dynamicBackend.mlsPrivateKeys.removal.ecdsa_secp521r1_sha512 | quote }} + {{- end }} integration.yaml: | brig: host: brig.{{ .Release.Namespace }}.svc.cluster.local @@ -118,6 +124,12 @@ data: {{ $name }}: domain: {{ $dynamicBackend.federatorExternalHostPrefix }}.{{ $.Release.Namespace }}.svc.cluster.local federatorExternalPort: {{ $dynamicBackend.federatorExternalPort }} + mlsPrivateKeyPaths: + removal: + ed25519: "/etc/wire/integration/{{ $name }}-mls-removal-key-ed25519.pem" + ecdsa_secp256r1_sha256: "/etc/wire/integration/{{ $name }}-mls-removal-key-ecdsa_secp256r1_sha256.pem" + ecdsa_secp384r1_sha384: "/etc/wire/integration/{{ $name }}-mls-removal-key-ecdsa_secp384r1_sha384.pem" + ecdsa_secp521r1_sha512: "/etc/wire/integration/{{ $name }}-mls-removal-key-ecdsa_secp521r1_sha512.pem" {{- end }} cassandra: host: {{ .Values.config.cassandra.host }} @@ -164,3 +176,44 @@ data: stern: host: stern.wire-federation-v0.svc.cluster.local port: 8080 + + federation-v1: + originDomain: federation-test-helper.wire-federation-v1.svc.cluster.local + brig: + host: brig.wire-federation-v1.svc.cluster.local + port: 8080 + cannon: + host: cannon.wire-federation-v1.svc.cluster.local + port: 8080 + cargohold: + host: cargohold.wire-federation-v1.svc.cluster.local + port: 8080 + federatorInternal: + host: federator.wire-federation-v1.svc.cluster.local + port: 8080 + federatorExternal: + host: federator.wire-federation-v1.svc.cluster.local + port: 8081 + galley: + host: galley.wire-federation-v1.svc.cluster.local + port: 8080 + gundeck: + host: gundeck.wire-federation-v1.svc.cluster.local + port: 8080 + nginz: + host: nginz-integration-http.wire-federation-v1.svc.cluster.local + port: 8080 + spar: + host: spar.wire-federation-v1.svc.cluster.local + port: 8080 + proxy: + host: proxy.wire-federation-v1.svc.cluster.local + port: 8080 + backgroundWorker: + host: backgroundWorker.wire-federation-v1.svc.cluster.local + port: 8080 + stern: + host: stern.wire-federation-v1.svc.cluster.local + port: 8080 + + integrationTestHostName: integration-headless.{{ .Release.Namespace }}.svc.cluster.local diff --git a/charts/integration/templates/integration-integration.yaml b/charts/integration/templates/integration-integration.yaml index 5199c03def4..be43911fcc6 100644 --- a/charts/integration/templates/integration-integration.yaml +++ b/charts/integration/templates/integration-integration.yaml @@ -280,3 +280,7 @@ spec: key: uploadXmlAwsSecretAccessKey {{- end }} {{- end }} + - name: ENABLE_FEDERATION_V0 + value: "1" + - name: ENABLE_FEDERATION_V1 + value: "1" diff --git a/charts/integration/values.yaml b/charts/integration/values.yaml index f1310f8fa4e..36305b2be75 100644 --- a/charts/integration/values.yaml +++ b/charts/integration/values.yaml @@ -17,13 +17,96 @@ config: dynamic-backend-1: federatorExternalHostPrefix: dynamic-backend-1 federatorExternalPort: 10098 + mlsPrivateKeys: + removal: + ed25519: | + -----BEGIN PRIVATE KEY----- + MC4CAQAwBQYDK2VwBCIEIJrGRHzIwjc5byivY2l+/MqbH3ty1yetYG8d5p4GGHhk + -----END PRIVATE KEY----- + ecdsa_secp256r1_sha256: | + -----BEGIN PRIVATE KEY----- + MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgaeLidXfwi/RVvWZ4 + OHoQhicePLIfyDZI7gMVsyXtec6hRANCAARM6EWywmjaCXtvsQ1M2edrbMescC+j + GSIhBrlE7igzhookThDBvOGAL67vf8xz+hw7tE8NqfzbdJQBL8NQik2L + -----END PRIVATE KEY----- + ecdsa_secp384r1_sha384: | + -----BEGIN PRIVATE KEY----- + MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDD1rK50pFsZmOomBiNQ + QFRRwAmed8Ox+nFseYbKzjLIAgWH0sMQ5DU8SAK8ks+GROShZANiAATyX0XQ6x6A + pi+HKz+ReWV9iIUOttxJv9u2aTY5ZrQ42IJs3fV1AGz1BE52uDvhbILOD9WfqZ9d + 6MqCjF6OqYT9nmnPkQ+CKC2XPzSVBpqJtuHXiMfFrc7n05E8CdIHOkI= + -----END PRIVATE KEY----- + ecdsa_secp521r1_sha512: | + -----BEGIN PRIVATE KEY----- + MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAQnVcp85/mC6r91yB + XjhiHOp4j450UcThSmDBdva4Pj9ihXvAScEFabus7CeECvUT3auqXKY9iSR45vQq + JuFI/0uhgYkDgYYABAG1paU01rRuYG4K2PWaIIbB9RuiYg5GVsu5mu6VHjYEH+7c + 1AGuCPEsUoM542cn3T1utv0EMtoj4yFPvf0xBs7AowHW04JsgMFzpWm8T1e/91n1 + IEkT5xOnq8obn7p4je9Ui95ojEA/n49gsTKsuO1qv2n79PnStLfn2yT5lAtcTcva + 6Q== + -----END PRIVATE KEY----- dynamic-backend-2: federatorExternalHostPrefix: dynamic-backend-2 federatorExternalPort: 11098 + mlsPrivateKeys: + removal: + ed25519: | + -----BEGIN PRIVATE KEY----- + MC4CAQAwBQYDK2VwBCIEIDgG4Dhqfq6KRyGKtEFiPeP+Nq1DBsTY31q3f/tC/lnk + -----END PRIVATE KEY----- + ecdsa_secp256r1_sha256: | + -----BEGIN PRIVATE KEY----- + MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgz0IEyU4GYrpkVH2y + iR87BMD1VAfBgl69WedewvA/Vl+hRANCAASTYYD2BF2E1zqPKYZtpHW1quo+YBsv + SAeznMX1bOeoOLD8zyFDHEGb3I9S90iGjYKTUogY+QfbbiqAiBIuSig7 + -----END PRIVATE KEY----- + ecdsa_secp384r1_sha384: | + -----BEGIN PRIVATE KEY----- + MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDA43epgPhtj4s0G3aXQ + TPXjnQHhiQ7Hfze+K8HgDSUL+Ds31v+g+Ko/OZrAA7povdWhZANiAATXd/dKoFvA + wlISC4MAbBsDV6g2oezzZt0nXUq4uysANJ24s+BNey7tYpB36qAOUhqmCzJW5IFJ + 22ttorUXSTaJeUIUdRiwD7xJ54z3NV5Wj8CUskvp0DIf/ILkOpbxdQY= + -----END PRIVATE KEY----- + ecdsa_secp521r1_sha512: | + -----BEGIN PRIVATE KEY----- + MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAjGOdWinAUUopQCYW + 6Ch4UuwdHhTERbUS90bQiQyoPdnTrTT8+NsYsB8DmPLltxls6h28q0IGCKUmO9ph + 8gFT0l6hgYkDgYYABAD3l73lFiVckI4V8BhR2x83o44dhjZA26d8SVSUBt9iuRbR + Lh0vP+zghhDQZLFLpfcL0Fo0K9H4HdQwe2cMxbOyQwDUC76ot9BdZjfsjKiRK6+k + ZNlnHSWx15yg8gF0dpt2eVn1LBLB0JvRcauYVMfKNox1IU8DY0ZiuO4DJNXRDVEI + 7w== + -----END PRIVATE KEY----- dynamic-backend-3: federatorExternalHostPrefix: dynamic-backend-3 federatorExternalPort: 12098 - + mlsPrivateKeys: + removal: + ed25519: | + -----BEGIN PRIVATE KEY----- + MC4CAQAwBQYDK2VwBCIEIB/Jddpef01pYWQXUEFmJ+k6dDQE7fVSKfk7/AyQaOnU + -----END PRIVATE KEY----- + ecdsa_secp256r1_sha256: | + -----BEGIN PRIVATE KEY----- + MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgIkeTuHoMtzsuaN3f + zug+mp/IlejrG1W4z/lOU9yGNqKhRANCAASLReHnUMJfSs0pDFxVYIgCOThRsiCD + Fq/6oKzWYnvX+taJgNUCVm7QND7Q9ll+Vy4ymZmE9YH1QuNW4FbVe1X6 + -----END PRIVATE KEY----- + ecdsa_secp384r1_sha384: | + -----BEGIN PRIVATE KEY----- + MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDB0dp/epJB3XhCeRcYJ + C76Ll50HHb/H2GR/UBFyAWYtQ8mbaXWis8NPjvYmqrqd5VWhZANiAARoOjIYWdAP + Y910LsLGcihvmnoFx7atJbOhaGTem57P/DOkYqcYohUcz6WaCuqzk/ZEj8NZtdvF + 4AYt0mnxkl9L5pt2a6i2HWW+4puR+JMmWD9qj0lRc5AQeEtmbuohIfg= + -----END PRIVATE KEY----- + ecdsa_secp521r1_sha512: | + -----BEGIN PRIVATE KEY----- + MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIBfyz5LCeeA0seQo1O + jlQiUKxL4tWX23mD5G5Y5nra3Ju/7mNYp/sIX5BS81iWno5N6KfEdgUtgEffa4Xj + nuyF2QqhgYkDgYYABAF3eFOMjqpO7hDdVua9WgquGdFRRd3LWLhY0fyeyiQn/7yr + vLIb01f8dX9UVFKMxw77ZMMcfF+uW5Enxa8kadDcmwHIiIh/6jW0oGlFxkmwmecr + MwfpR6lZMbtQMD4rm8AwQAsFCBCRyPyK8bWanzMYusbnCdS/nBB5YB8x0ejjYFlU + RQ== + -----END PRIVATE KEY----- cassandra: host: cassandra-ephemeral port: 9042 diff --git a/deploy/dockerephemeral/coredns-config/db.example.com b/deploy/dockerephemeral/coredns-config/db.example.com index a458686bca7..46df3527882 100644 --- a/deploy/dockerephemeral/coredns-config/db.example.com +++ b/deploy/dockerephemeral/coredns-config/db.example.com @@ -18,3 +18,4 @@ _wire-server-federator._tcp.d1 IN SRV 0 0 10443 localhost. _wire-server-federator._tcp.d2 IN SRV 0 0 11443 localhost. _wire-server-federator._tcp.d3 IN SRV 0 0 12443 localhost. _wire-server-federator._tcp.federation-v0 IN SRV 0 0 21443 localhost. +_wire-server-federator._tcp.federation-v1 IN SRV 0 0 22443 localhost. diff --git a/deploy/dockerephemeral/docker/elasticmq.conf b/deploy/dockerephemeral/docker/elasticmq.conf index 7cd41d7317e..77cdd11e4b4 100644 --- a/deploy/dockerephemeral/docker/elasticmq.conf +++ b/deploy/dockerephemeral/docker/elasticmq.conf @@ -43,6 +43,7 @@ queues { integration-brig-events4 = ${queues.default-queue-template} integration-brig-events5 = ${queues.default-queue-template} integration-brig-events-federation-v0 = ${queues.default-queue-template} + integration-brig-events-federation-v1 = ${queues.default-queue-template} integration-brig-events-internal = ${queues.default-queue-template} integration-brig-events-internal2 = ${queues.default-queue-template} @@ -50,6 +51,7 @@ queues { integration-brig-events-internal4 = ${queues.default-queue-template} integration-brig-events-internal5 = ${queues.default-queue-template} integration-brig-events-internal-federation-v0 = ${queues.default-queue-template} + integration-brig-events-internal-federation-v1 = ${queues.default-queue-template} "integration-user-events.fifo" = ${queues.fifo-queue-template} "integration-user-events2.fifo" = ${queues.fifo-queue-template} @@ -57,6 +59,7 @@ queues { "integration-user-events4.fifo" = ${queues.fifo-queue-template} "integration-user-events5.fifo" = ${queues.fifo-queue-template} "integration-user-events-federation-v0.fifo" = ${queues.fifo-queue-template} + "integration-user-events-federation-v1.fifo" = ${queues.fifo-queue-template} integration-gundeck-events = ${queues.default-queue-template} integration-gundeck-events2 = ${queues.default-queue-template} @@ -64,6 +67,7 @@ queues { integration-gundeck-events4 = ${queues.default-queue-template} integration-gundeck-events5 = ${queues.default-queue-template} integration-gundeck-events-federation-v0 = ${queues.default-queue-template} + integration-gundeck-events-federation-v1 = ${queues.default-queue-template} "integration-team-events.fifo" = ${queues.fifo-queue-template} "integration-team-events2.fifo" = ${queues.fifo-queue-template} @@ -71,10 +75,11 @@ queues { "integration-team-events4.fifo" = ${queues.fifo-queue-template} "integration-team-events5.fifo" = ${queues.fifo-queue-template} "integration-team-events-federation-v0.fifo" = ${queues.fifo-queue-template} + "integration-team-events-federation-v1.fifo" = ${queues.fifo-queue-template} } # Region and accountId which will be included in resource ids aws { region = eu-west-1 accountId = 000000000000 -} \ No newline at end of file +} diff --git a/deploy/dockerephemeral/federation-v0.yaml b/deploy/dockerephemeral/federation-v0.yaml index 8ed1179b048..639baf4b9b4 100644 --- a/deploy/dockerephemeral/federation-v0.yaml +++ b/deploy/dockerephemeral/federation-v0.yaml @@ -4,8 +4,11 @@ networks: demo_wire: external: false + coredns: + external: false + services: - brig_schema: + brig_schema-v0: container_name: brig-schema-federation-v0 image: quay.io/wire/brig-schema:4.38.51 command: --host cassandra --keyspace brig_test_federation_v0 --replication-factor 1 @@ -17,7 +20,7 @@ services: condition: on-failure networks: - demo_wire - brig: + brig-v0: container_name: brig-federation-v0 image: quay.io/wire/brig:4.38.0-mandarin.14 volumes: @@ -29,7 +32,7 @@ services: healthcheck: &haskell_health_check test: "curl --fail localhost:8080/i/status" depends_on: - brig_schema: + brig_schema-v0: condition: service_completed_successfully aws_cli: condition: service_completed_successfully @@ -43,7 +46,7 @@ services: - RABBITMQ_USERNAME=${RABBITMQ_USERNAME} - RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD} - galley_schema: + galley_schema-v0: container_name: galley-schema-federation-v0 image: quay.io/wire/galley-schema:4.38.51 command: --host cassandra --keyspace galley_test_federation_v0 --replication-factor 1 @@ -56,7 +59,7 @@ services: networks: - demo_wire - galley: + galley-v0: container_name: galley-federation-v0 image: quay.io/wire/galley:4.38.0-mandarin.14 volumes: @@ -67,7 +70,7 @@ services: - '127.0.0.1:21085:8080' healthcheck: *haskell_health_check depends_on: - galley_schema: + galley_schema-v0: condition: service_completed_successfully aws_cli: condition: service_completed_successfully @@ -81,7 +84,7 @@ services: - RABBITMQ_USERNAME=${RABBITMQ_USERNAME} - RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD} - cargohold: + cargohold-v0: container_name: cargohold-federation-v0 image: quay.io/wire/cargohold:4.38.0-mandarin.14 volumes: @@ -100,7 +103,7 @@ services: - AWS_ACCESS_KEY_ID=dummykey - AWS_SECRET_ACCESS_KEY=dummysecret - gundeck_schema: + gundeck_schema-v0: container_name: gundeck-schema-federation-v0 image: quay.io/wire/gundeck-schema:4.38.51 command: --host cassandra --keyspace gundeck_test_federation_v0 --replication-factor 1 @@ -113,7 +116,7 @@ services: networks: - demo_wire - gundeck: + gundeck-v0: container_name: gundeck-federation-v0 image: quay.io/wire/gundeck:4.38.0-mandarin.14 volumes: @@ -124,11 +127,11 @@ services: - '127.0.0.1:21086:8080' healthcheck: *haskell_health_check depends_on: - gundeck_schema: + gundeck_schema-v0: condition: service_completed_successfully aws_cli: condition: service_completed_successfully - redis: + redis-v0: condition: service_started environment: @@ -136,7 +139,7 @@ services: - AWS_ACCESS_KEY_ID=dummykey - AWS_SECRET_ACCESS_KEY=dummysecret - spar_schema: + spar_schema-v0: container_name: spar-schema-federation-v0 image: quay.io/wire/spar-schema:4.38.51 command: --host cassandra --keyspace spar_test_federation_v0 --replication-factor 1 @@ -149,7 +152,7 @@ services: networks: - demo_wire - spar: + spar-v0: container_name: spar-federation-v0 image: quay.io/wire/spar:4.38.0-mandarin.14 volumes: @@ -160,10 +163,10 @@ services: - '127.0.0.1:21088:8080' healthcheck: *haskell_health_check depends_on: - spar_schema: + spar_schema-v0: condition: service_completed_successfully - cannon: + cannon-v0: container_name: cannon-federation-v0 image: quay.io/wire/cannon:4.38.0-mandarin.14 volumes: @@ -174,7 +177,7 @@ services: - '127.0.0.1:21083:8080' healthcheck: *haskell_health_check - federator: + federator-v0: container_name: federator-federation-v0 image: quay.io/wire/federator:4.38.0-mandarin.14 volumes: @@ -191,10 +194,10 @@ services: healthcheck: test: "true" depends_on: - coredns-federation: + coredns-federation-v0: condition: service_started - background_worker: + background_worker-v0: container_name: background-worker-federation-v0 image: quay.io/wire/background-worker:4.38.0-mandarin.14 volumes: @@ -203,6 +206,7 @@ services: - demo_wire ports: - '127.0.0.1:21089:8080' + healthcheck: *haskell_health_check depends_on: init_vhosts: condition: service_completed_successfully @@ -210,7 +214,7 @@ services: - RABBITMQ_USERNAME=${RABBITMQ_USERNAME} - RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD} - proxy: + proxy-v0: container_name: proxy-federation-v0 image: quay.io/wire/proxy:4.38.0-mandarin.14 volumes: @@ -221,7 +225,7 @@ services: - '127.0.0.1:21087:8080' healthcheck: *haskell_health_check - nginz: + nginz-v0: container_name: nginz-federation-v0 image: quay.io/wire/nginz:4.38.0-mandarin.14 volumes: @@ -232,26 +236,28 @@ services: - '127.0.0.1:21080:8080' - '127.0.0.1:21443:8443' depends_on: - brig: + brig-v0: + condition: service_healthy + galley-v0: condition: service_healthy - galley: + gundeck-v0: condition: service_healthy - gundeck: + cargohold-v0: condition: service_healthy - cargohold: + cannon-v0: condition: service_healthy - cannon: + spar-v0: condition: service_healthy - spar: + federator-v0: condition: service_healthy - federator: + proxy-v0: condition: service_healthy - proxy: + background_worker-v0: condition: service_healthy # We have to run a separate redis instance for each version of wire-server we # want. This is because gundeck just assumes the whole redis is for itself - redis: + redis-v0: container_name: redis-federation-v0 image: redis:6.0-alpine networks: @@ -259,7 +265,7 @@ services: # This coredns serves slightly different SRV records, so federator running in # a docker container can talk to federator running on the host. - coredns-federation: + coredns-federation-v0: image: docker.io/coredns/coredns:1.8.4 volumes: - ./federation-v0/coredns-config:/coredns-config diff --git a/deploy/dockerephemeral/federation-v0/coredns-config/db.example.com b/deploy/dockerephemeral/federation-v0/coredns-config/db.example.com index 448d8b5f594..407f4770916 100644 --- a/deploy/dockerephemeral/federation-v0/coredns-config/db.example.com +++ b/deploy/dockerephemeral/federation-v0/coredns-config/db.example.com @@ -17,4 +17,5 @@ _wire-server-federator._tcp.b IN SRV 0 0 9443 host.docker.internal. _wire-server-federator._tcp.d1 IN SRV 0 0 10443 host.docker.internal. _wire-server-federator._tcp.d2 IN SRV 0 0 11443 host.docker.internal. _wire-server-federator._tcp.d3 IN SRV 0 0 12443 host.docker.internal. -_wire-server-federator._tcp.v0 IN SRV 0 0 21443 host.docker.internal. +_wire-server-federator._tcp.federation-v0 IN SRV 0 0 21443 host.docker.internal. +_wire-server-federator._tcp.federation-v1 IN SRV 0 0 22443 host.docker.internal. diff --git a/deploy/dockerephemeral/federation-v0/integration-ca.pem b/deploy/dockerephemeral/federation-v0/integration-ca.pem index 2315c7c7404..6a33fa9e2c3 100644 --- a/deploy/dockerephemeral/federation-v0/integration-ca.pem +++ b/deploy/dockerephemeral/federation-v0/integration-ca.pem @@ -1,19 +1,19 @@ -----BEGIN CERTIFICATE----- -MIIDAjCCAeqgAwIBAgIULBRPt7tLLvsw7kciIdjbXB8tddQwDQYJKoZIhvcNAQEL -BQAwGTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5jb20wHhcNMjMxMTIxMTM1ODAwWhcN -MjgxMTE5MTM1ODAwWjAZMRcwFQYDVQQDEw5jYS5leGFtcGxlLmNvbTCCASIwDQYJ -KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMcUoMS1MjHMEB4RN84hGz4J/pvS/BJF -7HL7FgOjGuJ+aMCtpmO2ht59mUWJVvt1TAYtEraz4fpZl2Vs4MsPm8R5GjWBU6Su -9MVBk8d5R38ruhKSgTtBJdUjRMZ68fDjVGy8mPy8J45QuXVjgfZeDzcpVH+A1K+3 -gJRazCD9r9vxVlc/W335uX1q8uH1u4kXCxkESjWK7/we/fHVcRI/caIdjoluqfP7 -bhDQ+jTJCYhrLR0yWLZocJhe+FgMaOxEBw+ojYKa+Xq6wEMK2YXkhmDZW49O/JQP -ZqROwXD8BHQ2IJyOES25adL3F7yN7sODXuPhDAg8SYV1/kr2nALQTzECAwEAAaNC -MEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJO6 -JJpzdazNjXtum3zX8UYWaQIJMA0GCSqGSIb3DQEBCwUAA4IBAQCoV7sw9CgICo9O -JacaB+P0Uk0dnISjsrKpcAKnuVdh1rN94+beXyttSBgQtDgVBehlESN+/B9fefLb -lhVxgCYq8inx4wZs22h8ZkjpJiOmBDjvHwgkCQOoh/Kog9gkmDr4qbFahU5GpaTp -x1rlNF3qaNRvZSVoxIVwYYiexKS5/KYMedII2EoBMHcFj0qKMhdDIT1Uw2PJZwiA -qjGDsSnLS+VeA8Zluc3m/os0ynjR6BEFQF1sn/OGO0eFaSMxXz0+Z4vT3J+c08Be -z2uZWQBgCiV/bL8F5xgokbHx+Vl0lz+1PEoFre8IJihmcnT8ZPWv/8eWPAr0gavH -+R0lNAyw +MIIDEzCCAfugAwIBAgIUEfjIXW9tD1WgwNHJ+kC3r6Cmv5swDQYJKoZIhvcNAQEL +BQAwGTEXMBUGA1UEAwwOY2EuZXhhbXBsZS5jb20wHhcNMjQwOTAzMTIwMzM3WhcN +MzQwOTAxMTIwMzM3WjAZMRcwFQYDVQQDDA5jYS5leGFtcGxlLmNvbTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAJkd+ihX0Tp6RG4Ue9kQFvTHFy2DL8rC +8u956hy17/9sTyIcMkTOtZMew+ua+ZrNzX1x1WP4BT5RZQA/wy6ioEpP5BVFxe9s +dmVqYawD9C5+XKTekEFF3gI0MIGZ4Vum2hdUOHTwDatAgdiqsBwuCxHM5terItzZ +SsrkYFlAhISmM9CsUFOR+1rqpMNWRQD5zzZ0Sk5HeyM1/9sHvkrq24rqKqkg0/uR +OTHMydrmuSArG5tKqkdb2zoJwOs8somraRzB1JOm5/i/3pCT5iBr5gf8eLGtb2Gu +XgmnTI129R2a+LbifqTC31jIMg/yNfFuU4MdUG9wE512ghhXZI+Hoc8CAwEAAaNT +MFEwHQYDVR0OBBYEFFqlhDsVqlH8UUKGOtCDE9xmYL3hMB8GA1UdIwQYMBaAFFql +hDsVqlH8UUKGOtCDE9xmYL3hMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBADQyh0k3xhcrNOqhvzAK1/A3TY5Hq1FE/a17Yiq8DIiLCJG/nAN60GBx +m7zuL8xoRJ4ylwIswa4z4rHj9p6M6tIbi2tTfJsbyB+FjyFRWoBmTngqNCiw7QUR +/ofSliuEu/YIjphR8LmTBvy4fVccTwXDaBPEGf2iN+DFmryLHxVpsVh3AA0uUSy0 +e2bZJLRwv1z0saC5KGHpWb6RJbAP2nRw5omcorMtP1KW8XyVESiJm7hDZAx6VLgD +k4GcEOUEq9CJs9UVAkIIDS87CfppHZEGPDK3Ufro5AhIwA3hpSJkTgzkf0TcQKIr +4E/zJSeTld4rMC26ghWodIwGRyofTP0= -----END CERTIFICATE----- diff --git a/deploy/dockerephemeral/federation-v0/integration-leaf-key.pem b/deploy/dockerephemeral/federation-v0/integration-leaf-key.pem index 8ed90523cd3..e4ee0a09ab1 100644 --- a/deploy/dockerephemeral/federation-v0/integration-leaf-key.pem +++ b/deploy/dockerephemeral/federation-v0/integration-leaf-key.pem @@ -1,27 +1,28 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEA1ueRV5jCjz+AWOmFzKkkjqPrCj1GGz8VDm5HLm4e7EO/LGXk -+RAYeKupGF9eqGBkiYfw9eZrjbf+uf5mpe7qKGrP67iCEzyjkbMMB8I89dcLwp7Y -uXYWfHw4NdFkSZoE0gmZ6Jh7EK+G2n+PZUaS9T43QoqMv0pFQ1roZpVMKkjnkW5J -4cU3JfXQQzdNCMiXlpGAIL0cKee6cwkPpGC1X3/6XQDyW7Q9nOjSw0mPmiZuK4iR -qwdy4edjKhcvJxuxHw215hVi0QVqbUcNzffS0mO+VIXz2IEbdzUwhSZJISsHQEOa -27UrBdRSg+Wb3FDQ+J8IeS6PR5JwjBcwt+DAjQIDAQABAoIBAHXZSS/TOqZZeWXI -sbH4824xX7weu+pHHqHqQaiphNWllRmgyv72H6VU5YbTDdKiAaAV50LB2CtAQjT2 -2I2YRdpiMKEgblxkPYKxwCAlGU7rXayddVXG9y/O4vhIWomuJ4SS9U7DB4Gv7/C0 -UQuFtyM7ugwIdISWEwOLv7Q5nSn2DYYXapNSmCUYv2FJEd57MJFtZ+CTHPu+ALxY -/qCGga8WBQ9Io/4A6UWN76m5IREeGh/pBwwhestpvUB9hXXe037Z11G3j/mNjqmz -SoUdEXnXpqJMA4c73hrryZR7TRPjRQx2P7YTyMwwOaJenhCS2F7ohJrwXNEtfbXt -Tb4mAQECgYEA8Qc4YqbF+xDmav1Mw7tpQ34EW7U1BF6RW+zpaRVVYXc+hZq8Rscl -yhzvYI2F4b9qOXw73Vdj3Hbd3f3BRC2ayMUk82pmbFEhZjQR9cGaLH1JfNXBdgz+ -wenmdczUAhmDiIseXTYdXL0FFgc9F/UFzmAYmD/kkMHTO2wnfeAci00CgYEA5EDv -UJzW/hWUtawWfg0Bw+H5RR2W/28dGG+680zazZwVHtDF7sEiThmR8AlLu74tWUMg -PBREdxOui5qRhmZO3y3JLJ8mjmEUQqC4x1NWReZCAcWGTNXn/PHsWPlK82qp/Q98 -lYJLShtbOOgo1hUPYeQ3hFnDi8HM3QssEeYB6kECgYA0kdSUf7dyuQ7oivKxRjEB -TXz5254Co/WkTRnjl4mVxoJWdZdXAJyXZpQ3RObMhAlRHG2aKzNWpH5jqrL6gc/e -tlEG3lAUk+Vq+zRnm6Baz8C1f5HAg7kU5kUjsFcVVidAIseuoNzqmzd+xHlovkJT -7tWub1EU2ZGOxloetEDFiQKBgQCfPrp4OGQ6cp4EvaIXoUV4/0Aku0cswL3A3brF -ofoJdvq5PBjLwQ0JBgfuOt4OhtkmrJFhuRYnKaEeHuGmrdwbEtuG+SYyMYKsFWu1 -DOxk6gdlKwTOuHIY5EPrs0laWDFur45Q1M1oT3uuUTKkYZ8QweMFwIaQC8687N17 -Q0hUwQKBgQDu55deAXAAS9FCqT4qidyxmvjdpkn8BKZhetss+t0m7Rum9OJCiMI5 -90exbnlRtUP4soNOccS3w3ie2HPspdlIsllYnd4/KaHQbdEoGtvrF5rM77X+81N1 -xPgNsMJM167VEWWJJCE+rkeWiF+irrjiHj7QlLmKkK4bmEzp5XuLyg== ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDPYxemHc21P8y2 +DIDNsuUzVpnc1bj81QIQ5a2aFSVtQFr3Dhpo0XXyu0KHKR64ZelmW33Yoe2J0zwl +R66fg07whBcZspsonXmsUB5uFFF/Dcv9shBNcg67jjynXPDvJK2sC37GCar5ar/v +11v/+Iq6LNerC/OqMJlMYyFDif1BvESbnw+9hsihUT9qK5s6md5krEV1/ro0Noh7 +kBgflylEKjapfCrVMnAMgaMV55jrk5BTpoR6KCDuDmXPk3Ed304Op/krGazfGB2j +1rz3IPfKOOtIgxn8n5PDYmShVUw1R3Rps1LeEyhsH1EGuAvVx1mKhB3QGMqFtIfo +wOpkgZPdAgMBAAECggEAVMurFUB1ZkkmXj9hgPnHLoUX10xJ3ZMIy7jlkS1ZRsD8 +EK0jDj2q0OtRSet9xJ7i3nfFTojzE5obqxCSrWUmp0ATI+4789DjuZluv8quAdm1 +0U73zHq43GZNlY7yco2YN1Lh7H5yepXz0dDILLLGolYIfscdw7YoUCvuI2vt8eyY +FaMkOi8Bs32Abn+53MhaNkEOVemmwP/u2rD81IY7pXQtF+1XfSxYBipHCB7phC1g +dr6ITU9CoF3SXvjYL6uP/7W1Du/CuaDSijVZJkBfQSgbkQbBr6pAovumtpNZgZvO +bixqS4oTZqsd+Rgl3YBOpx+JbuUFL0fG+uQBv1yZbwKBgQD8n7s1w/ouUGwW+qGi +4EFTYVDFD2Rgvg3oFPHt72zreaNXSy8YWrK9Mas/Fzu5knSE3f3O0S1WvqWdt/z6 +uPDLTpg9fIWX6v+hPX09F5ekeLlUDBazBT1PQUD0qd8PiyFNB1F+ffSPTxmKScjc +hTqQCtnun6rlICalWO9VvGt6QwKBgQDSKJkjxIGLtXBaXfMQiF9dQDrMk2it5grl +w0OnpPhYvpdp+Cfi01kMUrnfHwF0v92BqeqoKZo4DwJXkrmwf5kNnftxmRqbk9gE +dJq/E/6SELyT/chtzXfxC/wTmyyxhfZUJvJUxlaZ6KP/86t8A22DJcFO2Z85iUGH +8zy2UJUnXwKBgBvSs9m+FeXX8a+uNvMrY8Z9J1os0c9d30Y6WFLuVb6xjO3mV++E +vb7co5G1S1yq5q5jjLqkiyvMn4z5YKF0kQCzTU0oU8ZhmXn2vb5mxMrWiQLauf1J +jHEYLMFFnE2n8yj6r10RHkhSW+vBKKAxBDwtFceUSkwl+FupqeJ1eBjlAoGBAMou ++LWqdZ89HSwzOobrTCPgiTELmCfFKzLE2q/MTIjEQ9NVRLo57m+mnt+DatkxRR9b +oz/JVm8cMXqi1DZza4HoPWGalDic0bPnooC18bIAnAwcmdjZVcz3ZLpQDX10jfmD +xpu8fNBxOmYhvRcADTmg9wqu3zpxTDRI1F3pxLUtAoGAGfsX4bve5cLm49Oa1p0H +kEErLMuAMIKQNVsbzVELepLYr+uwEXBCXyyoIf79ABDvUHbzxMEwgANuet/4PQzS +yB1qzFk6GDvqZ5dfPUgMUWH9wvD1qEGp6yxkyESGt8CNwnu8GI50NAeSh2/JeUIa +r/u+m2vnJjOXpJdOJ+7f6yM= +-----END PRIVATE KEY----- diff --git a/deploy/dockerephemeral/federation-v0/integration-leaf.pem b/deploy/dockerephemeral/federation-v0/integration-leaf.pem index d8e7ee0955c..abd724df6b1 100644 --- a/deploy/dockerephemeral/federation-v0/integration-leaf.pem +++ b/deploy/dockerephemeral/federation-v0/integration-leaf.pem @@ -1,21 +1,20 @@ -----BEGIN CERTIFICATE----- -MIIDcjCCAlqgAwIBAgIUXlJ06fjgHbzEvIRscFvEwxpsioMwDQYJKoZIhvcNAQEL -BQAwGTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5jb20wHhcNMjMxMTIxMTM1ODAwWhcN -MjQxMTIwMTM1ODAwWjAAMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA -1ueRV5jCjz+AWOmFzKkkjqPrCj1GGz8VDm5HLm4e7EO/LGXk+RAYeKupGF9eqGBk -iYfw9eZrjbf+uf5mpe7qKGrP67iCEzyjkbMMB8I89dcLwp7YuXYWfHw4NdFkSZoE -0gmZ6Jh7EK+G2n+PZUaS9T43QoqMv0pFQ1roZpVMKkjnkW5J4cU3JfXQQzdNCMiX -lpGAIL0cKee6cwkPpGC1X3/6XQDyW7Q9nOjSw0mPmiZuK4iRqwdy4edjKhcvJxux -Hw215hVi0QVqbUcNzffS0mO+VIXz2IEbdzUwhSZJISsHQEOa27UrBdRSg+Wb3FDQ -+J8IeS6PR5JwjBcwt+DAjQIDAQABo4HKMIHHMA4GA1UdDwEB/wQEAwIFoDAdBgNV -HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4E -FgQUWm43ORCCQGlDu3JaPIm15lsr5swwHwYDVR0jBBgwFoAUk7okmnN1rM2Ne26b -fNfxRhZpAgkwSAYDVR0RAQH/BD4wPIIZKi5pbnRlZ3JhdGlvbi5leGFtcGxlLmNv -bYIUaG9zdC5kb2NrZXIuaW50ZXJuYWyCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsF -AAOCAQEAfrlC1maUJMg5n61YEpBwIS9O0LLhNidZ6dBEPwDiBwskzkTKoWksSR+n -7OytNFQvrdclejxIyvoOvBhLqNY4pFYdNRUu42GESUpCA6cQlW3a9QchTEuNASWR -AdrmGmjXYwPFGjnVUVPR+Abs9lG7/8eDYoq1B1AdBkW1EJ7+0/DrLOLDtloxYmBF -bydmLcesdPvgBLkHfBlOG54jH/ILXHAHxskWmGqixY6L1svhrcnwsindxRcfT4QB -fAtNDfAfiftUdb96QJfpwN1/N1oEHFl2D0ynE8sFOuVFm0gQ6mblH+Vahune6cSK -7SDUwM9Ia1OAO/r2cdEAvCrQqaeDZQ== +MIIDQTCCAimgAwIBAgIBADANBgkqhkiG9w0BAQsFADAZMRcwFQYDVQQDDA5jYS5l +eGFtcGxlLmNvbTAeFw0yNDA5MDMxMjAzMzhaFw0zNDA5MDExMjAzMzhaMAAwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPYxemHc21P8y2DIDNsuUzVpnc +1bj81QIQ5a2aFSVtQFr3Dhpo0XXyu0KHKR64ZelmW33Yoe2J0zwlR66fg07whBcZ +spsonXmsUB5uFFF/Dcv9shBNcg67jjynXPDvJK2sC37GCar5ar/v11v/+Iq6LNer +C/OqMJlMYyFDif1BvESbnw+9hsihUT9qK5s6md5krEV1/ro0Noh7kBgflylEKjap +fCrVMnAMgaMV55jrk5BTpoR6KCDuDmXPk3Ed304Op/krGazfGB2j1rz3IPfKOOtI +gxn8n5PDYmShVUw1R3Rps1LeEyhsH1EGuAvVx1mKhB3QGMqFtIfowOpkgZPdAgMB +AAGjgawwgakwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMEgGA1UdEQEB +/wQ+MDyCGSouaW50ZWdyYXRpb24uZXhhbXBsZS5jb22CFGhvc3QuZG9ja2VyLmlu +dGVybmFsgglsb2NhbGhvc3QwHQYDVR0OBBYEFBkpAu3ILiU4gtEYffAU7zxGHPC6 +MB8GA1UdIwQYMBaAFFqlhDsVqlH8UUKGOtCDE9xmYL3hMA0GCSqGSIb3DQEBCwUA +A4IBAQBB1VsthdoVT9ExXkfKixotbXm6+eBgYenK1R5Qx/UX3JrlI1nF/8rKMg5e +7QfMCydSJwVEQdvnXD3ddVhUTYRActQvnJwWTyXfeiezrfDCTLu4SNpLOP7ojFlq +9ZX/E9GC0axTIUmEIy8YIC3JJ2PAlvw9qMzrsivyAgbof3NX+9XXKfwZHBwSLsO1 +Gxr9zkL+U/qww7TvyJD1LqBR0UEd9pZriorpVVFAa/JlFQX5ip1Smcd6m97nq20N +qpUIalra+K6qHxjHVwA2UxVgbO9bLFIBmp9pNvSm+5umAKkmqFnHRNAHfCy/IFGl +3fw8u9mXJ8LzUR4tiS0cVb6bwQzd -----END CERTIFICATE----- diff --git a/deploy/dockerephemeral/federation-v0/nginz/upstreams b/deploy/dockerephemeral/federation-v0/nginz/upstreams index a3e6afada32..adfdafa6eec 100644 --- a/deploy/dockerephemeral/federation-v0/nginz/upstreams +++ b/deploy/dockerephemeral/federation-v0/nginz/upstreams @@ -1,38 +1,38 @@ upstream cargohold { least_conn; keepalive 32; - server cargohold:8080 max_fails=3 weight=1; + server cargohold-v0:8080 max_fails=3 weight=1; } upstream gundeck { least_conn; keepalive 32; - server gundeck:8080 max_fails=3 weight=1; + server gundeck-v0:8080 max_fails=3 weight=1; } upstream cannon { least_conn; keepalive 32; - server cannon:8080 max_fails=3 weight=1; + server cannon-v0:8080 max_fails=3 weight=1; } upstream galley { least_conn; keepalive 32; - server galley:8080 max_fails=3 weight=1; + server galley-v0:8080 max_fails=3 weight=1; } upstream proxy { least_conn; keepalive 32; - server proxy:8080 max_fails=3 weight=1; + server proxy-v0:8080 max_fails=3 weight=1; } upstream brig { least_conn; keepalive 32; - server brig:8080 max_fails=3 weight=1; + server brig-v0:8080 max_fails=3 weight=1; } upstream spar { least_conn; keepalive 32; - server spar:8080 max_fails=3 weight=1; + server spar-v0:8080 max_fails=3 weight=1; } upstream federator_external { - server federator:8081 max_fails=3 weight=1; + server federator-v0:8081 max_fails=3 weight=1; } diff --git a/deploy/dockerephemeral/federation-v1.yaml b/deploy/dockerephemeral/federation-v1.yaml new file mode 100644 index 00000000000..fc151a37dd0 --- /dev/null +++ b/deploy/dockerephemeral/federation-v1.yaml @@ -0,0 +1,274 @@ +networks: + demo_wire: + external: false + + coredns: + external: false + +services: + brig_schema-v1: + container_name: brig-schema-federation-v1 + image: quay.io/wire/brig-schema:4.42.0-pre.27 + command: --host cassandra --keyspace brig_test_federation_v1 --replication-factor 1 + depends_on: + cassandra: + condition: service_healthy + deploy: + restart_policy: + condition: on-failure + networks: + - demo_wire + + brig-v1: + container_name: brig-federation-v1 + image: quay.io/wire/brig:4.42.0-pre.27 + volumes: + - ./federation-v1:/etc/wire/brig/conf + networks: + - demo_wire + ports: + - '127.0.0.1:22082:8080' + healthcheck: &haskell_health_check + test: "curl --fail localhost:8080/i/status" + depends_on: + brig_schema-v1: + condition: service_completed_successfully + aws_cli: + condition: service_completed_successfully + init_vhosts: + condition: service_completed_successfully + environment: + - AWS_REGION=eu-west-1 + - AWS_ACCESS_KEY_ID=dummykey + - AWS_SECRET_ACCESS_KEY=dummysecret + - RABBITMQ_USERNAME=${RABBITMQ_USERNAME} + - RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD} + + galley_schema-v1: + container_name: galley-schema-federation-v1 + image: quay.io/wire/galley-schema:4.42.0-pre.27 + command: --host cassandra --keyspace galley_test_federation_v1 --replication-factor 1 + depends_on: + cassandra: + condition: service_healthy + deploy: + restart_policy: + condition: on-failure + networks: + - demo_wire + + galley-v1: + container_name: galley-federation-v1 + image: quay.io/wire/galley:4.42.0-pre.27 + volumes: + - ./federation-v1:/etc/wire/galley/conf + networks: + - demo_wire + ports: + - '127.0.0.1:22085:8080' + healthcheck: *haskell_health_check + depends_on: + galley_schema-v1: + condition: service_completed_successfully + aws_cli: + condition: service_completed_successfully + init_vhosts: + condition: service_completed_successfully + environment: + - AWS_REGION=eu-west-1 + - AWS_ACCESS_KEY_ID=dummykey + - AWS_SECRET_ACCESS_KEY=dummysecret + - RABBITMQ_USERNAME=${RABBITMQ_USERNAME} + - RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD} + + cargohold-v1: + container_name: cargohold-federation-v1 + image: quay.io/wire/cargohold:4.42.0-pre.27 + volumes: + - ./federation-v1:/etc/wire/cargohold/conf + networks: + - demo_wire + ports: + - '127.0.0.1:22084:8080' + healthcheck: *haskell_health_check + depends_on: + aws_cli: + condition: service_completed_successfully + environment: + - AWS_REGION=eu-west-1 + - AWS_ACCESS_KEY_ID=dummykey + - AWS_SECRET_ACCESS_KEY=dummysecret + + gundeck_schema-v1: + container_name: gundeck-schema-federation-v1 + image: quay.io/wire/gundeck-schema:4.42.0-pre.27 + command: --host cassandra --keyspace gundeck_test_federation_v1 --replication-factor 1 + depends_on: + cassandra: + condition: service_healthy + deploy: + restart_policy: + condition: on-failure + networks: + - demo_wire + + gundeck-v1: + container_name: gundeck-federation-v1 + image: quay.io/wire/gundeck:4.42.0-pre.27 + volumes: + - ./federation-v1:/etc/wire/gundeck/conf + networks: + - demo_wire + ports: + - '127.0.0.1:22086:8080' + healthcheck: *haskell_health_check + depends_on: + gundeck_schema-v1: + condition: service_completed_successfully + aws_cli: + condition: service_completed_successfully + redis-v1: + condition: service_started + environment: + - AWS_REGION=eu-west-1 + - AWS_ACCESS_KEY_ID=dummykey + - AWS_SECRET_ACCESS_KEY=dummysecret + + spar_schema-v1: + container_name: spar-schema-federation-v1 + image: quay.io/wire/spar-schema:4.42.0-pre.27 + command: --host cassandra --keyspace spar_test_federation_v1 --replication-factor 1 + depends_on: + cassandra: + condition: service_healthy + deploy: + restart_policy: + condition: on-failure + networks: + - demo_wire + + spar-v1: + container_name: spar-federation-v1 + image: quay.io/wire/spar:4.42.0-pre.27 + volumes: + - ./federation-v1:/etc/wire/spar/conf + networks: + - demo_wire + ports: + - '127.0.0.1:22088:8080' + healthcheck: *haskell_health_check + depends_on: + spar_schema-v1: + condition: service_completed_successfully + + cannon-v1: + container_name: cannon-federation-v1 + image: quay.io/wire/cannon:4.42.0-pre.27 + volumes: + - ./federation-v1:/etc/wire/cannon/conf + networks: + - demo_wire + ports: + - '127.0.0.1:22083:8080' + healthcheck: *haskell_health_check + + federator-v1: + container_name: federator-federation-v1 + image: quay.io/wire/federator:4.42.0-pre.27 + volumes: + - ./federation-v1:/etc/wire/federator/conf + networks: + - demo_wire + - coredns + extra_hosts: + - "host.docker.internal.:host-gateway" + ports: + - '127.0.0.1:22097:8080' + - '127.0.0.1:22098:8081' + # healthcheck: *haskell_health_check + healthcheck: + test: "true" + depends_on: + coredns-federation-v1: + condition: service_started + + background_worker-v1: + container_name: background-worker-federation-v1 + image: quay.io/wire/background-worker:4.42.0-pre.27 + volumes: + - ./federation-v1:/etc/wire/background-worker/conf + networks: + - demo_wire + ports: + - '127.0.0.1:22089:8080' + healthcheck: *haskell_health_check + depends_on: + init_vhosts: + condition: service_completed_successfully + environment: + - RABBITMQ_USERNAME=${RABBITMQ_USERNAME} + - RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD} + + proxy-v1: + container_name: proxy-federation-v1 + image: quay.io/wire/proxy:4.42.0-pre.27 + volumes: + - ./federation-v1:/etc/wire/proxy/conf + networks: + - demo_wire + ports: + - '127.0.0.1:22087:8080' + healthcheck: *haskell_health_check + + nginz-v1: + container_name: nginz-federation-v1 + image: quay.io/wire/nginz:4.42.0-pre.27 + volumes: + - ./federation-v1:/etc/wire/ + networks: + - demo_wire + ports: + - '127.0.0.1:22080:8080' + - '127.0.0.1:22443:8443' + depends_on: + brig-v1: + condition: service_healthy + galley-v1: + condition: service_healthy + gundeck-v1: + condition: service_healthy + cargohold-v1: + condition: service_healthy + cannon-v1: + condition: service_healthy + spar-v1: + condition: service_healthy + federator-v1: + condition: service_healthy + proxy-v1: + condition: service_healthy + background_worker-v1: + condition: service_healthy + + # We have to run a separate redis instance for each version of wire-server we + # want. This is because gundeck just assumes the whole redis is for itself + redis-v1: + container_name: redis-federation-v1 + image: redis:6.0-alpine + networks: + - demo_wire + + + # This coredns serves slightly different SRV records, so federator running in + # a docker container can talk to federator running on the host. + coredns-federation-v1: + image: docker.io/coredns/coredns:1.8.4 + volumes: + - ./federation-v1/coredns-config:/coredns-config + entrypoint: + - /coredns + - -conf + - /coredns-config/Corefile + networks: + coredns: + ipv4_address: 172.20.1.4 diff --git a/deploy/dockerephemeral/federation-v1/background-worker.yaml b/deploy/dockerephemeral/federation-v1/background-worker.yaml new file mode 100644 index 00000000000..d70699f7f4e --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/background-worker.yaml @@ -0,0 +1,28 @@ +logLevel: Debug + +backgroundWorker: + host: 0.0.0.0 + port: 8080 + +federatorInternal: + host: federator-federation-v1 + port: 8080 + +galley: + host: galley-federation-v1 + port: 8080 + +brig: + host: brig-federation-v1 + port: 8080 + +rabbitmq: + host: rabbitmq + port: 5672 + vHost: federation-v1 + adminPort: 15672 + +backendNotificationPusher: + pushBackoffMinWait: 1000 + pushBackoffMaxWait: 1000000 + remotesRefreshInterval: 5000000 diff --git a/deploy/dockerephemeral/federation-v1/brig.yaml b/deploy/dockerephemeral/federation-v1/brig.yaml new file mode 100644 index 00000000000..38a3ea856dd --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/brig.yaml @@ -0,0 +1,218 @@ +brig: + host: 0.0.0.0 + port: 8080 + +cassandra: + endpoint: + host: demo_wire_cassandra + port: 9042 + keyspace: brig_test_federation_v1 + # filterNodesByDatacentre: datacenter1 + +elasticsearch: + url: http://demo_wire_elasticsearch:9200 + index: directory_test + +rabbitmq: + host: rabbitmq + port: 5672 + vHost: federation-v1 + +cargohold: + host: cargohold-federation-v1 + port: 8080 + +galley: + host: galley-federation-v1 + port: 8080 + +gundeck: + host: gundeck-federation-v1 + port: 8080 + +federatorInternal: + host: federator-federation-v1 + port: 8080 + +multiSFT: false + +# You can set up local SQS/Dynamo running e.g. `../../deploy/dockerephemeral/run.sh` +aws: + userJournalQueue: integration-user-events-federation-v1.fifo + # ^ Comment this out if you don't want to journal user events + prekeyTable: integration-brig-prekeys-federation-v1 + sqsEndpoint: http://fake_sqs:4568 # https://sqs.eu-west-1.amazonaws.com + # dynamoDBEndpoint: http://localhost:4567 # https://dynamodb.eu-west-1.amazonaws.com + +# Uncomment to use the randomPrekey allocation strategy instead of dynamoDB +randomPrekeys: true + +# Uncomment this if you want STOMP. +# +# stomp: +# stompHost: localhost +# stompPort: 61613 +# stompTls: false + +# TODO: possibly move 'userJournalQueue' to the top level as well +internalEvents: + queueType: sqs + queueName: integration-brig-events-internal-federation-v1 + # queueType: stomp + # queueName: /queue/integration-brig-events-internal + +emailSMS: + # You can either use SES directly (in which case, ensure a feedback queue is configured) + # or you can use SMTP directly (blacklisting of email/phone must be otherwise handled by + # the operator). + email: + sesQueue: integration-brig-events-federation-v1 + sesEndpoint: http://ses:4569 # https://email.eu-west-1.amazonaws.com + # If you prefer to use SMTP directly, uncomment the following lines + # and set the correct credentials. + # NOTE: In case a user tries to supply config values for both SES and SMTP, + # SES takes precedence and gets used instead + # smtpEndpoint: + # host: localhost + # port: 2500 + # smtpCredentials: + # username: + # password: test/resources/smtp-secret.txt + # smtpConnType: plain + # ^ NOTE: blacklisting of emails (processing of bounces and complaints) is only done + # automatically IF sesQueue/sesEndpoint are used. If SMTP is used directly, the + # operator must handle these notifications "manually" (there are internal endpoints) + # that may be used for this + + general: + templateDir: /usr/share/wire/templates + emailSender: backend-integration@wire.com + smsSender: "+123456789" # or MG123456789... (twilio alphanumeric sender id) + templateBranding: + brand: Wire + brandUrl: https://wire.com + brandLabelUrl: wire.com # This is the text in the label for the above URL + brandLogoUrl: https://wire.com/p/img/email/logo-email-black.png + brandService: Wire Service Provider + copyright: © WIRE SWISS GmbH + misuse: misuse@wire.com + legal: https://wire.com/legal/ + forgot: https://wire.com/forgot/ + support: https://support.wire.com/ + user: + activationUrl: http://127.0.0.1:8080/activate?key=${key}&code=${code} + smsActivationUrl: http://127.0.0.1:8080/v/${code} + passwordResetUrl: http://127.0.0.1:8080/password-reset/${key}?code=${code} + invitationUrl: http://127.0.0.1:8080/register?invitation_code=${code} + deletionUrl: http://127.0.0.1:8080/users/delete?key=${key}&code=${code} + + provider: + homeUrl: https://provider.localhost/ + providerActivationUrl: http://127.0.0.1:8080/provider/activate?key=${key}&code=${code} + approvalUrl: http://127.0.0.1:8080/provider/approve?key=${key}&code=${code} + approvalTo: success@simulator.amazonses.com + providerPwResetUrl: http://127.0.0.1:8080/provider/password-reset?key=${key}&code=${code} + + team: + tInvitationUrl: http://127.0.0.1:8080/register?team=${team}&team_code=${code} + tActivationUrl: http://127.0.0.1:8080/register?team=${team}&team_code=${code} + tCreatorWelcomeUrl: http://127.0.0.1:8080/creator-welcome-website + tMemberWelcomeUrl: http://127.0.0.1:8080/member-welcome-website + +zauth: + privateKeys: /etc/wire/brig/conf/zauth-privkeys.txt + publicKeys: /etc/wire/brig/conf/zauth-pubkeys.txt + authSettings: + keyIndex: 1 + userTokenTimeout: 120 + sessionTokenTimeout: 20 + accessTokenTimeout: 30 + providerTokenTimeout: 60 + legalHoldUserTokenTimeout: 120 + legalHoldAccessTokenTimeout: 30 + +turn: + serversSource: dns # files | dns + baseDomain: example.com + discoveryIntervalSeconds: 100 + + # This should be the same secret as used by the TURN servers + secret: /etc/wire/brig/conf/turn-secret.txt + configTTL: 3600 + tokenTTL: 21600 + +optSettings: + setActivationTimeout: 10 + setVerificationTimeout: 10 + setTeamInvitationTimeout: 10 + setExpiredUserCleanupTimeout: 1 + setTwilio: /etc/wire/brig/conf/twilio-credentials.yaml + setNexmo: /etc/wire/brig/conf/nexmo-credentials.yaml + # setStomp: test/resources/stomp-credentials.yaml + setUserMaxConnections: 16 + setCookieInsecure: true + setUserCookieRenewAge: 2 + setUserCookieLimit: 5 + setUserCookieThrottle: + stdDev: 5 + retryAfter: 3 + setLimitFailedLogins: + timeout: 5 # seconds. if you reach the limit, how long do you have to wait to try again. + retryLimit: 5 # how many times can you have a failed login in that timeframe. + setSuspendInactiveUsers: # if this is omitted: never suspend inactive users. + suspendTimeout: 10 + setRichInfoLimit: 5000 # should be in sync with Spar + setDefaultUserLocale: en + setMaxTeamSize: 32 + setMaxConvSize: 16 + setEmailVisibility: visible_to_self + setPropertyMaxKeyLen: 1024 + setPropertyMaxValueLen: 4096 + setDeleteThrottleMillis: 0 + setSqsThrottleMillis: 1000 + setRestrictUserCreation: false + # setSearchSameTeamOnly: false + # ^ NOTE: this filters out search results for team users, + # i.e., if you are a team user the search endpoints will + # return only users part of the same team. For name search, + # this is slightly more inefficient as it requires 2 extra DB lookups + # setUserMaxPermClients: 7 + # ^ You can limit the max number of permanent clients that a user is allowed + # to register, per account. The default value is '7' if the option is unset. + + # Federation domain is used to qualify local IDs and handles, + # e.g. 0c4d8944-70fa-480e-a8b7-9d929862d18c@wire.com and somehandle@wire.com. + # It should also match the SRV DNS records under which other wire-server installations can find this backend: + # _wire-server-federator._tcp. + # Once set, DO NOT change it: if you do, existing users may have a broken experience and/or stop working + # Remember to keep it the same in Galley. + setFederationDomain: federation-v1.example.com + setFeatureFlags: # see #RefConfigOptions in `/docs/reference` + setFederationDomainConfigsUpdateFreq: 1 + setFederationStrategy: allowAll + setFederationDomainConfigs: + - domain: example.com + search_policy: full_search + set2FACodeGenerationDelaySecs: 5 + setDisabledAPIVersions: [] + setNonceTtlSecs: 5 + setDpopMaxSkewSecs: 1 + setDpopTokenExpirationTimeSecs: 300 # 5 minutes + setPublicKeyBundle: /etc/wire/brig/conf/jwt-ed25519-bundle.pem + setEnableMLS: true + # To only allow specific email address domains to register, uncomment and update the setting below + # setAllowlistEmailDomains: + # - wire.com + # To only allow specific phone number prefixes to register uncomment and update the settings below + # setAllowlistPhonePrefixes: + # - "+1555555" + # needs to be kept in sync with services/nginz/integration-test/resources/oauth/ed25519_public.jwk + setOAuthJwkKeyPair: /etc/wire/brig/conf/oauth-ed25519.jwk + setOAuthAuthCodeExpirationTimeSecs: 3 # 3 secs + setOAuthAccessTokenExpirationTimeSecs: 3 # 3 secs + setOAuthEnabled: true + setOAuthRefreshTokenExpirationTimeSecs: 14515200 # 24 weeks + setOAuthMaxActiveRefreshTokens: 10 + +logLevel: Warn +logNetStrings: false diff --git a/deploy/dockerephemeral/federation-v1/cannon.yaml b/deploy/dockerephemeral/federation-v1/cannon.yaml new file mode 100644 index 00000000000..c091ea67ed2 --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/cannon.yaml @@ -0,0 +1,26 @@ +# Example yaml-formatted configuration for cannon used in integration tests + +# cannon can be started with a config file (e.g. ./dist/cannon -c cannon.yaml.example) + +cannon: + host: 0.0.0.0 + port: 8080 + + # Each cannon instance advertises its own location (ip or dns name) to gundeck. + # Either externalHost or externalHostFile must be set (externalHost takes precedence if both are defined) + # externalHostFile expects a file with a single line containing the IP or dns name of this instance of cannon + externalHost: cannon-federation-v1 + #externalHostFile: /etc/wire/cannon/cannon-host.txt + +gundeck: + host: gundeck-federation-v1 + port: 8080 + +drainOpts: + gracePeriodSeconds: 1 + millisecondsBetweenBatches: 500 + minBatchSize: 5 + +disabledAPIVersions: [] +logLevel: Warn +logNetStrings: false diff --git a/deploy/dockerephemeral/federation-v1/cargohold.yaml b/deploy/dockerephemeral/federation-v1/cargohold.yaml new file mode 100644 index 00000000000..4913082900e --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/cargohold.yaml @@ -0,0 +1,33 @@ +brig: + host: brig-federation-v1 + port: 8080 + +cargohold: + host: 0.0.0.0 + port: 8080 + +federator: + host: federator-federation-v1 + port: 8080 + +aws: + s3Bucket: dummy-bucket-federation-v1 # <-- insert-bucket-name-here + s3Endpoint: http://fake_s3:4570 # https://s3-eu-west-1.amazonaws.com:443 + # s3DownloadEndpoint: http://fake-s3:4570 + # ^ When not using a real S3 service, we may need to use a different, + # publicly accessible endpoint for downloading assets. + # + # If you want to use cloudfront for asset downloads + # cloudFront: + # domain: + # keyPairId: + # privateKey: cf-pk.pem + +settings: + maxTotalBytes: 27262976 + downloadLinkTTL: 300 # Seconds + federationDomain: example.com + disabledAPIVersions: [] + +logLevel: Warn +logNetStrings: false diff --git a/deploy/dockerephemeral/federation-v1/coredns-config/Corefile b/deploy/dockerephemeral/federation-v1/coredns-config/Corefile new file mode 100644 index 00000000000..7bf495f2e89 --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/coredns-config/Corefile @@ -0,0 +1,4 @@ +example.com { + file /coredns-config/db.example.com + log +} \ No newline at end of file diff --git a/deploy/dockerephemeral/federation-v1/coredns-config/db.example.com b/deploy/dockerephemeral/federation-v1/coredns-config/db.example.com new file mode 100644 index 00000000000..407f4770916 --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/coredns-config/db.example.com @@ -0,0 +1,21 @@ +$ORIGIN example.com. +@ 3600 IN SOA sns.dns.icann.org. noc.dns.icann.org. ( + 2017042745 ; serial + 7200 ; refresh (2 hours) + 3600 ; retry (1 hour) + 1209600 ; expire (2 weeks) + 3600 ; minimum (1 hour) + ) + + 3600 IN NS a.iana-servers.net. + 3600 IN NS b.iana-servers.net. + +www IN A 127.0.0.1 + IN AAAA ::1 +_wire-server-federator._tcp IN SRV 0 0 8443 host.docker.internal. +_wire-server-federator._tcp.b IN SRV 0 0 9443 host.docker.internal. +_wire-server-federator._tcp.d1 IN SRV 0 0 10443 host.docker.internal. +_wire-server-federator._tcp.d2 IN SRV 0 0 11443 host.docker.internal. +_wire-server-federator._tcp.d3 IN SRV 0 0 12443 host.docker.internal. +_wire-server-federator._tcp.federation-v0 IN SRV 0 0 21443 host.docker.internal. +_wire-server-federator._tcp.federation-v1 IN SRV 0 0 22443 host.docker.internal. diff --git a/deploy/dockerephemeral/federation-v1/federator.yaml b/deploy/dockerephemeral/federation-v1/federator.yaml new file mode 100644 index 00000000000..1973a0540be --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/federator.yaml @@ -0,0 +1,29 @@ +federatorInternal: + host: 0.0.0.0 + port: 8080 +federatorExternal: + host: 0.0.0.0 + port: 8081 +brig: + host: brig-federation-v1 + port: 8080 +cargohold: + host: cargohold-federation-v1 + port: 8080 +galley: + host: galley-federation-v1 + port: 8080 + +logLevel: Warn +logNetStrings: false + +optSettings: + # Filepath to one or more PEM-encoded server certificates to use as a trust + # store when making requests to remote backends + remoteCAStore: "/etc/wire/federator/conf/integration-ca.pem" + useSystemCAStore: false + clientCertificate: "/etc/wire/federator/conf/integration-leaf.pem" + clientPrivateKey: "/etc/wire/federator/conf/integration-leaf-key.pem" + tcpConnectionTimeout: 5000000 + dnsHost: 172.20.1.3 + dnsPort: 53 diff --git a/deploy/dockerephemeral/federation-v1/galley.yaml b/deploy/dockerephemeral/federation-v1/galley.yaml new file mode 100644 index 00000000000..f272536260c --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/galley.yaml @@ -0,0 +1,107 @@ +galley: + host: 0.0.0.0 + port: 8080 + +cassandra: + endpoint: + host: demo_wire_cassandra + port: 9042 + keyspace: galley_test_federation_v1 + # filterNodesByDatacentre: datacenter1 + +brig: + host: brig-federation-v1 + port: 8080 + +gundeck: + host: gundeck-federation-v1 + port: 8080 + +spar: + host: spar-federation-v1 + port: 8080 + +federator: + host: federator-federation-v1 + port: 8080 + +rabbitmq: + host: rabbitmq + port: 5672 + vHost: federation-v1 + +settings: + httpPoolSize: 128 + maxTeamSize: 32 + maxFanoutSize: 18 + exposeInvitationURLsTeamAllowlist: [] + maxConvSize: 16 + intraListing: false + conversationCodeURI: https://account.wire.com/conversation-join/ + concurrentDeletionEvents: 1024 + deleteConvThrottleMillis: 0 + # Federation domain is used to qualify local IDs and handles, + # e.g. 0c4d8944-70fa-480e-a8b7-9d929862d18c@wire.com and somehandle@wire.com. + # It should also match the SRV DNS records under which other wire-server installations can find this backend: + # _wire-server-federator._tcp. + # Once set, DO NOT change it: if you do, existing users may have a broken experience and/or stop working + # Remember to keep it the same in Brig + federationDomain: federation-v1.example.com + mlsPrivateKeyPaths: + removal: + ed25519: /etc/wire/galley/conf/mls-private-key-ed25519.pem + guestLinkTTLSeconds: 604800 + disabledAPIVersions: [] + + featureFlags: # see #RefConfigOptions in `/docs/reference` + sso: disabled-by-default + legalhold: whitelist-teams-and-implicit-consent + teamSearchVisibility: disabled-by-default + appLock: + defaults: + status: enabled + config: + enforceAppLock: false + inactivityTimeoutSecs: 60 + classifiedDomains: + status: enabled + config: + domains: ["example.com"] + fileSharing: + defaults: + status: enabled + lockStatus: unlocked + conferenceCalling: + defaults: + status: enabled + outlookCalIntegration: + defaults: + status: disabled + lockStatus: locked + mlsE2EId: + defaults: + status: disabled + config: + verificationExpiration: 86400 + acmeDiscoveryUrl: null + lockStatus: unlocked + mlsMigration: + defaults: + status: enabled + config: + startTime: "2029-05-16T10:11:12.123Z" + finaliseRegardlessAfter: "2029-10-17T00:00:00.000Z" + usersThreshold: 100 + clientsThreshold: 50 + lockStatus: locked + limitedEventFanout: + defaults: + status: disabled + +logLevel: Warn +logNetStrings: false + +journal: # if set, journals; if not set, disables journaling + queueName: integration-team-events-federation-v1.fifo + endpoint: http://demo_wire_sqs:4568 # https://sqs.eu-west-1.amazonaws.com + region: eu-west-1 diff --git a/deploy/dockerephemeral/federation-v1/gundeck.yaml b/deploy/dockerephemeral/federation-v1/gundeck.yaml new file mode 100644 index 00000000000..6722f2e0b90 --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/gundeck.yaml @@ -0,0 +1,46 @@ +gundeck: + host: 0.0.0.0 + port: 8080 + +brig: + host: brig-federation-v1 + port: 8080 + +cassandra: + endpoint: + host: demo_wire_cassandra + port: 9042 + keyspace: gundeck_test_federation_v1 + # filterNodesByDatacentre: datacenter1 + +redis: + host: redis-federation-v1 + port: 6379 + connectionMode: master + +# redisAdditionalWrite: +# host: 127.0.0.1 +# port: 6379 +# connectionMode: master + +aws: + queueName: integration-gundeck-events-federation-v1 + region: eu-west-1 + account: "123456789012" # Default account nr used by localstack + arnEnv: integration + sqsEndpoint: http://demo_wire_sqs:4568 # https://sqs.eu-west-1.amazonaws.com + snsEndpoint: http://demo_wire_sns:4575 # https://sns.eu-west-1.amazonaws.com + +settings: + httpPoolSize: 1024 + notificationTTL: 24192200 + bulkPush: true + perNativePushConcurrency: 32 + sqsThrottleMillis: 1000 + maxConcurrentNativePushes: + hard: 30 # more than this number of threads will not be allowed + soft: 10 # more than this number of threads will be warned about + disabledAPIVersions: [] + +logLevel: Warn +logNetStrings: false diff --git a/deploy/dockerephemeral/federation-v1/integration-ca.pem b/deploy/dockerephemeral/federation-v1/integration-ca.pem new file mode 100644 index 00000000000..6a33fa9e2c3 --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/integration-ca.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDEzCCAfugAwIBAgIUEfjIXW9tD1WgwNHJ+kC3r6Cmv5swDQYJKoZIhvcNAQEL +BQAwGTEXMBUGA1UEAwwOY2EuZXhhbXBsZS5jb20wHhcNMjQwOTAzMTIwMzM3WhcN +MzQwOTAxMTIwMzM3WjAZMRcwFQYDVQQDDA5jYS5leGFtcGxlLmNvbTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAJkd+ihX0Tp6RG4Ue9kQFvTHFy2DL8rC +8u956hy17/9sTyIcMkTOtZMew+ua+ZrNzX1x1WP4BT5RZQA/wy6ioEpP5BVFxe9s +dmVqYawD9C5+XKTekEFF3gI0MIGZ4Vum2hdUOHTwDatAgdiqsBwuCxHM5terItzZ +SsrkYFlAhISmM9CsUFOR+1rqpMNWRQD5zzZ0Sk5HeyM1/9sHvkrq24rqKqkg0/uR +OTHMydrmuSArG5tKqkdb2zoJwOs8somraRzB1JOm5/i/3pCT5iBr5gf8eLGtb2Gu +XgmnTI129R2a+LbifqTC31jIMg/yNfFuU4MdUG9wE512ghhXZI+Hoc8CAwEAAaNT +MFEwHQYDVR0OBBYEFFqlhDsVqlH8UUKGOtCDE9xmYL3hMB8GA1UdIwQYMBaAFFql +hDsVqlH8UUKGOtCDE9xmYL3hMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBADQyh0k3xhcrNOqhvzAK1/A3TY5Hq1FE/a17Yiq8DIiLCJG/nAN60GBx +m7zuL8xoRJ4ylwIswa4z4rHj9p6M6tIbi2tTfJsbyB+FjyFRWoBmTngqNCiw7QUR +/ofSliuEu/YIjphR8LmTBvy4fVccTwXDaBPEGf2iN+DFmryLHxVpsVh3AA0uUSy0 +e2bZJLRwv1z0saC5KGHpWb6RJbAP2nRw5omcorMtP1KW8XyVESiJm7hDZAx6VLgD +k4GcEOUEq9CJs9UVAkIIDS87CfppHZEGPDK3Ufro5AhIwA3hpSJkTgzkf0TcQKIr +4E/zJSeTld4rMC26ghWodIwGRyofTP0= +-----END CERTIFICATE----- diff --git a/deploy/dockerephemeral/federation-v1/integration-leaf-key.pem b/deploy/dockerephemeral/federation-v1/integration-leaf-key.pem new file mode 100644 index 00000000000..e4ee0a09ab1 --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/integration-leaf-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDPYxemHc21P8y2 +DIDNsuUzVpnc1bj81QIQ5a2aFSVtQFr3Dhpo0XXyu0KHKR64ZelmW33Yoe2J0zwl +R66fg07whBcZspsonXmsUB5uFFF/Dcv9shBNcg67jjynXPDvJK2sC37GCar5ar/v +11v/+Iq6LNerC/OqMJlMYyFDif1BvESbnw+9hsihUT9qK5s6md5krEV1/ro0Noh7 +kBgflylEKjapfCrVMnAMgaMV55jrk5BTpoR6KCDuDmXPk3Ed304Op/krGazfGB2j +1rz3IPfKOOtIgxn8n5PDYmShVUw1R3Rps1LeEyhsH1EGuAvVx1mKhB3QGMqFtIfo +wOpkgZPdAgMBAAECggEAVMurFUB1ZkkmXj9hgPnHLoUX10xJ3ZMIy7jlkS1ZRsD8 +EK0jDj2q0OtRSet9xJ7i3nfFTojzE5obqxCSrWUmp0ATI+4789DjuZluv8quAdm1 +0U73zHq43GZNlY7yco2YN1Lh7H5yepXz0dDILLLGolYIfscdw7YoUCvuI2vt8eyY +FaMkOi8Bs32Abn+53MhaNkEOVemmwP/u2rD81IY7pXQtF+1XfSxYBipHCB7phC1g +dr6ITU9CoF3SXvjYL6uP/7W1Du/CuaDSijVZJkBfQSgbkQbBr6pAovumtpNZgZvO +bixqS4oTZqsd+Rgl3YBOpx+JbuUFL0fG+uQBv1yZbwKBgQD8n7s1w/ouUGwW+qGi +4EFTYVDFD2Rgvg3oFPHt72zreaNXSy8YWrK9Mas/Fzu5knSE3f3O0S1WvqWdt/z6 +uPDLTpg9fIWX6v+hPX09F5ekeLlUDBazBT1PQUD0qd8PiyFNB1F+ffSPTxmKScjc +hTqQCtnun6rlICalWO9VvGt6QwKBgQDSKJkjxIGLtXBaXfMQiF9dQDrMk2it5grl +w0OnpPhYvpdp+Cfi01kMUrnfHwF0v92BqeqoKZo4DwJXkrmwf5kNnftxmRqbk9gE +dJq/E/6SELyT/chtzXfxC/wTmyyxhfZUJvJUxlaZ6KP/86t8A22DJcFO2Z85iUGH +8zy2UJUnXwKBgBvSs9m+FeXX8a+uNvMrY8Z9J1os0c9d30Y6WFLuVb6xjO3mV++E +vb7co5G1S1yq5q5jjLqkiyvMn4z5YKF0kQCzTU0oU8ZhmXn2vb5mxMrWiQLauf1J +jHEYLMFFnE2n8yj6r10RHkhSW+vBKKAxBDwtFceUSkwl+FupqeJ1eBjlAoGBAMou ++LWqdZ89HSwzOobrTCPgiTELmCfFKzLE2q/MTIjEQ9NVRLo57m+mnt+DatkxRR9b +oz/JVm8cMXqi1DZza4HoPWGalDic0bPnooC18bIAnAwcmdjZVcz3ZLpQDX10jfmD +xpu8fNBxOmYhvRcADTmg9wqu3zpxTDRI1F3pxLUtAoGAGfsX4bve5cLm49Oa1p0H +kEErLMuAMIKQNVsbzVELepLYr+uwEXBCXyyoIf79ABDvUHbzxMEwgANuet/4PQzS +yB1qzFk6GDvqZ5dfPUgMUWH9wvD1qEGp6yxkyESGt8CNwnu8GI50NAeSh2/JeUIa +r/u+m2vnJjOXpJdOJ+7f6yM= +-----END PRIVATE KEY----- diff --git a/deploy/dockerephemeral/federation-v1/integration-leaf.pem b/deploy/dockerephemeral/federation-v1/integration-leaf.pem new file mode 100644 index 00000000000..abd724df6b1 --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/integration-leaf.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgIBADANBgkqhkiG9w0BAQsFADAZMRcwFQYDVQQDDA5jYS5l +eGFtcGxlLmNvbTAeFw0yNDA5MDMxMjAzMzhaFw0zNDA5MDExMjAzMzhaMAAwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPYxemHc21P8y2DIDNsuUzVpnc +1bj81QIQ5a2aFSVtQFr3Dhpo0XXyu0KHKR64ZelmW33Yoe2J0zwlR66fg07whBcZ +spsonXmsUB5uFFF/Dcv9shBNcg67jjynXPDvJK2sC37GCar5ar/v11v/+Iq6LNer +C/OqMJlMYyFDif1BvESbnw+9hsihUT9qK5s6md5krEV1/ro0Noh7kBgflylEKjap +fCrVMnAMgaMV55jrk5BTpoR6KCDuDmXPk3Ed304Op/krGazfGB2j1rz3IPfKOOtI +gxn8n5PDYmShVUw1R3Rps1LeEyhsH1EGuAvVx1mKhB3QGMqFtIfowOpkgZPdAgMB +AAGjgawwgakwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMEgGA1UdEQEB +/wQ+MDyCGSouaW50ZWdyYXRpb24uZXhhbXBsZS5jb22CFGhvc3QuZG9ja2VyLmlu +dGVybmFsgglsb2NhbGhvc3QwHQYDVR0OBBYEFBkpAu3ILiU4gtEYffAU7zxGHPC6 +MB8GA1UdIwQYMBaAFFqlhDsVqlH8UUKGOtCDE9xmYL3hMA0GCSqGSIb3DQEBCwUA +A4IBAQBB1VsthdoVT9ExXkfKixotbXm6+eBgYenK1R5Qx/UX3JrlI1nF/8rKMg5e +7QfMCydSJwVEQdvnXD3ddVhUTYRActQvnJwWTyXfeiezrfDCTLu4SNpLOP7ojFlq +9ZX/E9GC0axTIUmEIy8YIC3JJ2PAlvw9qMzrsivyAgbof3NX+9XXKfwZHBwSLsO1 +Gxr9zkL+U/qww7TvyJD1LqBR0UEd9pZriorpVVFAa/JlFQX5ip1Smcd6m97nq20N +qpUIalra+K6qHxjHVwA2UxVgbO9bLFIBmp9pNvSm+5umAKkmqFnHRNAHfCy/IFGl +3fw8u9mXJ8LzUR4tiS0cVb6bwQzd +-----END CERTIFICATE----- diff --git a/deploy/dockerephemeral/federation-v1/jwt-ed25519-bundle.pem b/deploy/dockerephemeral/federation-v1/jwt-ed25519-bundle.pem new file mode 100644 index 00000000000..afbd4dfb0ec --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/jwt-ed25519-bundle.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIFANnxZLNE4p+GDzWzR3wm/v8x/0bxZYkCyke1aTRucX +-----END PRIVATE KEY----- +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEACPvhIdimF20tOPjbb+fXJrwS2RKDp7686T90AZ0+Th8= +-----END PUBLIC KEY----- diff --git a/deploy/dockerephemeral/federation-v1/mls-private-key-ed25519.pem b/deploy/dockerephemeral/federation-v1/mls-private-key-ed25519.pem new file mode 100644 index 00000000000..182df6f5a7d --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/mls-private-key-ed25519.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIKqoSUVW579Aw8Nz47CRwArSigl/25jg0suQmg6mOwdy +-----END PRIVATE KEY----- diff --git a/deploy/dockerephemeral/federation-v1/nexmo-credentials.yaml b/deploy/dockerephemeral/federation-v1/nexmo-credentials.yaml new file mode 100644 index 00000000000..1f83517f2ee --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/nexmo-credentials.yaml @@ -0,0 +1,2 @@ +key: "dummy" +secret: "dummy" diff --git a/deploy/dockerephemeral/federation-v1/nginz/conf/README.md b/deploy/dockerephemeral/federation-v1/nginz/conf/README.md new file mode 100644 index 00000000000..8e614e99d1b --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/nginz/conf/README.md @@ -0,0 +1,7 @@ +# How to regenerate certificates in this directory + +Run from this directory: + +```bash +../../../../../hack/bin/gen-certs.sh +``` diff --git a/deploy/dockerephemeral/federation-v1/nginz/conf/common_response.conf b/deploy/dockerephemeral/federation-v1/nginz/conf/common_response.conf new file mode 100644 index 00000000000..1b8a947f437 --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/nginz/conf/common_response.conf @@ -0,0 +1,38 @@ + # remove access_token from logs, see 'Note sanitized_request'. + set $sanitized_request $request; + if ($sanitized_request ~ (.*)access_token=[^&]*(.*)) { + set $sanitized_request $1access_token=****$2; + } + + # Should be overriden when using websockets + proxy_set_header Connection ""; + proxy_set_header Z-Type $zauth_type; + proxy_set_header Z-User $zauth_user; + proxy_set_header Z-Client $zauth_client; + proxy_set_header Z-Connection $zauth_connection; + proxy_set_header Z-Provider $zauth_provider; + proxy_set_header Z-Bot $zauth_bot; + proxy_set_header Z-Conversation $zauth_conversation; + proxy_set_header Request-Id $request_id; + + # NOTE: This should only be used on endpoints where credentials are needed + more_set_headers 'Access-Control-Allow-Credentials: true'; + # NOTE: This allows all origins, you may want to tune this value + more_set_headers 'Access-Control-Allow-Origin: $http_origin'; + more_set_headers 'Access-Control-Expose-Headers: Request-Id, Location'; + more_set_headers 'Request-Id: $request_id'; + more_set_headers 'Strict-Transport-Security: max-age=31536000; preload'; + + if ($request_method = 'OPTIONS') { + add_header 'Access-Control-Allow-Methods' "GET, POST, PUT, DELETE, OPTIONS"; + add_header 'Access-Control-Allow-Headers' "$http_access_control_request_headers, DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type"; + add_header 'Content-Type' 'text/plain; charset=UTF-8'; + add_header 'Content-Length' 0; + return 204; + } + + + proxy_http_version 1.1; + + # NOTE: You may want to tune this + client_max_body_size 64M; diff --git a/deploy/dockerephemeral/federation-v1/nginz/conf/common_response_no_zauth.conf b/deploy/dockerephemeral/federation-v1/nginz/conf/common_response_no_zauth.conf new file mode 100644 index 00000000000..4277ede8c0f --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/nginz/conf/common_response_no_zauth.conf @@ -0,0 +1,2 @@ + zauth off; + include common_response.conf; diff --git a/deploy/dockerephemeral/federation-v1/nginz/conf/common_response_with_zauth.conf b/deploy/dockerephemeral/federation-v1/nginz/conf/common_response_with_zauth.conf new file mode 100644 index 00000000000..699dd263b31 --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/nginz/conf/common_response_with_zauth.conf @@ -0,0 +1,3 @@ + include common_response.conf; + proxy_set_header Authorization ""; + proxy_set_header Z-Host $host; diff --git a/deploy/dockerephemeral/federation-v1/nginz/conf/integration.conf b/deploy/dockerephemeral/federation-v1/nginz/conf/integration.conf new file mode 100644 index 00000000000..12c49ccfe88 --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/nginz/conf/integration.conf @@ -0,0 +1,19 @@ +# plain TCP/http listening for integration tests only. +listen 8080; +listen 8081; + +# for nginx-without-tls, we need to use a separate port for http2 traffic, +# as nginx cannot handle unencrypted http1 and http2 traffic on the same +# port. +# This port is only used for trying out nginx http2 forwarding without TLS locally and should not +# be ported to any production nginz config. +listen 8090 http2; + +######## TLS/SSL block start ############## +# +# Most integration tests simply use the http ports 8080 and 8081 +# But to also test tls forwarding, this port can be used. +# This applies only locally, as for kubernetes (helm chart) based deployments, +# TLS is terminated at the ingress level, not at nginz level +listen 8443 ssl http2; +listen [::]:8443 ssl http2; diff --git a/deploy/dockerephemeral/federation-v1/nginz/conf/nginx.conf b/deploy/dockerephemeral/federation-v1/nginz/conf/nginx.conf new file mode 100644 index 00000000000..bef49f1d046 --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/nginz/conf/nginx.conf @@ -0,0 +1,498 @@ +worker_processes 4; +worker_rlimit_nofile 1024; +include pid.conf; # for easy overriding + +# nb. start up errors (eg. misconfiguration) may still end up in /$(LOG_PATH)/error.log +error_log stderr warn; + +events { + worker_connections 1024; + multi_accept off; +} + +http { + # + # Some temporary paths (by default, will use the `prefix` path given when starting nginx) + # + + client_body_temp_path /tmp; + fastcgi_temp_path /tmp; + proxy_temp_path /tmp; + scgi_temp_path /tmp; + uwsgi_temp_path /tmp; + + # + # Sockets + # + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + + # + # Timeouts + # + + client_body_timeout 60; + client_header_timeout 60; + keepalive_timeout 75; + send_timeout 60; + + ignore_invalid_headers off; + + types_hash_max_size 2048; + + server_names_hash_bucket_size 64; + server_name_in_redirect off; + + large_client_header_buffers 4 8k; + + # + # Security + # + + server_tokens off; + + # + # Logging + # + # Note sanitized_request: + # We allow passing access_token as query parameter for e.g. websockets + # However we do not want to log access tokens. + # + + log_format custom_zeta '$remote_addr - $remote_user [$time_local] "$sanitized_request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for" - $connection $request_time $upstream_response_time $upstream_cache_status $zauth_user $zauth_connection $request_id $proxy_protocol_addr'; + access_log /dev/stdout custom_zeta; + + # + # Monitoring + # + vhost_traffic_status_zone; + + # + # Gzip + # + + gzip on; + gzip_disable msie6; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_buffers 16 8k; + gzip_http_version 1.1; + gzip_min_length 1024; + gzip_types 'text/plain text/css application/json text/xml'; + + # + # Proxied Upstream Services + # + + include ../upstreams; + + # + # Mapping for websocket connections + # + + map $http_upgrade $connection_upgrade { + websocket upgrade; + default ''; + } + + # + # Locations + # + + server { + include integration.conf; + + # self-signed certificates generated using wire-server/hack/bin/gen-certs.sh + ssl_certificate /etc/wire/integration-leaf.pem; + ssl_certificate_key /etc/wire/integration-leaf-key.pem; + + ssl_verify_client on; + ssl_client_certificate /etc/wire/integration-ca.pem; + ######## TLS/SSL block end ############## + + zauth_keystore /etc/wire/zauth-pubkeys.txt; + zauth_acl /etc/wire/nginz/conf/zauth_acl.txt; + # needs to be kept in sync with services/brig/test/resources/oauth/ed25519.jwk + oauth_pub_key /etc/wire/oauth-ed25519_public.jwk; + + location /status { + set $sanitized_request $request; + zauth off; + return 200; + } + + location /i/status { + set $sanitized_request $request; + zauth off; + return 200; + } + + location /vts { + set $sanitized_request $request; + zauth off; + vhost_traffic_status_display; + vhost_traffic_status_display_format html; + } + + # + # Service Routing + # + + # Federator endpoints: expose the federatorExternal port (Inward service) + location /federation { + set $sanitized_request $request; + zauth off; + + proxy_set_header "X-SSL-Certificate" $ssl_client_escaped_cert; + proxy_pass http://federator_external; + + # FUTUREWORK(federation): are any other settings + # (e.g. timeouts, body size, buffers, headers,...) + # useful/recommended/important-for-security?) + } + + # Brig Endpoints + # + ## brig unauthenticated endpoints + + location ~* ^(/v[0-9]+)?/api/swagger-ui { + include common_response_no_zauth.conf; + proxy_pass http://brig; + } + + location ~* ^(/v[0-9]+)?/api/swagger.json { + include common_response_no_zauth.conf; + proxy_pass http://brig; + } + + location ~* ^(/v[0-9]+)?/api-internal/swagger-ui { + include common_response_no_zauth.conf; + proxy_pass http://brig; + } + + location ~* ^(/v[0-9]+)?/api-internal/swagger.json { + include common_response_no_zauth.conf; + proxy_pass http://brig; + } + + location /register { + include common_response_no_zauth.conf; + proxy_pass http://brig; + } + + location /access { + include common_response_no_zauth.conf; + proxy_pass http://brig; + } + + location /activate { + include common_response_no_zauth.conf; + proxy_pass http://brig; + } + + location /login { + include common_response_no_zauth.conf; + proxy_pass http://brig; + } + + location ~* ^(/v[0-9]+)?/teams/invitations/([^/]*)$ { + include common_response_no_zauth.conf; + proxy_pass http://brig; + } + + location /verification-code/send { + include common_response_no_zauth.conf; + proxy_pass http://brig; + } + + ## brig authenticated endpoints + + location ~* ^(/v[0-9]+)?/self$ { + include common_response_with_zauth.conf; + oauth_scope self; + proxy_pass http://brig; + } + + location /users { + include common_response_with_zauth.conf; + proxy_pass http://brig; + } + + location /list-users { + include common_response_with_zauth.conf; + proxy_pass http://brig; + } + + location /search { + include common_response_with_zauth.conf; + proxy_pass http://brig; + } + + location /list-connections { + include common_response_with_zauth.conf; + proxy_pass http://brig; + } + + location ~* ^/teams/([^/]+)/search$ { + include common_response_with_zauth.conf; + proxy_pass http://brig; + } + + location /connections { + include common_response_with_zauth.conf; + proxy_pass http://brig; + } + + location ~* ^(/v[0-9]+)?/clients { + include common_response_with_zauth.conf; + proxy_pass http://brig; + } + + location ~* ^(/v[0-9]+)?/mls/key-packages { + include common_response_with_zauth.conf; + proxy_pass http://brig; + } + + location /properties { + include common_response_with_zauth.conf; + proxy_pass http://brig; + } + + location /calls/config { + include common_response_with_zauth.conf; + proxy_pass http://brig; + } + + location ~* ^/teams/([^/]*)/size$ { + include common_response_with_zauth.conf; + proxy_pass http://brig; + } + + location ~* ^(/v[0-9]+)?/system/settings/unauthorized$ { + include common_response_no_zauth.conf; + proxy_pass http://brig; + } + + location ~* ^(/v[0-9]+)?/system/settings$ { + include common_response_with_zauth.conf; + proxy_pass http://brig; + } + + location ~* ^/oauth/clients/([^/]*)$ { + include common_response_with_zauth.conf; + proxy_pass http://brig; + } + + location ~* ^/oauth/authorization/codes$ { + include common_response_with_zauth.conf; + proxy_pass http://brig; + } + + location /oauth/token { + include common_response_no_zauth.conf; + proxy_pass http://brig; + } + + location /oauth/revoke { + include common_response_no_zauth.conf; + proxy_pass http://brig; + } + + location /oauth/applications { + include common_response_with_zauth.conf; + proxy_pass http://brig; + } + + # Cargohold Endpoints + + location ~* ^(/v[0-9]+)?/assets { + include common_response_with_zauth.conf; + proxy_pass http://cargohold; + } + + location /assets { + include common_response_with_zauth.conf; + proxy_pass http://cargohold; + } + + location /bot/assets { + include common_response_with_zauth.conf; + proxy_pass http://cargohold; + } + + location /provider/assets { + include common_response_with_zauth.conf; + proxy_pass http://cargohold; + } + + # Galley Endpoints + + location ~* ^(/v[0-9]+)?/legalhold/conversations/(.*)$ { + include common_response_with_zauth.conf; + proxy_pass http://galley; + } + + location ~* ^(/v[0-9]+)?/conversations$ { + include common_response_with_zauth.conf; + oauth_scope conversations; + proxy_pass http://galley; + } + + location ~* ^(/v[0-9]+)?/conversations/([^/]*)/code { + include common_response_with_zauth.conf; + oauth_scope conversations_code; + proxy_pass http://galley; + } + + location ~* ^(/v[0-9]+)?/conversations.* { + include common_response_with_zauth.conf; + proxy_pass http://galley; + } + + location ~* ^/conversations/([^/]*)/otr/messages { + include common_response_with_zauth.conf; + proxy_pass http://galley; + } + + location ~* ^/conversations/([^/]*)/([^/]*)/proteus/messages { + include common_response_with_zauth.conf; + proxy_pass http://galley; + } + + location /broadcast { + include common_response_with_zauth.conf; + proxy_pass http://galley; + } + + location /bot/conversation { + include common_response_with_zauth.conf; + proxy_pass http://galley; + } + + location /bot/messages { + include common_response_with_zauth.conf; + proxy_pass http://galley; + } + + location ~* ^/teams$ { + include common_response_with_zauth.conf; + proxy_pass http://galley; + } + + location ~* ^/teams/([^/]*)$ { + include common_response_with_zauth.conf; + proxy_pass http://galley; + } + + location ~* ^/teams/([^/]*)/members(.*) { + include common_response_with_zauth.conf; + proxy_pass http://galley; + } + + location ~* ^/teams/([^/]*)/conversations(.*) { + include common_response_with_zauth.conf; + proxy_pass http://galley; + } + + location ~* ^/teams/([^/]*)/features { + include common_response_with_zauth.conf; + proxy_pass http://galley; + } + + location ~* ^/teams/([^/]*)/features/([^/]*) { + include common_response_with_zauth.conf; + proxy_pass http://galley; + } + + location ~* ^(/v[0-9]+)?/feature-configs$ { + include common_response_with_zauth.conf; + oauth_scope feature_configs; + proxy_pass http://galley; + } + + location ~* ^(/v[0-9]+)?/feature-configs(.*) { + include common_response_with_zauth.conf; + proxy_pass http://galley; + } + + location ~* ^/teams/([^/]*)/legalhold(.*) { + include common_response_with_zauth.conf; + proxy_pass http://galley; + } + + location ~* ^/teams/([^/]*)/members/csv$ { + include common_response_with_zauth.conf; + proxy_pass http://galley; + } + + location /mls/welcome { + include common_response_with_zauth.conf; + proxy_pass http://galley; + } + + location /mls/messages { + include common_response_with_zauth.conf; + proxy_pass http://galley; + } + + location ~* ^(/v[0-9]+)?/mls/commit-bundles { + include common_response_with_zauth.conf; + proxy_pass http://galley; + } + + location ~* ^(/v[0-9]+)?/mls/public-keys { + include common_response_with_zauth.conf; + proxy_pass http://galley; + } + + # Gundeck Endpoints + + location /push { + include common_response_with_zauth.conf; + proxy_pass http://gundeck; + } + + location /presences { + include common_response_with_zauth.conf; + proxy_pass http://gundeck; + } + + location ~* ^(/v[0-9]+)?/notifications$ { + include common_response_with_zauth.conf; + proxy_pass http://gundeck; + } + + # Proxy Endpoints + + location /proxy { + include common_response_with_zauth.conf; + proxy_pass http://proxy; + } + + # Cannon Endpoints + + location /await { + include common_response_with_zauth.conf; + proxy_pass http://cannon; + + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_read_timeout 1h; + } + + # Spar Endpoints + + location /sso { + include common_response_no_zauth.conf; + proxy_pass http://spar; + } + + location /identity-providers { + include common_response_with_zauth.conf; + proxy_pass http://spar; + } + } +} diff --git a/deploy/dockerephemeral/federation-v1/nginz/conf/pid.conf b/deploy/dockerephemeral/federation-v1/nginz/conf/pid.conf new file mode 100644 index 00000000000..e722aa5ae23 --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/nginz/conf/pid.conf @@ -0,0 +1 @@ +pid /tmp/nginz.pid; diff --git a/deploy/dockerephemeral/federation-v1/nginz/conf/zauth_acl.txt b/deploy/dockerephemeral/federation-v1/nginz/conf/zauth_acl.txt new file mode 100644 index 00000000000..3b644bf3d98 --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/nginz/conf/zauth_acl.txt @@ -0,0 +1,15 @@ +a (blacklist (regex "(/v[0-9]+)?/provider(/.*)?") + (regex "(/v[0-9]+)?/bot(/.*)?") + (regex "(/v[0-9]+)?/i/.*")) + +b (whitelist (regex "(/v[0-9]+)?/bot(/.*)?")) + +p (whitelist (regex "(/v[0-9]+)?/provider(/.*)?")) + +# LegalHold Access Tokens +# FUTUREWORK: remove /legalhold/conversations/ when support for v1 dropped +la (whitelist (regex "(/v[0-9]+)?/notifications") + (regex "(/v[0-9]+)?/assets/v3/.*") + (regex "(/v[0-9]+)?/users(/.*)?") + (regex "(/v[0-9]+)?/legalhold/conversations/[^/]+") + (regex "(/v[0-9]+)?/conversations/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$")) diff --git a/deploy/dockerephemeral/federation-v1/nginz/upstreams b/deploy/dockerephemeral/federation-v1/nginz/upstreams new file mode 100644 index 00000000000..cbf7b353fe1 --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/nginz/upstreams @@ -0,0 +1,38 @@ +upstream cargohold { + least_conn; + keepalive 32; + server cargohold-v1:8080 max_fails=3 weight=1; +} +upstream gundeck { + least_conn; + keepalive 32; + server gundeck-v1:8080 max_fails=3 weight=1; +} +upstream cannon { + least_conn; + keepalive 32; + server cannon-v1:8080 max_fails=3 weight=1; +} +upstream galley { + least_conn; + keepalive 32; + server galley-v1:8080 max_fails=3 weight=1; +} +upstream proxy { + least_conn; + keepalive 32; + server proxy-v1:8080 max_fails=3 weight=1; +} +upstream brig { + least_conn; + keepalive 32; + server brig-v1:8080 max_fails=3 weight=1; +} +upstream spar { + least_conn; + keepalive 32; + server spar-v1:8080 max_fails=3 weight=1; +} +upstream federator_external { + server federator-v1:8081 max_fails=3 weight=1; +} diff --git a/deploy/dockerephemeral/federation-v1/oauth-ed25519.jwk b/deploy/dockerephemeral/federation-v1/oauth-ed25519.jwk new file mode 100644 index 00000000000..c00a8270aa4 --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/oauth-ed25519.jwk @@ -0,0 +1 @@ +{"kty":"OKP","crv":"Ed25519","x":"mhP-NgFw3ifIXGZqJVB0kemt9L3BtD5P8q4Gah4Iklc","d":"R8-pV2-sPN7dykV8HFJ73S64F3kMHTNnJiSN8UdWk_o"} diff --git a/deploy/dockerephemeral/federation-v1/proxy.config b/deploy/dockerephemeral/federation-v1/proxy.config new file mode 100644 index 00000000000..d2225ca26c9 --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/proxy.config @@ -0,0 +1,8 @@ +secrets { + youtube = "my-youtube-secret" + googlemaps = "my-googlemaps-secret" + soundcloud = "my-soundcloud-secret" + giphy = "my-giphy-secret" + # Base64 encoded client ID and secret: `Bearer id:secret`: + spotify = "my-spotify-secret" +} diff --git a/deploy/dockerephemeral/federation-v1/proxy.yaml b/deploy/dockerephemeral/federation-v1/proxy.yaml new file mode 100644 index 00000000000..43a117e6a20 --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/proxy.yaml @@ -0,0 +1,20 @@ +# Example yaml-formatted configuration for proxy +# proxy can be started with a config file (e.g. ./dist/proxy -c proxy.yaml.example) + +host: 0.0.0.0 +port: 8080 + +# number of connections for the http pool +httpPoolSize: 1000 + +# maximum number of incoming connections +maxConns: 5000 + +# File containing upstream secrets. +secretsConfig: /etc/wire/proxy/conf/proxy.config + +disabledAPIVersions: [] + +# Logging settings +logLevel: Info +logNetStrings: false diff --git a/deploy/dockerephemeral/federation-v1/spar.yaml b/deploy/dockerephemeral/federation-v1/spar.yaml new file mode 100644 index 00000000000..e111292bc03 --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/spar.yaml @@ -0,0 +1,44 @@ +saml: + version: SAML2.0 + logLevel: Warn + + spHost: 0.0.0.0 + spPort: 8080 + # TODO: change these + spAppUri: http://localhost:8080/ + spSsoUri: http://localhost:8080/sso + + contacts: + - type: ContactBilling + company: evil corp. + givenName: Dr. + surname: Girlfriend + email: email:president@evil.corp + +brig: + host: brig-federation-v1 + port: 8080 + +galley: + host: galley-federation-v1 + port: 8080 + +cassandra: + endpoint: + host: demo_wire_cassandra + port: 9042 + keyspace: spar_test_federation_v1 + filterNodesByDatacentre: datacenter1 + +# Wire/AWS specific, optional +# discoUrl: "https://" + +disabledAPIVersions: [] + +maxttlAuthreq: 5 # seconds. don't set this too large, it is also the run time of one TTL test. +maxttlAuthresp: 7200 # seconds. do not set this to 1h or less, as that is what the mock idp wants. + +maxScimTokens: 2 # Token limit {#RefScimToken} +richInfoLimit: 5000 # should be in sync with Brig + +logNetStrings: False # log using netstrings encoding (see http://cr.yp.to/proto/netstrings.txt) diff --git a/deploy/dockerephemeral/federation-v1/turn-secret.txt b/deploy/dockerephemeral/federation-v1/turn-secret.txt new file mode 100644 index 00000000000..5e558cab2cc --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/turn-secret.txt @@ -0,0 +1 @@ +xMtZyTpu=Leb?YKCoq#BXQR:gG^UrE83dNWzFJ2VcD \ No newline at end of file diff --git a/deploy/dockerephemeral/federation-v1/twilio-credentials.yaml b/deploy/dockerephemeral/federation-v1/twilio-credentials.yaml new file mode 100644 index 00000000000..d64e0ec4f23 --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/twilio-credentials.yaml @@ -0,0 +1,2 @@ +sid: "dummy" +token: "dummy" diff --git a/deploy/dockerephemeral/federation-v1/zauth-privkeys.txt b/deploy/dockerephemeral/federation-v1/zauth-privkeys.txt new file mode 100644 index 00000000000..7b6d17ed984 --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/zauth-privkeys.txt @@ -0,0 +1,4 @@ +qjIAZtKrpXInwyqgM7JCZ3QeK9B4JGBYAv1_63YjTtgDylLfTTpdwvDYSy32is13biThD03QZAUOhBO042Odrw== +dNLsH_oIA6hJCyw-AwokLz3AukHNghlP3H-pW5Ao1Wy06OI2MGgBwRnvjgfI2l1mgCLPJQflUR-7DsYO0p6zoQ== +drShe2GnggBy-VAW1gdE6myf4UAFcN1ZdixCO8NRuYLv_TO-xNQzRj-8RfemJ4R6Oz-R5KTfP6Oj_Tj0qezDTw== +tZWlAKOCe5-vlQl0TbECvxeIptEBGRrnGSiej-olAFe-46gXpFkWTas2Ci84VUWyhWzRJj4rtBmyJkAm-TMvwQ== diff --git a/deploy/dockerephemeral/federation-v1/zauth-pubkeys.txt b/deploy/dockerephemeral/federation-v1/zauth-pubkeys.txt new file mode 100644 index 00000000000..661fcfc71ba --- /dev/null +++ b/deploy/dockerephemeral/federation-v1/zauth-pubkeys.txt @@ -0,0 +1,4 @@ +A8pS3006XcLw2Est9orNd24k4Q9N0GQFDoQTtONjna8= +tOjiNjBoAcEZ744HyNpdZoAizyUH5VEfuw7GDtKes6E= +7_0zvsTUM0Y_vEX3pieEejs_keSk3z-jo_049Knsw08= +vuOoF6RZFk2rNgovOFVFsoVs0SY-K7QZsiZAJvkzL8E= diff --git a/deploy/dockerephemeral/init_vhosts.sh b/deploy/dockerephemeral/init_vhosts.sh index 9323e6f5a43..49c2d470909 100755 --- a/deploy/dockerephemeral/init_vhosts.sh +++ b/deploy/dockerephemeral/init_vhosts.sh @@ -1,16 +1,23 @@ #!/usr/bin/env sh +set -eu + exec_until_ready() { until $1; do echo 'service not ready yet'; sleep 1; done } +create_vhost() { + exec_until_ready "curl -u $RABBITMQ_USERNAME:$RABBITMQ_PASSWORD -X PUT http://rabbitmq:15672/api/vhosts/$1" +} + echo 'Creating RabbitMQ resources' -exec_until_ready "curl -u $RABBITMQ_USERNAME:$RABBITMQ_PASSWORD -X PUT http://rabbitmq:15672/api/vhosts/backendA" -exec_until_ready "curl -u $RABBITMQ_USERNAME:$RABBITMQ_PASSWORD -X PUT http://rabbitmq:15672/api/vhosts/backendB" -exec_until_ready "curl -u $RABBITMQ_USERNAME:$RABBITMQ_PASSWORD -X PUT http://rabbitmq:15672/api/vhosts/d1.example.com" -exec_until_ready "curl -u $RABBITMQ_USERNAME:$RABBITMQ_PASSWORD -X PUT http://rabbitmq:15672/api/vhosts/d2.example.com" -exec_until_ready "curl -u $RABBITMQ_USERNAME:$RABBITMQ_PASSWORD -X PUT http://rabbitmq:15672/api/vhosts/d3.example.com" -exec_until_ready "curl -u $RABBITMQ_USERNAME:$RABBITMQ_PASSWORD -X PUT http://rabbitmq:15672/api/vhosts/federation-v0" +create_vhost backendA +create_vhost backendB +create_vhost d1.example.com +create_vhost d2.example.com +create_vhost d3.example.com +create_vhost federation-v0 +create_vhost federation-v1 echo 'RabbitMQ resources created successfully!' diff --git a/deploy/dockerephemeral/run.sh b/deploy/dockerephemeral/run.sh index 8d9a98cc8be..f6455dacf5c 100755 --- a/deploy/dockerephemeral/run.sh +++ b/deploy/dockerephemeral/run.sh @@ -1,17 +1,34 @@ #!/usr/bin/env bash -set -xe +set -e # run.sh should work no matter what is the current directory SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" DOCKER_FILE="$SCRIPT_DIR/docker-compose.yaml" +FED_VERSIONS=(0 1) + +opts=( "--file" "$DOCKER_FILE" ) +for v in "${FED_VERSIONS[@]}"; do + var="ENABLE_FEDERATION_V$v" + if [[ "${!var}" == 1 ]]; then + opts+=( "--file" "$SCRIPT_DIR/federation-v$v.yaml" ) + fi +done + +dc() { + docker-compose "${opts[@]}" "$@" +} cleanup () { - docker-compose --file "$DOCKER_FILE" --file "$SCRIPT_DIR/federation-v0.yaml" down + dc down } -docker-compose --file "$DOCKER_FILE" --file "$SCRIPT_DIR/federation-v0.yaml" up -d -trap cleanup EXIT -echo "All Services started successfully, press Ctrl+C to stop them" -# Wait for something to kill this -while true; do sleep 100000000; done +if [ -z "$1" ]; then + dc up -d + trap cleanup EXIT + echo "All Services started successfully, press Ctrl+C to stop them" + # Wait for something to kill this + sleep infinity +else + dc "$@" +fi diff --git a/hack/helm_vars/wire-server/values-domain1.yaml.gotmpl b/hack/helm_vars/wire-server/values-domain1.yaml.gotmpl new file mode 100644 index 00000000000..65bc78ca64a --- /dev/null +++ b/hack/helm_vars/wire-server/values-domain1.yaml.gotmpl @@ -0,0 +1,30 @@ +galley: + secrets: + mlsPrivateKeys: + removal: + ed25519: | + -----BEGIN PRIVATE KEY----- + MC4CAQAwBQYDK2VwBCIEIAocCDXsKIAjb65gOUn5vEF0RIKnVJkKR4ebQzuZ709c + -----END PRIVATE KEY----- + ecdsa_secp256r1_sha256: | + -----BEGIN PRIVATE KEY----- + MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg3qjgQ9U+/rTBObn9 + tXSVi2UtHksRDXmQ1VOszFZfjryhRANCAATNkLmZZLyORf5D3PUOxt+rkJTE5vuD + aCqZ7sE5NSN8InRRwuQ1kv0oblDVeQA89ZlHqyxx75JPK+/air7Z1n5I + -----END PRIVATE KEY----- + ecdsa_secp384r1_sha384: | + -----BEGIN PRIVATE KEY----- + MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBLwv3i5LDz9b++O0iw + QAit/Uq7L5PWPgKN99wCm8xkZnuyqWujXW4wvlVUVlZWgh2hZANiAAT0+RXKE31c + VxdYazaVopY50/nV9c18uRdqoENBvtxuD6oDtJtU6oCS/Htkd8JEArTQ9ZHqq144 + yRjuc3d2CqvJmEA/lzIBk9wnz+lghFhvB4TkSHvvLyEBc9DZvhb4EEQ= + -----END PRIVATE KEY----- + ecdsa_secp521r1_sha512: | + -----BEGIN PRIVATE KEY----- + MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIBiaEARm5BMaRct1xj + MlemUHijWGAoHtNMhSttSr4jo0WxMwfMnvnDQJSlO2Zs4Tzum2j5eO34EHu6MUrv + qquZYwyhgYkDgYYABAHuvCV/+gJitvAbDwgrBHZJ41oy8Lc+wPIM7Yp6s/vTzTsG + Klo7aMdkx6DUjv/56tVD9bZNulFAjwS8xoIyWg8NSAE1ofo8CBvN1XGZOWuMYjEh + zLrZADduEnOvayw5sEvm135WC0vWjPJaYwKZPdDIXUz9ILJPgNe3gEUvHsDEXvdX + lw== + -----END PRIVATE KEY----- diff --git a/hack/helm_vars/wire-server/values-domain2.yaml.gotmpl b/hack/helm_vars/wire-server/values-domain2.yaml.gotmpl new file mode 100644 index 00000000000..0c317f59726 --- /dev/null +++ b/hack/helm_vars/wire-server/values-domain2.yaml.gotmpl @@ -0,0 +1,30 @@ +galley: + secrets: + mlsPrivateKeys: + removal: + ed25519: | + -----BEGIN PRIVATE KEY----- + MC4CAQAwBQYDK2VwBCIEINsoqzdpVTv5+odHwwGO1I+Kp1+T24p7URvq50n79iCJ + -----END PRIVATE KEY----- + ecdsa_secp256r1_sha256: | + -----BEGIN PRIVATE KEY----- + MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgl2RK2WydNOp7ViAB + 5mhF4N0HdmJQ89f4YtxiCE252LehRANCAASJgocuA+eIaebS+M6t6ouQT3LzObg3 + XkWNvPZWE/4wsm6FAZ7ulLKU02AumSUx4u71d/1x9epAHJyc+RdACUQt + -----END PRIVATE KEY----- + ecdsa_secp384r1_sha384: | + -----BEGIN PRIVATE KEY----- + MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDD/lnMI4VPb7VchrKHj + hiE6ntS71rVDXps8UdnHenKQhBccRlqznBV6vP5QbcKVQcqhZANiAAR3yJC8eV/G + tC6ZDk5uMNaiqlmwVzH0mRNiJPGShVHfL3rBFq99sf3nOTs4v79PXajGwFJXppJC + /TQc7PMy2IbTor5tdWjcNBgSZiPn734IACuLpExqvsPuD6MlV2aHmXQ= + -----END PRIVATE KEY----- + ecdsa_secp521r1_sha512: | + -----BEGIN PRIVATE KEY----- + MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAcHs/PncuxyMlECqE + DeUHeqbd3aktlnenf8q0vemi4DSebEEp3ONCG7ZfFQFZyp5aZQWgZaOj1pwGDzth + FmmOFwShgYkDgYYABAF5QeG/mdn2MNuaHzDc+/6UbAfsb+ddighFuxqobl4731w2 + 2myXfvFGcseoKjymDe8kuv1a4eDmLzLkrUGNZhWIfQD7CP0j0JmBNzVnYAKlRcOd + SU5XbMx1q7oyaiQ51B47IObxT8sVKZzbnE3qZa060cAglu4G0OS3OlJzVOinBAfA + 5w== + -----END PRIVATE KEY----- diff --git a/hack/helm_vars/wire-server/values.yaml.gotmpl b/hack/helm_vars/wire-server/values.yaml.gotmpl index 9cd2b618078..6b508ae6310 100644 --- a/hack/helm_vars/wire-server/values.yaml.gotmpl +++ b/hack/helm_vars/wire-server/values.yaml.gotmpl @@ -427,6 +427,7 @@ federator: config: optSettings: useSystemCAStore: false + logLevel: Debug tests: {{- if .Values.uploadXml }} config: diff --git a/hack/helmfile.yaml b/hack/helmfile.yaml index 78634f17b25..2523074e917 100644 --- a/hack/helmfile.yaml +++ b/hack/helmfile.yaml @@ -155,6 +155,7 @@ releases: chart: '../.local/charts/wire-server' values: - './helm_vars/wire-server/values.yaml.gotmpl' + - './helm_vars/wire-server/values-domain1.yaml.gotmpl' set: - name: brig.config.optSettings.setFederationDomain value: {{ .Values.federationDomain1 }} @@ -170,6 +171,7 @@ releases: chart: '../.local/charts/wire-server' values: - './helm_vars/wire-server/values.yaml.gotmpl' + - './helm_vars/wire-server/values-domain2.yaml.gotmpl' set: - name: brig.config.optSettings.setFederationDomain value: {{ .Values.federationDomain2 }} diff --git a/integration/Setup.hs b/integration/Setup.hs index a9c6110e00b..71b172a7e29 100644 --- a/integration/Setup.hs +++ b/integration/Setup.hs @@ -191,10 +191,11 @@ testHooks hooks = [ "module RunAllTests where", "import Testlib.PTest", "import Prelude", + "import Control.Monad.Trans.Writer", unlines (map ("import qualified " <>) modules), - "allTests :: [Test]", - "allTests =", - " " <> intercalate " <>\n " (map (\(m, n, s, f) -> "mkTests " <> unwords [show m, show n, show s, show f, m <> "." <> n]) tests) + "mkAllTests :: IO [Test]", + "mkAllTests = execWriterT $ do", + unlines (map (\(m, n, s, f) -> " yieldTests " <> unwords [show m, show n, show s, show f, m <> "." <> n]) tests) ] ) pure () diff --git a/integration/integration.cabal b/integration/integration.cabal index 5abd1ed636b..e3e7baf7251 100644 --- a/integration/integration.cabal +++ b/integration/integration.cabal @@ -164,6 +164,7 @@ library Testlib.Run Testlib.RunServices Testlib.Types + Testlib.VersionedFed Testlib.XML build-depends: diff --git a/integration/test/MLS/Util.hs b/integration/test/MLS/Util.hs index 2a59f980579..6e2ac34338a 100644 --- a/integration/test/MLS/Util.hs +++ b/integration/test/MLS/Util.hs @@ -9,7 +9,6 @@ import Control.Monad import Control.Monad.Catch import Control.Monad.Codensity import Control.Monad.Cont -import Control.Monad.Reader import Control.Monad.Trans.Maybe import qualified Data.Aeson as A import qualified Data.Aeson as Aeson @@ -221,7 +220,14 @@ createSubConv cid subId = do resetGroup cid sub void $ createPendingProposalCommit cid >>= sendAndConsumeCommitBundle -resetGroup :: (MakesValue conv) => ClientIdentity -> conv -> App () +createOne2OneSubConv :: (HasCallStack, MakesValue keys) => ClientIdentity -> String -> keys -> App () +createOne2OneSubConv cid subId keys = do + mls <- getMLSState + sub <- getSubConversation cid mls.convId subId >>= getJSON 200 + resetOne2OneGroupGeneric cid sub keys + void $ createPendingProposalCommit cid >>= sendAndConsumeCommitBundle + +resetGroup :: (HasCallStack, MakesValue conv) => ClientIdentity -> conv -> App () resetGroup cid conv = do convId <- objSubConvObject conv groupId <- conv %. "group_id" & asString @@ -233,29 +239,46 @@ resetGroup cid conv = do epoch = 0, newMembers = mempty } - resetClientGroup cid groupId + keys <- getMLSPublicKeys cid.qualifiedUserId >>= getJSON 200 + resetClientGroup cid groupId keys + +resetOne2OneGroup :: (HasCallStack, MakesValue one2OneConv) => ClientIdentity -> one2OneConv -> App () +resetOne2OneGroup cid one2OneConv = + resetOne2OneGroupGeneric cid (one2OneConv %. "conversation") (one2OneConv %. "public_keys") + +-- | Useful when keys are to be taken from main conv and the conv here is the subconv +resetOne2OneGroupGeneric :: (HasCallStack, MakesValue conv, MakesValue keys) => ClientIdentity -> conv -> keys -> App () +resetOne2OneGroupGeneric cid conv keys = do + convId <- objSubConvObject conv + groupId <- conv %. "group_id" & asString + modifyMLSState $ \s -> + s + { groupId = Just groupId, + convId = Just convId, + members = Set.singleton cid, + epoch = 0, + newMembers = mempty + } + resetClientGroup cid groupId keys -resetClientGroup :: ClientIdentity -> String -> App () -resetClientGroup cid gid = do +resetClientGroup :: (HasCallStack, MakesValue keys) => ClientIdentity -> String -> keys -> App () +resetClientGroup cid gid keys = do mls <- getMLSState - removalKeyPaths <- asks (.removalKeyPaths) - removalKeyPath <- - assertOne $ - Map.lookup (csSignatureScheme mls.ciphersuite) removalKeyPaths + removalKey <- asByteString $ keys %. ("removal." <> csSignatureScheme mls.ciphersuite) void $ mlscli cid [ "group", "create", "--removal-key", - removalKeyPath, + "-", "--group-out", "", "--ciphersuite", mls.ciphersuite.code, gid ] - Nothing + (Just removalKey) keyPackageFile :: (HasCallStack) => ClientIdentity -> String -> App FilePath keyPackageFile cid ref = do diff --git a/integration/test/SetupHelpers.hs b/integration/test/SetupHelpers.hs index 7a9eab93257..e12f77ae592 100644 --- a/integration/test/SetupHelpers.hs +++ b/integration/test/SetupHelpers.hs @@ -161,13 +161,22 @@ addUserToTeam u = do -- | Create a user on the given domain, such that the 1-1 conversation with -- 'other' resides on 'convDomain'. This connects the two users as a side-effect. -createMLSOne2OnePartner :: MakesValue user => Domain -> user -> Domain -> App Value +createMLSOne2OnePartner :: + (MakesValue user, MakesValue domain, MakesValue convDomain, HasCallStack) => + domain -> + user -> + convDomain -> + App Value createMLSOne2OnePartner domain other convDomain = loop where loop = do u <- randomUser domain def connectTwoUsers u other - conv <- getMLSOne2OneConversation other u >>= getJSON 200 + apiVersion <- getAPIVersionFor domain + conv <- + if apiVersion < 6 + then getMLSOne2OneConversation other u >>= getJSON 200 + else getMLSOne2OneConversation other u >>= getJSON 200 >>= (%. "conversation") desiredConvDomain <- make convDomain & asString actualConvDomain <- conv %. "qualified_id.domain" & asString @@ -206,14 +215,15 @@ withFederatingBackendsAllowDynamic k = do -- | Create two users on different domains such that the one-to-one -- conversation, once finalised, will be hosted on the backend given by the --- input domain. -createOne2OneConversation :: HasCallStack => Domain -> App (Value, Value, Value) -createOne2OneConversation owningDomain = do +-- first domain. +createOne2OneConversation :: + (HasCallStack, MakesValue domain1, MakesValue domain2) => + domain1 -> + domain2 -> + App (Value, Value, Value) +createOne2OneConversation owningDomain otherDomain = do owningUser <- randomUser owningDomain def domainName <- owningUser %. "qualified_id.domain" - let otherDomain = case owningDomain of - OwnDomain -> OtherDomain - OtherDomain -> OwnDomain let go = do otherUser <- randomUser otherDomain def otherUserId <- otherUser %. "qualified_id" diff --git a/integration/test/Test/Connection.hs b/integration/test/Test/Connection.hs index 0852552c1d4..2c6b9bacb20 100644 --- a/integration/test/Test/Connection.hs +++ b/integration/test/Test/Connection.hs @@ -21,11 +21,15 @@ import API.BrigInternal import API.Galley import SetupHelpers import Testlib.Prelude +import Testlib.VersionedFed import UnliftIO.Async (forConcurrently_) -testConnectWithRemoteUser :: HasCallStack => Domain -> App () +testConnectWithRemoteUser :: (HasCallStack) => OneOf Domain AnyFedDomain -> App () testConnectWithRemoteUser owningDomain = do - (alice, bob, one2oneId) <- createOne2OneConversation owningDomain + let otherDomain = case owningDomain of + OneOfA OwnDomain -> OtherDomain + _ -> OwnDomain + (alice, bob, one2oneId) <- createOne2OneConversation owningDomain otherDomain aliceId <- alice %. "qualified_id" getConversation alice one2oneId `bindResponse` \resp -> do resp.status `shouldMatchInt` 200 @@ -78,20 +82,21 @@ testRemoteUserGetsDeleted = do pure charlie - charlieUnconnected <- do - randomUser OtherDomain def + charlieUnconnected <- randomUser OtherDomain def - forConcurrently_ [charliePending, charlieConnected, charlieBlocked, charlieUnconnected] \charlie -> do - deleteUser charlie + forConcurrently_ + [charliePending, charlieConnected, charlieBlocked, charlieUnconnected] + \charlie -> do + deleteUser charlie - -- charlie is on their local backend, so asking should be instant - getConnection charlie alice `bindResponse` \resp -> - resp.status `shouldMatchInt` 404 + -- charlie is on their local backend, so asking should be instant + getConnection charlie alice `bindResponse` \resp -> + resp.status `shouldMatchInt` 404 - -- for alice, charlie is on the remote backend, so the status change - -- may not be instant - getConnection alice charlie `waitForResponse` \resp -> - resp.status `shouldMatchInt` 404 + -- for alice, charlie is on the remote backend, so the status change + -- may not be instant + getConnection alice charlie `waitForResponse` \resp -> + resp.status `shouldMatchInt` 404 testInternalGetConStatusesAll :: HasCallStack => App () testInternalGetConStatusesAll = @@ -148,9 +153,11 @@ assertConnectionStatus userFrom userTo connStatus = resp.status `shouldMatchInt` 200 resp.json %. "status" `shouldMatch` connStatus -testConnectFromIgnored :: HasCallStack => App () -testConnectFromIgnored = do - [alice, bob] <- forM [OwnDomain, OtherDomain] $ flip randomUser def +testConnectFromIgnored :: (HasCallStack) => StaticDomain -> App () +testConnectFromIgnored domain = do + alice <- randomUser OwnDomain def + bob <- randomUser domain def + void $ postConnection bob alice >>= getBody 201 -- set up an initial "ignored" state on Alice's side assertConnectionStatus alice bob "pending" @@ -167,9 +174,11 @@ testConnectFromIgnored = do resp.status `shouldMatchInt` 200 resp.json %. "status" `shouldMatch` "accepted" -testSentFromIgnored :: HasCallStack => App () -testSentFromIgnored = do - [alice, bob] <- forM [OwnDomain, OtherDomain] $ flip randomUser def +testSentFromIgnored :: (HasCallStack) => StaticDomain -> App () +testSentFromIgnored domain = do + alice <- randomUser OwnDomain def + bob <- randomUser domain def + -- set up an initial "ignored" state void $ postConnection bob alice >>= getBody 201 void $ putConnection alice bob "ignored" >>= getBody 200 @@ -184,9 +193,9 @@ testSentFromIgnored = do void $ putConnection alice bob "accepted" >>= getBody 200 assertConnectionStatus alice bob "sent" -testConnectFromBlocked :: HasCallStack => App () -testConnectFromBlocked = do - (alice, bob, one2oneId) <- createOne2OneConversation OwnDomain +testConnectFromBlocked :: (HasCallStack) => StaticDomain -> App () +testConnectFromBlocked domain = do + (alice, bob, one2oneId) <- createOne2OneConversation OwnDomain domain bobId <- bob %. "qualified_id" -- set up an initial "blocked" state @@ -210,9 +219,11 @@ testConnectFromBlocked = do qIds <- for others (%. "qualified_id") qIds `shouldMatchSet` [bobId] -testSentFromBlocked :: HasCallStack => App () -testSentFromBlocked = do - [alice, bob] <- forM [OwnDomain, OtherDomain] $ flip randomUser def +testSentFromBlocked :: (HasCallStack) => StaticDomain -> App () +testSentFromBlocked domain = do + alice <- randomUser OwnDomain def + bob <- randomUser domain def + -- set up an initial "blocked" state void $ postConnection bob alice >>= getBody 201 void $ putConnection alice bob "blocked" >>= getBody 200 @@ -227,9 +238,10 @@ testSentFromBlocked = do void $ putConnection alice bob "accepted" >>= getBody 200 assertConnectionStatus alice bob "sent" -testCancel :: HasCallStack => App () -testCancel = do - [alice, bob] <- forM [OwnDomain, OtherDomain] $ flip randomUser def +testCancel :: (HasCallStack) => StaticDomain -> App () +testCancel domain = do + alice <- randomUser OwnDomain def + bob <- randomUser domain def void $ postConnection alice bob >>= getBody 201 assertConnectionStatus alice bob "sent" @@ -237,16 +249,16 @@ testCancel = do void $ putConnection alice bob "cancelled" >>= getBody 200 assertConnectionStatus alice bob "cancelled" -testConnectionLimits :: HasCallStack => App () -testConnectionLimits = do +testConnectionLimits :: (HasCallStack) => StaticDomain -> App () +testConnectionLimits domain = do let connectionLimit = 16 alice <- randomUser OwnDomain def [charlie1, charlie2, charlie3, charlie4] <- replicateM 4 do - randomUser OtherDomain def + randomUser domain def -- connect to connectionLimit - 1 many users (charlie5 : _) <- replicateM (connectionLimit - 1) do - charlie <- randomUser OtherDomain def + charlie <- randomUser domain def postConnection alice charlie `bindResponse` \resp -> resp.status `shouldMatchInt` 201 pure charlie diff --git a/integration/test/Test/Conversation.hs b/integration/test/Test/Conversation.hs index 9426f1a41c1..20fd75d8e5f 100644 --- a/integration/test/Test/Conversation.hs +++ b/integration/test/Test/Conversation.hs @@ -35,8 +35,9 @@ import SetupHelpers hiding (deleteUser) import Testlib.One2One (generateRemoteAndConvIdWithDomain) import Testlib.Prelude import Testlib.ResourcePool +import Testlib.VersionedFed -testDynamicBackendsFullyConnectedWhenAllowAll :: HasCallStack => App () +testDynamicBackendsFullyConnectedWhenAllowAll :: (HasCallStack) => App () testDynamicBackendsFullyConnectedWhenAllowAll = do -- The default setting is 'allowAll' startDynamicBackends [def, def, def] $ \dynDomains -> do @@ -56,7 +57,7 @@ testDynamicBackendsFullyConnectedWhenAllowAll = do resp.status `shouldMatchInt` 200 resp.json %. "status" `shouldMatch` "fully-connected" -testDynamicBackendsNotFederating :: HasCallStack => App () +testDynamicBackendsNotFederating :: (HasCallStack) => App () testDynamicBackendsNotFederating = do let overrides = def @@ -72,7 +73,7 @@ testDynamicBackendsNotFederating = do resp.status `shouldMatchInt` 533 resp.json %. "unreachable_backends" `shouldMatchSet` [domainB, domainC] -testDynamicBackendsFullyConnectedWhenAllowDynamic :: HasCallStack => App () +testDynamicBackendsFullyConnectedWhenAllowDynamic :: (HasCallStack) => App () testDynamicBackendsFullyConnectedWhenAllowDynamic = do withFederatingBackendsAllowDynamic $ \(domainA, domainB, domainC) -> do -- Allowing 'full_search' or any type of search is how we enable federation @@ -96,7 +97,7 @@ testDynamicBackendsFullyConnectedWhenAllowDynamic = do retryT $ assertConnected uidB domainA domainC retryT $ assertConnected uidC domainA domainB -testDynamicBackendsNotFullyConnected :: HasCallStack => App () +testDynamicBackendsNotFullyConnected :: (HasCallStack) => App () testDynamicBackendsNotFullyConnected = do withFederatingBackendsAllowDynamic $ \(domainA, domainB, domainC) -> do -- A is connected to B and C, but B and C are not connected to each other @@ -113,10 +114,10 @@ testDynamicBackendsNotFullyConnected = do resp.json %. "status" `shouldMatch` "non-fully-connected" resp.json %. "not_connected" `shouldMatchSet` [domainB, domainC] -testFederationStatus :: HasCallStack => App () -testFederationStatus = do +testFederationStatus :: (HasCallStack) => StaticDomain -> App () +testFederationStatus domain = do uid <- randomUser OwnDomain def {BrigI.team = True} - federatingRemoteDomain <- asString OtherDomain + federatingRemoteDomain <- asString domain let invalidDomain = "c.example.com" -- Does not have any srv records bindResponse (getFederationStatus uid []) @@ -136,7 +137,7 @@ testFederationStatus = do resp.status `shouldMatchInt` 200 resp.json %. "status" `shouldMatch` "fully-connected" -testCreateConversationFullyConnected :: HasCallStack => App () +testCreateConversationFullyConnected :: (HasCallStack) => App () testCreateConversationFullyConnected = do startDynamicBackends [def, def, def] $ \[domainA, domainB, domainC] -> do [u1, u2, u3] <- createUsers [domainA, domainB, domainC] @@ -145,7 +146,7 @@ testCreateConversationFullyConnected = do bindResponse (postConversation u1 (defProteus {qualifiedUsers = [u2, u3]})) $ \resp -> do resp.status `shouldMatchInt` 201 -testCreateConversationNonFullyConnected :: HasCallStack => App () +testCreateConversationNonFullyConnected :: (HasCallStack) => App () testCreateConversationNonFullyConnected = do withFederatingBackendsAllowDynamic $ \(domainA, domainB, domainC) -> do -- A is connected to B and C, but B and C are not connected to each other @@ -165,7 +166,7 @@ testCreateConversationNonFullyConnected = do resp.status `shouldMatchInt` 409 resp.json %. "non_federating_backends" `shouldMatchSet` [domainB, domainC] -testAddMembersFullyConnectedProteus :: HasCallStack => App () +testAddMembersFullyConnectedProteus :: (HasCallStack) => App () testAddMembersFullyConnectedProteus = do startDynamicBackends [def, def, def] $ \[domainA, domainB, domainC] -> do [u1, u2, u3] <- createUsers [domainA, domainB, domainC] @@ -181,7 +182,7 @@ testAddMembersFullyConnectedProteus = do addedUsers <- forM users (%. "qualified_id") addedUsers `shouldMatchSet` members -testAddMembersNonFullyConnectedProteus :: HasCallStack => App () +testAddMembersNonFullyConnectedProteus :: (HasCallStack) => App () testAddMembersNonFullyConnectedProteus = do withFederatingBackendsAllowDynamic $ \(domainA, domainB, domainC) -> do void $ BrigI.createFedConn domainA (BrigI.FedConn domainB "full_search" Nothing) @@ -205,7 +206,7 @@ testAddMembersNonFullyConnectedProteus = do resp.status `shouldMatchInt` 409 resp.json %. "non_federating_backends" `shouldMatchSet` [domainB, domainC] -testAddMember :: HasCallStack => App () +testAddMember :: (HasCallStack) => App () testAddMember = do alice <- randomUser OwnDomain def aliceId <- alice %. "qualified_id" @@ -242,7 +243,7 @@ testAddMember = do mem %. "qualified_id" `shouldMatch` aliceId mem %. "conversation_role" `shouldMatch` "wire_admin" -testAddMemberV1 :: HasCallStack => Domain -> App () +testAddMemberV1 :: (HasCallStack) => Domain -> App () testAddMemberV1 domain = do [alice, bob] <- createAndConnectUsers [OwnDomain, domain] conv <- postConversation alice defProteus >>= getJSON 201 @@ -261,12 +262,12 @@ testAddMemberV1 domain = do users <- resp.json %. "data.users" >>= asList traverse (%. "qualified_id") users `shouldMatchSet` [bobId] -testConvWithUnreachableRemoteUsers :: HasCallStack => App () -testConvWithUnreachableRemoteUsers = do +testConvWithUnreachableRemoteUsers :: (HasCallStack) => StaticDomain -> App () +testConvWithUnreachableRemoteUsers domain = do ([alice, alex, bob, charlie, dylan], domains) <- startDynamicBackends [def, def] $ \domains -> do own <- make OwnDomain & asString - other <- make OtherDomain & asString + other <- make domain & asString users@(alice : others) <- createUsers $ [own, own, other] <> domains forM_ others $ connectTwoUsers alice pure (users, domains) @@ -280,11 +281,11 @@ testConvWithUnreachableRemoteUsers = do regConvs <- filterM (\c -> (==) <$> (c %. "type" & asInt) <*> pure 0) convs regConvs `shouldMatch` ([] :: [Value]) -testAddUserWithUnreachableRemoteUsers :: HasCallStack => App () -testAddUserWithUnreachableRemoteUsers = do +testAddUserWithUnreachableRemoteUsers :: (HasCallStack) => StaticDomain -> App () +testAddUserWithUnreachableRemoteUsers domain = do resourcePool <- asks resourcePool own <- make OwnDomain & asString - other <- make OtherDomain & asString + other <- make domain & asString runCodensity (acquireResources 1 resourcePool) $ \[cDom] -> do ([alex, bobId, bradId, chrisId], conv) <- runCodensity (startDynamicBackend cDom mempty) $ \_ -> do [alice, alex, bob, brad, charlie, chris] <- @@ -302,7 +303,7 @@ testAddUserWithUnreachableRemoteUsers = do runCodensity (startDynamicBackend cDom mempty) $ \_ -> void $ addMembers alex conv def {users = [bobId]} >>= getBody 200 - -- even though backend C is unreachable, we know B/OtherDomain and C + -- even though backend C is unreachable, we know B/domain and C -- federate because Bob joined when C was reachable, hence it is OK to add -- brad from B to the conversation. void $ addMembers alex conv def {users = [bradId]} >>= getBody 200 @@ -312,13 +313,13 @@ testAddUserWithUnreachableRemoteUsers = do resp.status `shouldMatchInt` 533 resp.jsonBody %. "unreachable_backends" `shouldMatchSet` [cDom.berDomain] -testAddUnreachableUserFromFederatingBackend :: HasCallStack => App () -testAddUnreachableUserFromFederatingBackend = do +testAddUnreachableUserFromFederatingBackend :: (HasCallStack) => StaticDomain -> App () +testAddUnreachableUserFromFederatingBackend domain = do resourcePool <- asks resourcePool runCodensity (acquireResources 1 resourcePool) $ \[cDom] -> do (alice, chadId, conv) <- runCodensity (startDynamicBackend cDom mempty) $ \_ -> do ownDomain <- make OwnDomain & asString - otherDomain <- make OtherDomain & asString + otherDomain <- make domain & asString [alice, bob, charlie, chad] <- createAndConnectUsers [ownDomain, otherDomain, cDom.berDomain, cDom.berDomain] @@ -335,7 +336,7 @@ testAddUnreachableUserFromFederatingBackend = do resp.status `shouldMatchInt` 533 resp.jsonBody %. "unreachable_backends" `shouldMatchSet` [cDom.berDomain] -testAddUnreachable :: HasCallStack => App () +testAddUnreachable :: (HasCallStack) => App () testAddUnreachable = do ([alex, charlie], [charlieDomain, dylanDomain], conv) <- startDynamicBackends [def, def] $ \domains -> do @@ -355,11 +356,11 @@ testAddUnreachable = do -- need to be reachable so we can check that the graph for those domains is fully connected. resp.json %. "unreachable_backends" `shouldMatchSet` [charlieDomain, dylanDomain] -testGetOneOnOneConvInStatusSentFromRemote :: App () -testGetOneOnOneConvInStatusSentFromRemote = do +testGetOneOnOneConvInStatusSentFromRemote :: (HasCallStack) => StaticDomain -> App () +testGetOneOnOneConvInStatusSentFromRemote domain = do d1User <- randomUser OwnDomain def let shouldBeLocal = True - (d2Usr, d2ConvId) <- generateRemoteAndConvIdWithDomain OtherDomain (not shouldBeLocal) d1User + (d2Usr, d2ConvId) <- generateRemoteAndConvIdWithDomain domain (not shouldBeLocal) d1User bindResponse (postConnection d1User d2Usr) $ \r -> do r.status `shouldMatchInt` 201 r.json %. "status" `shouldMatch` "sent" @@ -373,8 +374,8 @@ testGetOneOnOneConvInStatusSentFromRemote = do resp <- getConversation d1User d2ConvId resp.status `shouldMatchInt` 200 -testAddingUserNonFullyConnectedFederation :: HasCallStack => App () -testAddingUserNonFullyConnectedFederation = do +testAddingUserNonFullyConnectedFederation :: (HasCallStack) => StaticDomain -> App () +testAddingUserNonFullyConnectedFederation domain = do let overrides = def { brigCfg = @@ -382,7 +383,7 @@ testAddingUserNonFullyConnectedFederation = do } startDynamicBackends [overrides] $ \[dynBackend] -> do own <- asString OwnDomain - other <- asString OtherDomain + other <- asString domain -- Ensure that dynamic backend only federates with own domain, but not other -- domain. @@ -404,7 +405,7 @@ testAddingUserNonFullyConnectedFederation = do resp.status `shouldMatchInt` 409 resp.json %. "non_federating_backends" `shouldMatchSet` [other, dynBackend] -testMultiIngressGuestLinks :: HasCallStack => App () +testMultiIngressGuestLinks :: (HasCallStack) => App () testMultiIngressGuestLinks = do do configuredURI <- readServiceConfig Galley & (%. "settings.conversationCodeURI") & asText @@ -470,7 +471,7 @@ testMultiIngressGuestLinks = do res <- getJSON 403 resp res %. "label" `shouldMatch` "access-denied" -testAddUserWhenOtherBackendOffline :: HasCallStack => App () +testAddUserWhenOtherBackendOffline :: (HasCallStack) => App () testAddUserWhenOtherBackendOffline = do ([alice, alex], conv) <- startDynamicBackends [def] $ \domains -> do @@ -484,10 +485,12 @@ testAddUserWhenOtherBackendOffline = do bindResponse (addMembers alice conv def {users = [alex]}) $ \resp -> do resp.status `shouldMatchInt` 200 -testSynchroniseUserRemovalNotification :: HasCallStack => App () -testSynchroniseUserRemovalNotification = do +testSynchroniseUserRemovalNotification :: (HasCallStack) => StaticDomain -> App () +testSynchroniseUserRemovalNotification domain = do resourcePool <- asks resourcePool - [alice, bob] <- createAndConnectUsers [OwnDomain, OtherDomain] + ownDomain <- make OwnDomain + otherDomain <- make domain + [alice, bob] <- createAndConnectUsers [ownDomain, otherDomain] runCodensity (acquireResources 1 resourcePool) $ \[dynBackend] -> do (conv, charlie, client) <- runCodensity (startDynamicBackend dynBackend mempty) $ \_ -> do @@ -511,7 +514,7 @@ testSynchroniseUserRemovalNotification = do leaveNotif <- awaitNotification charlie client noValue isConvLeaveNotif leaveNotif %. "payload.0.qualified_conversation" `shouldMatch` objQidObject conv -testConvRenaming :: HasCallStack => App () +testConvRenaming :: (HasCallStack) => App () testConvRenaming = do [alice, bob] <- createAndConnectUsers [OwnDomain, OtherDomain] conv <- @@ -525,7 +528,7 @@ testConvRenaming = do nameNotif %. "payload.0.data.name" `shouldMatch` newConvName nameNotif %. "payload.0.qualified_conversation" `shouldMatch` objQidObject conv -testReceiptModeWithRemotesOk :: HasCallStack => App () +testReceiptModeWithRemotesOk :: (HasCallStack) => App () testReceiptModeWithRemotesOk = do [alice, bob] <- createAndConnectUsers [OwnDomain, OtherDomain] conv <- @@ -539,7 +542,7 @@ testReceiptModeWithRemotesOk = do notif %. "payload.0.qualified_from" `shouldMatch` objQidObject alice notif %. "payload.0.data.receipt_mode" `shouldMatchInt` 43 -testReceiptModeWithRemotesUnreachable :: HasCallStack => App () +testReceiptModeWithRemotesUnreachable :: (HasCallStack) => App () testReceiptModeWithRemotesUnreachable = do ownDomain <- asString OwnDomain alice <- randomUser ownDomain def @@ -555,7 +558,7 @@ testReceiptModeWithRemotesUnreachable = do notif %. "payload.0.qualified_from" `shouldMatch` objQidObject alice notif %. "payload.0.data.receipt_mode" `shouldMatchInt` 43 -testDeleteLocalMember :: HasCallStack => App () +testDeleteLocalMember :: (HasCallStack) => App () testDeleteLocalMember = do [alice, alex, bob] <- createUsers [OwnDomain, OwnDomain, OtherDomain] connectTwoUsers alice alex @@ -574,7 +577,7 @@ testDeleteLocalMember = do r.status `shouldMatchInt` 204 r.jsonBody `shouldMatch` (Nothing @Aeson.Value) -testDeleteRemoteMember :: HasCallStack => App () +testDeleteRemoteMember :: (HasCallStack) => App () testDeleteRemoteMember = do [alice, alex, bob] <- createUsers [OwnDomain, OwnDomain, OtherDomain] connectTwoUsers alice alex @@ -593,7 +596,7 @@ testDeleteRemoteMember = do r.status `shouldMatchInt` 204 r.jsonBody `shouldMatch` (Nothing @Aeson.Value) -testDeleteRemoteMemberRemoteUnreachable :: HasCallStack => App () +testDeleteRemoteMemberRemoteUnreachable :: (HasCallStack) => App () testDeleteRemoteMemberRemoteUnreachable = do [alice, bob, bart] <- createUsers [OwnDomain, OtherDomain, OtherDomain] conv <- startDynamicBackends [mempty] $ \[dynBackend] -> do @@ -617,7 +620,7 @@ testDeleteRemoteMemberRemoteUnreachable = do r.status `shouldMatchInt` 204 r.jsonBody `shouldMatch` (Nothing @Aeson.Value) -testDeleteTeamConversationWithRemoteMembers :: HasCallStack => App () +testDeleteTeamConversationWithRemoteMembers :: (HasCallStack) => App () testDeleteTeamConversationWithRemoteMembers = do (alice, team, _) <- createTeam OwnDomain 1 conv <- postConversation alice (defProteus {team = Just team}) >>= getJSON 201 @@ -633,7 +636,7 @@ testDeleteTeamConversationWithRemoteMembers = do notif %. "payload.0.qualified_conversation" `shouldMatch` objQidObject conv notif %. "payload.0.qualified_from" `shouldMatch` objQidObject alice -testDeleteTeamConversationWithUnreachableRemoteMembers :: HasCallStack => App () +testDeleteTeamConversationWithUnreachableRemoteMembers :: (HasCallStack) => App () testDeleteTeamConversationWithUnreachableRemoteMembers = do resourcePool <- asks resourcePool (alice, team, _) <- createTeam OwnDomain 1 @@ -660,7 +663,7 @@ testDeleteTeamConversationWithUnreachableRemoteMembers = do notif <- awaitNotification bob bobClient noValue isConvDeleteNotif assertNotification notif -testDeleteTeamMemberLimitedEventFanout :: HasCallStack => App () +testDeleteTeamMemberLimitedEventFanout :: (HasCallStack) => App () testDeleteTeamMemberLimitedEventFanout = do -- Alex will get removed from the team (alice, team, [alex, alison]) <- createTeam OwnDomain 3 @@ -683,7 +686,8 @@ testDeleteTeamMemberLimitedEventFanout = do -- from the team assertSuccess =<< setTeamFeatureStatus OwnDomain team "limitedEventFanout" "enabled" - withWebSockets [alice, amy, bob, alison, ana] $ + withWebSockets + [alice, amy, bob, alison, ana] \[wsAlice, wsAmy, wsBob, wsAlison, wsAna] -> do void $ deleteTeamMember team alice alex >>= getBody 202 @@ -719,7 +723,7 @@ testDeleteTeamMemberLimitedEventFanout = do -- is disabled by default. The counterpart test -- 'testDeleteTeamMemberLimitedEventFanout' enables the flag and tests the -- limited fanout. -testDeleteTeamMemberFullEventFanout :: HasCallStack => App () +testDeleteTeamMemberFullEventFanout :: (HasCallStack) => App () testDeleteTeamMemberFullEventFanout = do (alice, team, [alex, alison]) <- createTeam OwnDomain 3 [amy, bob] <- for [OwnDomain, OtherDomain] $ flip randomUser def @@ -749,7 +753,7 @@ testDeleteTeamMemberFullEventFanout = do memIds `shouldMatchSet` [aliceId, alisonId, amyId] assertConvUserDeletedNotif wsBob alexId -testLeaveConversationSuccess :: HasCallStack => App () +testLeaveConversationSuccess :: (HasCallStack) => App () testLeaveConversationSuccess = do [alice, bob, chad, dee] <- createUsers [OwnDomain, OwnDomain, OtherDomain, OtherDomain] [aClient, bClient] <- forM [alice, bob] $ \user -> @@ -771,7 +775,7 @@ testLeaveConversationSuccess = do assertLeaveNotification chad conv bob bClient chad assertLeaveNotification chad conv eve eClient chad -testOnUserDeletedConversations :: HasCallStack => App () +testOnUserDeletedConversations :: (HasCallStack) => App () testOnUserDeletedConversations = do startDynamicBackends [def] $ \[dynDomain] -> do [ownDomain, otherDomain] <- forM [OwnDomain, OtherDomain] asString @@ -803,7 +807,7 @@ testOnUserDeletedConversations = do expectedIds <- for [alex, bart, chad] (%. "qualified_id") memIds `shouldMatchSet` expectedIds -testUpdateConversationByRemoteAdmin :: HasCallStack => App () +testUpdateConversationByRemoteAdmin :: (HasCallStack) => App () testUpdateConversationByRemoteAdmin = do [alice, bob, charlie] <- createUsers [OwnDomain, OtherDomain, OtherDomain] connectTwoUsers alice bob @@ -816,14 +820,14 @@ testUpdateConversationByRemoteAdmin = do void $ updateReceiptMode bob conv (41 :: Int) >>= getBody 200 for_ wss $ \ws -> awaitMatch isReceiptModeUpdateNotif ws -testGuestCreatesConversation :: HasCallStack => App () +testGuestCreatesConversation :: (HasCallStack) => App () testGuestCreatesConversation = do alice <- randomUser OwnDomain def {BrigI.activate = False} bindResponse (postConversation alice defProteus) $ \resp -> do resp.status `shouldMatchInt` 403 resp.json %. "label" `shouldMatch` "operation-denied" -testGuestLinksSuccess :: HasCallStack => App () +testGuestLinksSuccess :: (HasCallStack) => App () testGuestLinksSuccess = do (user, _, tm : _) <- createTeam OwnDomain 2 conv <- postConversation user (allowGuests defProteus) >>= getJSON 201 @@ -836,7 +840,7 @@ testGuestLinksSuccess = do resp.status `shouldMatchInt` 200 resp.json %. "id" `shouldMatch` objId conv -testGuestLinksExpired :: HasCallStack => App () +testGuestLinksExpired :: (HasCallStack) => App () testGuestLinksExpired = do withModifiedBackend def {galleyCfg = setField "settings.guestLinkTTLSeconds" (1 :: Int)} @@ -851,10 +855,10 @@ testGuestLinksExpired = do bindResponse (getJoinCodeConv tm k v) $ \resp -> do resp.status `shouldMatchInt` 404 -testConversationWithFedV0 :: HasCallStack => App () -testConversationWithFedV0 = do +testConversationWithLegacyFed :: (HasCallStack) => AnyFedDomain -> App () +testConversationWithLegacyFed domain = do alice <- randomUser OwnDomain def - bob <- randomUser FedV0Domain def + bob <- randomUser domain def withAPIVersion 4 $ connectTwoUsers alice bob conv <- @@ -865,7 +869,7 @@ testConversationWithFedV0 = do void $ changeConversationName alice conv "foobar" >>= getJSON 200 void $ awaitMatch isConvNameChangeNotif ws -testConversationWithoutFederation :: HasCallStack => App () +testConversationWithoutFederation :: (HasCallStack) => App () testConversationWithoutFederation = withModifiedBackend (def {galleyCfg = removeField "federator" >=> removeField "rabbitmq"}) $ \domain -> do diff --git a/integration/test/Test/Demo.hs b/integration/test/Test/Demo.hs index bd45cb3c334..51c643fa263 100644 --- a/integration/test/Test/Demo.hs +++ b/integration/test/Test/Demo.hs @@ -7,18 +7,13 @@ import qualified API.Brig as BrigP import qualified API.BrigInternal as BrigI import qualified API.GalleyInternal as GalleyI import qualified API.Nginz as Nginz -import Control.Concurrent -import Control.Monad.Cont -import Data.Function -import Data.Maybe import GHC.Stack import SetupHelpers -import Testlib.ModService.ServiceInstance import Testlib.Prelude -import UnliftIO.Directory +import Testlib.VersionedFed -- | Deleting unknown clients should fail with 404. -testDeleteUnknownClient :: HasCallStack => App () +testDeleteUnknownClient :: (HasCallStack) => App () testDeleteUnknownClient = do user <- randomUser OwnDomain def let fakeClientId = "deadbeefdeadbeef" @@ -26,7 +21,7 @@ testDeleteUnknownClient = do resp.status `shouldMatchInt` 404 resp.json %. "label" `shouldMatch` "client-not-found" -testModifiedBrig :: HasCallStack => App () +testModifiedBrig :: (HasCallStack) => App () testModifiedBrig = do withModifiedBackend (def {brigCfg = setField "optSettings.setFederationDomain" "overridden.example.com"}) @@ -36,7 +31,7 @@ testModifiedBrig = do resp.status `shouldMatchInt` 200 (resp.json %. "domain") `shouldMatch` "overridden.example.com" -testModifiedGalley :: HasCallStack => App () +testModifiedGalley :: (HasCallStack) => App () testModifiedGalley = do (_user, tid, _) <- createTeam OwnDomain 1 @@ -54,23 +49,23 @@ testModifiedGalley = do (_user, tid', _) <- createTeam domain 1 getFeatureStatus domain tid' `shouldMatch` "enabled" -testModifiedCannon :: HasCallStack => App () +testModifiedCannon :: (HasCallStack) => App () testModifiedCannon = do withModifiedBackend def $ \_ -> pure () -testModifiedGundeck :: HasCallStack => App () +testModifiedGundeck :: (HasCallStack) => App () testModifiedGundeck = do withModifiedBackend def $ \_ -> pure () -testModifiedCargohold :: HasCallStack => App () +testModifiedCargohold :: (HasCallStack) => App () testModifiedCargohold = do withModifiedBackend def $ \_ -> pure () -testModifiedSpar :: HasCallStack => App () +testModifiedSpar :: (HasCallStack) => App () testModifiedSpar = do withModifiedBackend def $ \_ -> pure () -testModifiedServices :: HasCallStack => App () +testModifiedServices :: (HasCallStack) => App () testModifiedServices = do let serviceMap = def @@ -94,12 +89,12 @@ testModifiedServices = do resp.status `shouldMatchInt` 200 resp.json %. "setRestrictUserCreation" `shouldMatch` False -testDynamicBackend :: HasCallStack => App () +testDynamicBackend :: (HasCallStack) => App () testDynamicBackend = do ownDomain <- objDomain OwnDomain user <- randomUser OwnDomain def uid <- objId user - bindResponse (BrigP.getSelf' ownDomain uid) $ \resp -> do + bindResponse (BrigP.getSelf user) $ \resp -> do resp.status `shouldMatchInt` 200 (resp.json %. "id") `shouldMatch` objId user @@ -117,7 +112,7 @@ testDynamicBackend = do -- now create a user in the dynamic backend userD1 <- randomUser dynDomain def uidD1 <- objId userD1 - bindResponse (BrigP.getSelf' dynDomain uidD1) $ \resp -> do + bindResponse (BrigP.getSelf userD1) $ \resp -> do resp.status `shouldMatchInt` 200 (resp.json %. "id") `shouldMatch` objId userD1 @@ -125,7 +120,7 @@ testDynamicBackend = do bindResponse (BrigP.getSelf' ownDomain uidD1) $ \resp -> do resp.status `shouldMatchInt` 404 -testStartMultipleDynamicBackends :: HasCallStack => App () +testStartMultipleDynamicBackends :: (HasCallStack) => App () testStartMultipleDynamicBackends = do let assertCorrectDomain domain = bindResponse (BrigP.getAPIVersion domain) $ @@ -134,7 +129,7 @@ testStartMultipleDynamicBackends = do (resp.json %. "domain") `shouldMatch` domain startDynamicBackends [def, def, def] $ mapM_ assertCorrectDomain -testIndependentESIndices :: HasCallStack => App () +testIndependentESIndices :: (HasCallStack) => App () testIndependentESIndices = do u1 <- randomUser OwnDomain def u2 <- randomUser OwnDomain def @@ -167,14 +162,14 @@ testIndependentESIndices = do [] -> assertFailure "Expected a non empty result, but got an empty one" doc : _ -> doc %. "id" `shouldMatch` uidD2 -testDynamicBackendsFederation :: HasCallStack => App () +testDynamicBackendsFederation :: (HasCallStack) => App () testDynamicBackendsFederation = do startDynamicBackends [def, def] $ \[aDynDomain, anotherDynDomain] -> do [u1, u2] <- createAndConnectUsers [aDynDomain, anotherDynDomain] bindResponse (BrigP.getConnection u1 u2) assertSuccess bindResponse (BrigP.getConnection u2 u1) assertSuccess -testWebSockets :: HasCallStack => App () +testWebSockets :: (HasCallStack) => App () testWebSockets = do user <- randomUser OwnDomain def withWebSocket user $ \ws -> do @@ -200,50 +195,15 @@ testUnrace = do -} retryT $ True `shouldMatch` True -testFedV0Instance :: HasCallStack => App () -testFedV0Instance = do - res <- BrigP.getAPIVersion FedV0Domain >>= getJSON 200 - res %. "domain" `shouldMatch` FedV0Domain +testLegacyFedInstance :: (HasCallStack) => AnyFedDomain -> App () +testLegacyFedInstance domain = do + res <- BrigP.getAPIVersion domain >>= getJSON 200 + res %. "domain" `shouldMatch` domain -testFedV0Federation :: HasCallStack => App () -testFedV0Federation = do +testLegacyFedFederation :: (HasCallStack) => AnyFedDomain -> App () +testLegacyFedFederation domain = do alice <- randomUser OwnDomain def - bob <- randomUser FedV0Domain def + bob <- randomUser domain def bob' <- BrigP.getUser alice bob >>= getJSON 200 bob' %. "qualified_id" `shouldMatch` (bob %. "qualified_id") - -testServiceHandles :: App () -testServiceHandles = do - -- The name was generated with a roll of a fair dice - let exe = "/tmp/tmp-42956614-e50a-11ee-8c4b-6b596d54b36b" - execName = "test-exec" - dom = "test-domain" - - writeFile - exe - "#!/usr/bin/env bash\n\ - \echo errmsg >&2\n\ - \for i in `seq 0 100`; do\n\ - \ echo $i\n\ - \ sleep 0.1\n\ - \done\n" - perms <- getPermissions exe - setPermissions exe (setOwnerExecutable True perms) - serviceInstance <- liftIO $ startServiceInstance exe [] Nothing exe execName dom - liftIO $ threadDelay 1_000_000 - cleanupServiceInstance serviceInstance - processState <- liftIO $ flushServiceInstanceOutput serviceInstance - processState - `shouldContainString` "=== stdout: ============================================\n\ - \[test-exec@test-domain] 0\n\ - \[test-exec@test-domain] 1\n\ - \[test-exec@test-domain] 2\n\ - \[test-exec@test-domain] 3\n\ - \[test-exec@test-domain] 4\n\ - \[test-exec@test-domain] 5\n\ - \[test-exec@test-domain] 6\n\ - \[test-exec@test-domain] 7\n" - processState - `shouldContainString` "=== stderr: ============================================\n\ - \[test-exec@test-domain] errmsg\n" diff --git a/integration/test/Test/MLS.hs b/integration/test/Test/MLS.hs index dcf18eb99dc..34bff33573e 100644 --- a/integration/test/Test/MLS.hs +++ b/integration/test/Test/MLS.hs @@ -405,10 +405,47 @@ testRemoteRemoveClient suite = do shouldMatch (nPayload n %. "conversation") (objId conv) shouldMatch (nPayload n %. "from") (objId bob) - msg <- asByteString (nPayload n %. "data") >>= showMessage alice1 + mlsMsg <- asByteString (nPayload n %. "data") + + -- Checks that the remove proposal is consumable by alice + void $ mlsCliConsume alice1 mlsMsg + -- This doesn't work because `sendAndConsumeCommitBundle` doesn't like + -- remove proposals from the backend. We should fix that in future. + -- void $ createPendingProposalCommit alice1 >>= sendAndConsumeCommitBundle + + parsedMsg <- showMessage alice1 mlsMsg let leafIndexBob = 1 - msg %. "message.content.body.Proposal.Remove.removed" `shouldMatchInt` leafIndexBob - msg %. "message.content.sender.External" `shouldMatchInt` 0 + parsedMsg %. "message.content.body.Proposal.Remove.removed" `shouldMatchInt` leafIndexBob + parsedMsg %. "message.content.sender.External" `shouldMatchInt` 0 + +testRemoteRemoveCreatorClient :: (HasCallStack) => Ciphersuite -> App () +testRemoteRemoveCreatorClient suite = do + setMLSCiphersuite suite + [alice, bob] <- createAndConnectUsers [OwnDomain, OtherDomain] + [alice1, bob1] <- traverse (createMLSClient def) [alice, bob] + void $ uploadNewKeyPackage bob1 + (_, conv) <- createNewGroup alice1 + void $ createAddCommit alice1 [bob] >>= sendAndConsumeCommitBundle + + withWebSocket bob $ \wsBob -> do + void $ deleteClient alice alice1.client >>= getBody 200 + let predicate n = nPayload n %. "type" `isEqual` "conversation.mls-message-add" + n <- awaitMatch predicate wsBob + shouldMatch (nPayload n %. "conversation") (objId conv) + shouldMatch (nPayload n %. "from") (objId alice) + + mlsMsg <- asByteString (nPayload n %. "data") + + -- Checks that the remove proposal is consumable by alice + void $ mlsCliConsume alice1 mlsMsg + -- This doesn't work because `sendAndConsumeCommitBundle` doesn't like + -- remove proposals from the backend. We should fix that in future. + -- void $ createPendingProposalCommit alice1 >>= sendAndConsumeCommitBundle + + parsedMsg <- showMessage alice1 mlsMsg + let leafIndexAlice = 0 + parsedMsg %. "message.content.body.Proposal.Remove.removed" `shouldMatchInt` leafIndexAlice + parsedMsg %. "message.content.sender.External" `shouldMatchInt` 0 testCreateSubConv :: HasCallStack => Ciphersuite -> App () testCreateSubConv suite = do diff --git a/integration/test/Test/MLS/One2One.hs b/integration/test/Test/MLS/One2One.hs index 9620c079399..1289d110a45 100644 --- a/integration/test/Test/MLS/One2One.hs +++ b/integration/test/Test/MLS/One2One.hs @@ -31,26 +31,26 @@ import Notifications import SetupHelpers import Test.Version import Testlib.Prelude +import Testlib.VersionedFed -testGetMLSOne2One :: HasCallStack => Version5 -> Domain -> App () -testGetMLSOne2One v otherDomain = withVersion5 v $ do - [alice, bob] <- createAndConnectUsers [OwnDomain, otherDomain] - +testGetMLSOne2OneLocalV5 :: (HasCallStack) => App () +testGetMLSOne2OneLocalV5 = withVersion5 Version5 $ do + [alice, bob] <- createAndConnectUsers [OwnDomain, OwnDomain] let assertConvData conv = do conv %. "epoch" `shouldMatchInt` 0 - case v of - Version5 -> conv %. "cipher_suite" `shouldMatchInt` 1 - NoVersion5 -> assertFieldMissing conv "cipher_suite" + conv %. "cipher_suite" `shouldMatchInt` 1 - conv <- getMLSOne2OneConversation alice bob >>= getJSON 200 - conv %. "type" `shouldMatchInt` 2 - shouldBeEmpty (conv %. "members.others") + convId <- + getMLSOne2OneConversation alice bob `bindResponse` \resp -> do + conv <- getJSON 200 resp + conv %. "type" `shouldMatchInt` 2 + shouldBeEmpty (conv %. "members.others") - conv %. "members.self.conversation_role" `shouldMatch` "wire_member" - conv %. "members.self.qualified_id" `shouldMatch` (alice %. "qualified_id") - assertConvData conv + conv %. "members.self.conversation_role" `shouldMatch` "wire_member" + conv %. "members.self.qualified_id" `shouldMatch` (alice %. "qualified_id") + assertConvData conv - convId <- conv %. "qualified_id" + conv %. "qualified_id" -- check that the conversation has the same ID on the other side conv2 <- bindResponse (getMLSOne2OneConversation bob alice) $ \resp -> do @@ -61,21 +61,69 @@ testGetMLSOne2One v otherDomain = withVersion5 v $ do conv2 %. "qualified_id" `shouldMatch` convId assertConvData conv2 -testMLSOne2OneOtherMember :: HasCallStack => One2OneScenario -> App () +testGetMLSOne2OneRemoteV5 :: (HasCallStack) => App () +testGetMLSOne2OneRemoteV5 = withVersion5 Version5 $ do + [alice, bob] <- createAndConnectUsers [OwnDomain, OtherDomain] + getMLSOne2OneConversation alice bob `bindResponse` \resp -> do + resp.status `shouldMatchInt` 400 + resp.jsonBody %. "label" `shouldMatch` "mls-federated-one2one-not-supported" + + getMLSOne2OneConversation bob alice `bindResponse` \resp -> do + resp.status `shouldMatchInt` 400 + resp.jsonBody %. "label" `shouldMatch` "mls-federated-one2one-not-supported" + +testGetMLSOne2One :: (HasCallStack) => Domain -> App () +testGetMLSOne2One bobDomain = do + [alice, bob] <- createAndConnectUsers [OwnDomain, bobDomain] + bobDomainStr <- asString bobDomain + let assertConvData conv = do + conv %. "epoch" `shouldMatchInt` 0 + assertFieldMissing conv "cipher_suite" + + mlsOne2OneConv <- + getMLSOne2OneConversation alice bob `bindResponse` \resp -> do + one2oneConv <- getJSON 200 resp + convOwnerDomain <- asString $ one2oneConv %. "conversation.qualified_id.domain" + let user = if convOwnerDomain == bobDomainStr then bob else alice + ownerDomainPublicKeys <- getMLSPublicKeys user >>= getJSON 200 + + one2oneConv %. "public_keys" `shouldMatch` ownerDomainPublicKeys + + conv <- one2oneConv %. "conversation" + conv %. "type" `shouldMatchInt` 2 + shouldBeEmpty (conv %. "members.others") + conv %. "members.self.conversation_role" `shouldMatch` "wire_member" + conv %. "members.self.qualified_id" `shouldMatch` (alice %. "qualified_id") + assertConvData conv + + pure one2oneConv + + -- check that the conversation has the same ID on the other side + mlsOne2OneConv2 <- bindResponse (getMLSOne2OneConversation bob alice) $ \resp -> do + resp.status `shouldMatchInt` 200 + resp.json + + conv2 <- mlsOne2OneConv2 %. "conversation" + conv2 %. "type" `shouldMatchInt` 2 + conv2 %. "qualified_id" `shouldMatch` (mlsOne2OneConv %. "conversation.qualified_id") + mlsOne2OneConv2 %. "public_keys" `shouldMatch` (mlsOne2OneConv %. "public_keys") + assertConvData conv2 + +testMLSOne2OneOtherMember :: (HasCallStack) => One2OneScenario -> App () testMLSOne2OneOtherMember scenario = do alice <- randomUser OwnDomain def let otherDomain = one2OneScenarioUserDomain scenario convDomain = one2OneScenarioConvDomain scenario bob <- createMLSOne2OnePartner otherDomain alice convDomain - conv <- getMLSOne2OneConversation alice bob >>= getJSON 200 + one2OneConv <- getMLSOne2OneConversation alice bob >>= getJSON 200 do - convId <- conv %. "qualified_id" - bobConv <- getMLSOne2OneConversation bob alice >>= getJSON 200 - convId `shouldMatch` (bobConv %. "qualified_id") + convId <- one2OneConv %. "conversation.qualified_id" + bobOne2OneConv <- getMLSOne2OneConversation bob alice >>= getJSON 200 + convId `shouldMatch` (bobOne2OneConv %. "conversation.qualified_id") [alice1, bob1] <- traverse (createMLSClient def) [alice, bob] traverse_ uploadNewKeyPackage [bob1] - resetGroup alice1 conv + resetOne2OneGroup alice1 one2OneConv withWebSocket bob1 $ \ws -> do commit <- createAddCommit alice1 [bob] void $ sendAndConsumeCommitBundle commit @@ -85,14 +133,47 @@ testMLSOne2OneOtherMember scenario = do -- Make sure the membership info is OK both for the MLS 1-to-1 endpoint and -- for the general conversation fetching endpoint. - let assertOthers other resp = do - bdy <- getJSON 200 resp - othersObj <- bdy %. "members.others" & asList + let assertOthers :: (HasCallStack, MakesValue other, MakesValue retrievedConv) => other -> retrievedConv -> App () + assertOthers other retrievedConv = do + othersObj <- retrievedConv %. "members.others" & asList otherActual <- assertOne othersObj otherActual %. "qualified_id" `shouldMatch` (other %. "qualified_id") forM_ [(alice, bob), (bob, alice)] $ \(self, other) -> do - getMLSOne2OneConversation self other `bindResponse` assertOthers other - getConversation self conv `bindResponse` assertOthers other + getMLSOne2OneConversation self other `bindResponse` \resp -> do + retrievedConv <- getJSON 200 resp >>= (%. "conversation") + assertOthers other retrievedConv + getConversation self (one2OneConv %. "conversation") `bindResponse` \resp -> do + retrievedConv <- getJSON 200 resp + assertOthers other retrievedConv + +testMLSOne2OneRemoveClientLocalV5 :: App () +testMLSOne2OneRemoveClientLocalV5 = withVersion5 Version5 $ do + [alice, bob] <- createAndConnectUsers [OwnDomain, OwnDomain] + conv <- getMLSOne2OneConversation alice bob >>= getJSON 200 + + [alice1, bob1] <- traverse (createMLSClient def) [alice, bob] + traverse_ uploadNewKeyPackage [bob1] + resetGroup alice1 conv + + void $ createAddCommit alice1 [bob] >>= sendAndConsumeCommitBundle + + withWebSocket alice $ \wsAlice -> do + _ <- deleteClient bob bob1.client >>= getBody 200 + let predicate n = nPayload n %. "type" `isEqual` "conversation.mls-message-add" + n <- awaitMatch predicate wsAlice + shouldMatch (nPayload n %. "conversation") (objId conv) + shouldMatch (nPayload n %. "from") (objId bob) + + mlsMsg <- asByteString (nPayload n %. "data") + + -- Checks that the remove proposal is consumable by alice + void $ mlsCliConsume alice1 mlsMsg + + parsedMsg <- showMessage alice1 mlsMsg + let leafIndexBob = 1 + -- msg `shouldMatch` "foo" + parsedMsg %. "message.content.body.Proposal.Remove.removed" `shouldMatchInt` leafIndexBob + parsedMsg %. "message.content.sender.External" `shouldMatchInt` 0 testGetMLSOne2OneUnconnected :: HasCallStack => Domain -> App () testGetMLSOne2OneUnconnected otherDomain = do @@ -116,15 +197,15 @@ testMLSOne2OneBlockedAfterConnected scenario = do let otherDomain = one2OneScenarioUserDomain scenario convDomain = one2OneScenarioConvDomain scenario bob <- createMLSOne2OnePartner otherDomain alice convDomain - conv <- getMLSOne2OneConversation alice bob >>= getJSON 200 - convId <- conv %. "qualified_id" + one2OneConv <- getMLSOne2OneConversation alice bob >>= getJSON 200 + convId <- one2OneConv %. "conversation.qualified_id" do bobConv <- getMLSOne2OneConversation bob alice >>= getJSON 200 - convId `shouldMatch` (bobConv %. "qualified_id") + convId `shouldMatch` (bobConv %. "conversation.qualified_id") [alice1, bob1] <- traverse (createMLSClient def) [alice, bob] traverse_ uploadNewKeyPackage [bob1] - resetGroup alice1 conv + resetOne2OneGroup alice1 one2OneConv commit <- createAddCommit alice1 [bob] withWebSocket bob1 $ \ws -> do void $ sendAndConsumeCommitBundle commit @@ -155,15 +236,15 @@ testMLSOne2OneUnblocked scenario = do let otherDomain = one2OneScenarioUserDomain scenario convDomain = one2OneScenarioConvDomain scenario bob <- createMLSOne2OnePartner otherDomain alice convDomain - conv <- getMLSOne2OneConversation alice bob >>= getJSON 200 + one2OneConv <- getMLSOne2OneConversation alice bob >>= getJSON 200 do - convId <- conv %. "qualified_id" + convId <- one2OneConv %. "conversation.qualified_id" bobConv <- getMLSOne2OneConversation bob alice >>= getJSON 200 - convId `shouldMatch` (bobConv %. "qualified_id") + convId `shouldMatch` (bobConv %. "conversation.qualified_id") [alice1, bob1] <- traverse (createMLSClient def) [alice, bob] traverse_ uploadNewKeyPackage [bob1] - resetGroup alice1 conv + resetOne2OneGroup alice1 one2OneConv withWebSocket bob1 $ \ws -> do commit <- createAddCommit alice1 [bob] void $ sendAndConsumeCommitBundle commit @@ -217,11 +298,12 @@ data One2OneScenario One2OneScenarioRemoteConv instance TestCases One2OneScenario where - testCases = - [ MkTestCase "[domain=own]" One2OneScenarioLocal, - MkTestCase "[domain=other;conv=own]" One2OneScenarioLocalConv, - MkTestCase "[domain=other;conv=other]" One2OneScenarioRemoteConv - ] + mkTestCases = + pure + [ MkTestCase "[domain=own]" One2OneScenarioLocal, + MkTestCase "[domain=other;conv=own]" One2OneScenarioLocalConv, + MkTestCase "[domain=other;conv=other]" One2OneScenarioRemoteConv + ] one2OneScenarioUserDomain :: One2OneScenario -> Domain one2OneScenarioUserDomain One2OneScenarioLocal = OwnDomain @@ -242,8 +324,8 @@ testMLSOne2One suite scenario = do [alice1, bob1] <- traverse (createMLSClient def) [alice, bob] traverse_ uploadNewKeyPackage [bob1] - conv <- getMLSOne2OneConversation alice bob >>= getJSON 200 - resetGroup alice1 conv + one2OneConv <- getMLSOne2OneConversation alice bob >>= getJSON 200 + resetOne2OneGroup alice1 one2OneConv commit <- createAddCommit alice1 [bob] withWebSocket bob1 $ \ws -> do @@ -264,9 +346,9 @@ testMLSOne2One suite scenario = do void $ createPendingProposalCommit alice1 >>= sendAndConsumeCommitBundle - conv' <- getMLSOne2OneConversation alice bob >>= getJSON 200 + one2OneConv' <- getMLSOne2OneConversation alice bob >>= getJSON 200 (suiteCode, _) <- assertOne $ T.hexadecimal (T.pack suite.code) - conv' %. "cipher_suite" `shouldMatchInt` suiteCode + one2OneConv' %. "conversation.cipher_suite" `shouldMatchInt` suiteCode -- | This test verifies that one-to-one conversations are created inside the -- commit lock. There used to be an issue where a conversation could be @@ -278,15 +360,16 @@ testMLSGhostOne2OneConv = do [alice, bob] <- createAndConnectUsers [OwnDomain, OwnDomain] [alice1, bob1, bob2] <- traverse (createMLSClient def) [alice, bob, bob] traverse_ uploadNewKeyPackage [bob1, bob2] - conv <- getMLSOne2OneConversation alice bob >>= getJSON 200 - resetGroup alice1 conv + one2OneConv <- getMLSOne2OneConversation alice bob >>= getJSON 200 + resetOne2OneGroup alice1 one2OneConv doneVar <- liftIO $ newEmptyMVar let checkConversation = liftIO (tryReadMVar doneVar) >>= \case Nothing -> do - bindResponse (getConversation alice conv) $ \resp -> + bindResponse (getConversation alice (one2OneConv %. "conversation")) $ \resp -> resp.status `shouldMatchOneOf` [404 :: Int, 403, 200] + checkConversation Just _ -> pure () checkConversationIO <- appToIO checkConversation @@ -301,3 +384,114 @@ testMLSGhostOne2OneConv = do createCommit liftIO $ putMVar doneVar () wait a + +-- [NOTE: Federated 1:1 MLS Conversations] +-- 1:1 Conversations shouldn't work when there is no way for the creator to know +-- the MLS public keys of the backend which will host this conversation. In +-- federation API V2, this will always work and has been tested above. When one +-- of the backends doesn't support federation API v2, the 1:1 conversation can +-- still be created but only by the user whose backend hosts this conversation. + +-- | See Note: [Federated 1:1 MLS Conversations] +testMLSFederationV1ConvOnOldBackend :: App () +testMLSFederationV1ConvOnOldBackend = do + alice <- randomUser OwnDomain def + let createBob = do + bobCandidate <- randomUser (StaticFedDomain 1) def + connectUsers [alice, bobCandidate] + getMLSOne2OneConversation alice bobCandidate `bindResponse` \resp -> do + if resp.status == 533 + then pure bobCandidate + else createBob + + bob <- createBob + [alice1, bob1] <- traverse (createMLSClient def) [alice, bob] + traverse_ uploadNewKeyPackage [alice1] + + -- Alice cannot start this conversation because it would exist on Bob's + -- backend and Alice cannot get the MLS public keys of that backend. + getMLSOne2OneConversation alice bob `bindResponse` \resp -> do + fedError <- getJSON 533 resp + fedError %. "label" `shouldMatch` "federation-version-error" + + conv <- getMLSOne2OneConversation bob alice >>= getJSON 200 + keys <- getMLSPublicKeys bob >>= getJSON 200 + resetOne2OneGroupGeneric bob1 conv keys + + withWebSocket alice1 $ \wsAlice -> do + commit <- createAddCommit bob1 [alice] + void $ sendAndConsumeCommitBundle commit + + let isMessage n = nPayload n %. "type" `isEqual` "conversation.mls-welcome" + n <- awaitMatch isMessage wsAlice + nPayload n %. "data" `shouldMatch` B8.unpack (Base64.encode (fold commit.welcome)) + + withWebSocket bob1 $ \wsBob -> do + _ <- deleteClient alice alice1.client >>= getBody 200 + + let predicate n = nPayload n %. "type" `isEqual` "conversation.mls-message-add" + n <- awaitMatch predicate wsBob + shouldMatch (nPayload n %. "conversation") (objId conv) + shouldMatch (nPayload n %. "from") (objId alice) + + mlsMsg <- asByteString (nPayload n %. "data") + + -- Checks that the remove proposal is consumable by bob + void $ mlsCliConsume bob1 mlsMsg + + parsedMsg <- showMessage bob1 mlsMsg + let leafIndexAlice = 1 + parsedMsg %. "message.content.body.Proposal.Remove.removed" `shouldMatchInt` leafIndexAlice + parsedMsg %. "message.content.sender.External" `shouldMatchInt` 0 + +-- | See Note: Federated 1:1 MLS Conversations +testMLSFederationV1ConvOnNewBackend :: App () +testMLSFederationV1ConvOnNewBackend = do + alice <- randomUser OwnDomain def + let createBob = do + bobCandidate <- randomUser (StaticFedDomain 1) def + connectUsers [alice, bobCandidate] + getMLSOne2OneConversation alice bobCandidate `bindResponse` \resp -> do + if resp.status == 200 + then pure bobCandidate + else createBob + + bob <- createBob + [alice1, bob1] <- traverse (createMLSClient def) [alice, bob] + traverse_ uploadNewKeyPackage [bob1] + + -- Bob cannot start this conversation because it would exist on Alice's + -- backend and Bob cannot get the MLS public keys of that backend. + getMLSOne2OneConversation bob alice `bindResponse` \resp -> do + fedError <- getJSON 533 resp + fedError %. "label" `shouldMatch` "federation-remote-error" + + one2OneConv <- getMLSOne2OneConversation alice bob >>= getJSON 200 + conv <- one2OneConv %. "conversation" + resetOne2OneGroup alice1 one2OneConv + + withWebSocket bob1 $ \wsBob -> do + commit <- createAddCommit alice1 [bob] + void $ sendAndConsumeCommitBundle commit + + let isMessage n = nPayload n %. "type" `isEqual` "conversation.mls-welcome" + n <- awaitMatch isMessage wsBob + nPayload n %. "data" `shouldMatch` B8.unpack (Base64.encode (fold commit.welcome)) + + withWebSocket alice1 $ \wsAlice -> do + _ <- deleteClient bob bob1.client >>= getBody 200 + + let predicate n = nPayload n %. "type" `isEqual` "conversation.mls-message-add" + n <- awaitMatch predicate wsAlice + shouldMatch (nPayload n %. "conversation") (objId conv) + shouldMatch (nPayload n %. "from") (objId bob) + + mlsMsg <- asByteString (nPayload n %. "data") + + -- Checks that the remove proposal is consumable by bob + void $ mlsCliConsume alice1 mlsMsg + + parsedMsg <- showMessage alice1 mlsMsg + let leafIndexBob = 1 + parsedMsg %. "message.content.body.Proposal.Remove.removed" `shouldMatchInt` leafIndexBob + parsedMsg %. "message.content.sender.External" `shouldMatchInt` 0 diff --git a/integration/test/Test/MLS/SubConversation.hs b/integration/test/Test/MLS/SubConversation.hs index 919faef4a93..c428f0124bf 100644 --- a/integration/test/Test/MLS/SubConversation.hs +++ b/integration/test/Test/MLS/SubConversation.hs @@ -7,6 +7,7 @@ import qualified Data.Set as Set import MLS.Util import Notifications import SetupHelpers +import Test.MLS.One2One import Testlib.Prelude testJoinSubConv :: App () @@ -35,14 +36,14 @@ testJoinOne2OneSubConv = do [alice, bob] <- createAndConnectUsers [OwnDomain, OwnDomain] [alice1, bob1, bob2] <- traverse (createMLSClient def) [alice, bob, bob] traverse_ uploadNewKeyPackage [bob1, bob2] - conv <- getMLSOne2OneConversation alice bob >>= getJSON 200 - resetGroup alice1 conv + one2OneConv <- getMLSOne2OneConversation alice bob >>= getJSON 200 + resetOne2OneGroup alice1 one2OneConv void $ createAddCommit alice1 [bob] >>= sendAndConsumeCommitBundle - createSubConv bob1 "conference" + createOne2OneSubConv bob1 "conference" (one2OneConv %. "public_keys") -- bob adds his first client to the subconversation - sub' <- getSubConversation bob conv "conference" >>= getJSON 200 + sub' <- getSubConversation bob (one2OneConv %. "conversation") "conference" >>= getJSON 200 do tm <- sub' %. "epoch_timestamp" assertBool "Epoch timestamp should not be null" (tm /= Null) @@ -52,6 +53,38 @@ testJoinOne2OneSubConv = do createExternalCommit alice1 Nothing >>= sendAndConsumeCommitBundle +testLeaveOne2OneSubConv :: One2OneScenario -> Leaver -> App () +testLeaveOne2OneSubConv scenario leaver = do + -- set up 1-1 conversation + alice <- randomUser OwnDomain def + let otherDomain = one2OneScenarioUserDomain scenario + convDomain = one2OneScenarioConvDomain scenario + bob <- createMLSOne2OnePartner otherDomain alice convDomain + [alice1, bob1] <- traverse (createMLSClient def) [alice, bob] + traverse_ uploadNewKeyPackage [bob1] + one2OneConv <- getMLSOne2OneConversation alice bob >>= getJSON 200 + resetOne2OneGroup alice1 one2OneConv + void $ createAddCommit alice1 [bob] >>= sendAndConsumeCommitBundle + + -- create and join subconversation + createOne2OneSubConv alice1 "conference" (one2OneConv %. "public_keys") + void $ createExternalCommit bob1 Nothing >>= sendAndConsumeCommitBundle + + -- one of the two clients leaves + let (leaverClient, leaverIndex, remainingClient) = case leaver of + Alice -> (alice1, 0, bob1) + Bob -> (bob1, 1, alice1) + + withWebSocket remainingClient $ \ws -> do + leaveCurrentConv leaverClient + + msg <- consumeMessage remainingClient Nothing ws + msg %. "message.content.body.Proposal.Remove.removed" `shouldMatchInt` leaverIndex + msg %. "message.content.sender.External" `shouldMatchInt` 0 + + -- the other client commits the pending proposal + void $ createPendingProposalCommit remainingClient >>= sendAndConsumeCommitBundle + testDeleteParentOfSubConv :: (HasCallStack) => Domain -> App () testDeleteParentOfSubConv secondDomain = do (alice, tid, _) <- createTeam OwnDomain 1 diff --git a/integration/test/Test/User.hs b/integration/test/Test/User.hs index 507311b37c1..a916030e909 100644 --- a/integration/test/Test/User.hs +++ b/integration/test/Test/User.hs @@ -10,8 +10,9 @@ import qualified Data.UUID as UUID import qualified Data.UUID.V4 as UUID import SetupHelpers import Testlib.Prelude +import Testlib.VersionedFed -testSupportedProtocols :: HasCallStack => Domain -> App () +testSupportedProtocols :: (HasCallStack) => OneOf Domain (FedDomain 1) -> App () testSupportedProtocols bobDomain = do alice <- randomUser OwnDomain def alice %. "supported_protocols" `shouldMatchSet` ["proteus"] diff --git a/integration/test/Test/Version.hs b/integration/test/Test/Version.hs index c3d771aa3c0..25f9d7d40e6 100644 --- a/integration/test/Test/Version.hs +++ b/integration/test/Test/Version.hs @@ -9,17 +9,24 @@ newtype Versioned' = Versioned' Versioned -- | This instance is used to generate tests for some of the versions. (Not checking all of them for time efficiency reasons) instance TestCases Versioned' where - testCases = - [ MkTestCase "[version=unversioned]" (Versioned' Unversioned), - MkTestCase "[version=versioned]" (Versioned' Versioned), - MkTestCase "[version=v1]" (Versioned' (ExplicitVersion 1)), - MkTestCase "[version=v3]" (Versioned' (ExplicitVersion 3)), - MkTestCase "[version=v6]" (Versioned' (ExplicitVersion 6)) - ] + mkTestCases = + pure + [ MkTestCase "[version=unversioned]" (Versioned' Unversioned), + MkTestCase "[version=versioned]" (Versioned' Versioned), + MkTestCase "[version=v1]" (Versioned' (ExplicitVersion 1)), + MkTestCase "[version=v3]" (Versioned' (ExplicitVersion 3)), + MkTestCase "[version=v6]" (Versioned' (ExplicitVersion 6)) + ] -- | Used to test endpoints that have changed after version 5 data Version5 = Version5 | NoVersion5 - deriving (Generic) + +instance TestCases Version5 where + mkTestCases = + pure + [ MkTestCase "[version=versioned]" NoVersion5, + MkTestCase "[version=v5]" Version5 + ] withVersion5 :: Version5 -> App a -> App a withVersion5 Version5 = withAPIVersion 5 diff --git a/integration/test/Testlib/App.hs b/integration/test/Testlib/App.hs index b6ca5970f4f..7a7a8798ef1 100644 --- a/integration/test/Testlib/App.hs +++ b/integration/test/Testlib/App.hs @@ -62,11 +62,6 @@ instance MakesValue Domain where make OwnDomain = asks (String . T.pack . (.domain1)) make OtherDomain = asks (String . T.pack . (.domain2)) -data FedDomain = FedV0Domain - -instance MakesValue FedDomain where - make FedV0Domain = asks (String . T.pack . (.federationV0Domain)) - -- | Run an action, `recoverAll`ing with exponential backoff (min step 8ms, total timeout -- ~15s). Search this package for examples how to use it. -- diff --git a/integration/test/Testlib/Env.hs b/integration/test/Testlib/Env.hs index c3be8cee328..57022fa0ac5 100644 --- a/integration/test/Testlib/Env.hs +++ b/integration/test/Testlib/Env.hs @@ -88,7 +88,8 @@ mkGlobalEnv cfgFile = do Map.fromList $ [ (intConfig.backendOne.originDomain, intConfig.backendOne.beServiceMap), (intConfig.backendTwo.originDomain, intConfig.backendTwo.beServiceMap), - (intConfig.federationV0.originDomain, intConfig.federationV0.beServiceMap) + (intConfig.federationV0.originDomain, intConfig.federationV0.beServiceMap), + (intConfig.federationV1.originDomain, intConfig.federationV1.beServiceMap) ] <> [(berDomain resource, resourceServiceMap resource) | resource <- resources] tempDir <- Codensity $ withSystemTempDirectory "test" @@ -101,6 +102,7 @@ mkGlobalEnv cfgFile = do gDomain1 = intConfig.backendOne.originDomain, gDomain2 = intConfig.backendTwo.originDomain, gFederationV0Domain = intConfig.federationV0.originDomain, + gFederationV1Domain = intConfig.federationV1.originDomain, gDynamicDomains = (.domain) <$> Map.elems intConfig.dynamicBackends, gDefaultAPIVersion = 6, gManager = manager, @@ -139,8 +141,17 @@ mkEnv ge = do domain1 = gDomain1 ge, domain2 = gDomain2 ge, federationV0Domain = gFederationV0Domain ge, + federationV1Domain = gFederationV1Domain ge, dynamicDomains = gDynamicDomains ge, defaultAPIVersion = gDefaultAPIVersion ge, + -- hardcode API versions for federated domains because they don't have + -- latest things. Ensure we do not use development API versions in + -- those domains. + apiVersionByDomain = + Map.fromList + [ (gFederationV0Domain ge, 4), + (gFederationV1Domain ge, 5) + ], manager = gManager ge, servicesCwdBase = gServicesCwdBase ge, removalKeyPaths = gRemovalKeyPaths ge, diff --git a/integration/test/Testlib/HTTP.hs b/integration/test/Testlib/HTTP.hs index 1a84ba4f48f..687e9e0cb6f 100644 --- a/integration/test/Testlib/HTTP.hs +++ b/integration/test/Testlib/HTTP.hs @@ -11,6 +11,7 @@ import qualified Data.CaseInsensitive as CI import Data.Function import Data.List import Data.List.Split (splitOn) +import qualified Data.Map as Map import Data.Maybe import Data.String import Data.String.Conversions (cs) @@ -130,21 +131,29 @@ data Versioned = Versioned | Unversioned | ExplicitVersion Int -- OwnDomain ...`. rawBaseRequest :: (HasCallStack, MakesValue domain) => domain -> Service -> Versioned -> String -> App HTTP.Request rawBaseRequest domain service versioned path = do + domainV <- objDomain domain pathSegsPrefix <- case versioned of Versioned -> do - v <- asks (.defaultAPIVersion) + v <- getAPIVersionFor domainV pure ["v" <> show v] Unversioned -> pure [] ExplicitVersion v -> do pure ["v" <> show v] - domainV <- objDomain domain serviceMap <- getServiceMap domainV liftIO . HTTP.parseRequest $ let HostPort h p = serviceHostPort serviceMap service in "http://" <> h <> ":" <> show p <> ("/" <> joinHttpPath (pathSegsPrefix <> splitHttpPath path)) +getAPIVersionFor :: (MakesValue domain) => domain -> App Int +getAPIVersionFor domain = do + d <- asString domain + versionMap <- asks (.apiVersionByDomain) + case Map.lookup d versionMap of + Nothing -> asks (.defaultAPIVersion) + Just v -> pure v + baseRequest :: (HasCallStack, MakesValue user) => user -> Service -> Versioned -> String -> App HTTP.Request baseRequest user service versioned path = do req <- rawBaseRequest user service versioned path diff --git a/integration/test/Testlib/ModService.hs b/integration/test/Testlib/ModService.hs index 4510894cd6b..b8bdbbd228a 100644 --- a/integration/test/Testlib/ModService.hs +++ b/integration/test/Testlib/ModService.hs @@ -136,6 +136,7 @@ startDynamicBackend resource beOverrides = do setEsIndex, setFederationSettings, setAwsConfigs, + setMlsPrivateKeyPaths, setLogLevel, beOverrides ] @@ -200,6 +201,12 @@ startDynamicBackend resource beOverrides = do { brigCfg = setField "elasticsearch.index" resource.berElasticsearchIndex } + setMlsPrivateKeyPaths :: ServiceOverrides + setMlsPrivateKeyPaths = + def + { galleyCfg = setField "settings.mlsPrivateKeyPaths" resource.berMlsPrivateKeyPaths + } + setLogLevel :: ServiceOverrides setLogLevel = def diff --git a/integration/test/Testlib/PTest.hs b/integration/test/Testlib/PTest.hs index 037cb276260..850960b2d2c 100644 --- a/integration/test/Testlib/PTest.hs +++ b/integration/test/Testlib/PTest.hs @@ -1,44 +1,45 @@ module Testlib.PTest where +import Control.Monad.Trans.Class +import Control.Monad.Trans.Writer import Data.Bifunctor (bimap) import Data.Char (toLower) import Data.Functor ((<&>)) import Data.Kind import Data.Proxy +import Data.Traversable import GHC.Generics import GHC.TypeLits import Testlib.Env +import Testlib.JSON import Testlib.Types import Prelude type Test = (String, String, String, String, App ()) +yieldTests :: (HasTests x) => String -> String -> String -> String -> x -> WriterT [Test] IO () +yieldTests m n s f x = do + t <- lift (mkTests m n s f x) + tell t + class HasTests x where - mkTests :: String -> String -> String -> String -> x -> [Test] + mkTests :: String -> String -> String -> String -> x -> IO [Test] instance HasTests (App ()) where - mkTests m n s f x = [(m, n, s, f, x)] + mkTests m n s f x = pure [(m, n, s, f, x)] instance (HasTests x, TestCases a) => HasTests (a -> x) where - mkTests m n s f x = - flip foldMap (testCases @a) \tc -> + mkTests m n s f x = do + tcs <- mkTestCases @a + fmap concat $ for tcs $ \tc -> mkTests m (n <> tc.testCaseName) s f (x tc.testCase) data TestCase a = MkTestCase {testCaseName :: String, testCase :: a} - deriving stock (Eq, Ord, Show, Generic) + deriving stock (Eq, Ord, Show, Generic, Functor, Foldable, Traversable) -- | enumerate all members of a bounded enum type --- --- >>> testCases @Bool --- [MkTestCase {testCaseName = "[bool=false]", testCase = False},MkTestCase {testCaseName = "[bool=true]", testCase = True}] --- >>> testCases @Domain --- [MkTestCase {testCaseName = "[domain=owndomain]", testCase = OwnDomain},MkTestCase {testCaseName = "[domain=otherdomain]", testCase = OtherDomain}] --- >>> testCases @Ciphersuite --- [MkTestCase {testCaseName = "[suite=0x0001]", testCase = Ciphersuite {code = "0x0001"}},MkTestCase {testCaseName = "[suite=0xf031]", testCase = Ciphersuite {code = "0xf031"}}] --- >>> testCases @(Tagged "foo" Bool) --- [MkTestCase {testCaseName = "[foo=false]", testCase = MkTagged {unTagged = False}},MkTestCase {testCaseName = "[foo=true]", testCase = MkTagged {unTagged = True}}] class TestCases a where - testCases :: [TestCase a] + mkTestCases :: IO [TestCase a] type Tagged :: Symbol -> Type -> Type newtype Tagged s a = MkTagged {unTagged :: a} @@ -52,21 +53,20 @@ pattern TaggedBool a = MkTagged a {-# COMPLETE TaggedBool #-} -- | only works for outer-most use of `Tagged` (not: `Maybe (Tagged "bla" Bool)`) --- --- >>> testCases @(Tagged "bla" Bool) instance (GEnum (Rep a), KnownSymbol s, Generic a) => TestCases (Tagged s a) where - testCases = - uni @(Rep a) <&> \case - -- replace the toplevel - (Left _ : ls, tc) -> - MkTestCase - { testCaseName = foldr mkName "" (Left (symbolVal @s Proxy) : ls), - testCase = MkTagged $ to tc - } - _ -> error "tagged test cases: impossible" + mkTestCases = + pure $ + uni @(Rep a) <&> \case + -- replace the toplevel + (Left _ : ls, tc) -> + MkTestCase + { testCaseName = foldr mkName "" (Left (symbolVal @s Proxy) : ls), + testCase = MkTagged $ to tc + } + _ -> error "tagged test cases: impossible" instance TestCases Ciphersuite where - testCases = do + mkTestCases = pure $ do suite <- allCiphersuites pure $ MkTestCase @@ -75,20 +75,22 @@ instance TestCases Ciphersuite where } instance TestCases CredentialType where - testCases = - [ MkTestCase "[ctype=basic]" BasicCredentialType, - MkTestCase "[ctype=x509]" X509CredentialType - ] + mkTestCases = + pure + [ MkTestCase "[ctype=basic]" BasicCredentialType, + MkTestCase "[ctype=x509]" X509CredentialType + ] -- | a default instance, normally we don't do such things but this is more convenient in -- the test suite as you don't have to derive anything instance {-# OVERLAPPABLE #-} (Generic a, GEnum (Rep a)) => TestCases a where - testCases = - uni @(Rep a) <&> \(tcn, tc) -> - MkTestCase - { testCaseName = foldr mkName "" tcn, - testCase = to tc - } + mkTestCases = + pure $ + uni @(Rep a) <&> \(tcn, tc) -> + MkTestCase + { testCaseName = foldr mkName "" tcn, + testCase = to tc + } {-# INLINE [1] mkName #-} mkName :: Either String String -> String -> String @@ -118,3 +120,15 @@ instance GEnum U1 where instance (GEnum (Rep k), Generic k) => GEnum (K1 r k) where uni = fmap (K1 . to) <$> uni @(Rep k) + +data OneOf a b = OneOfA a | OneOfB b + +instance (MakesValue a, MakesValue b) => MakesValue (OneOf a b) where + make (OneOfA a) = make a + make (OneOfB b) = make b + +instance (TestCases a, TestCases b) => TestCases (OneOf a b) where + mkTestCases = do + as <- fmap (map (fmap OneOfA)) mkTestCases + bs <- fmap (map (fmap OneOfB)) mkTestCases + pure $ as <> bs diff --git a/integration/test/Testlib/ResourcePool.hs b/integration/test/Testlib/ResourcePool.hs index 560967c06d0..cde16577091 100644 --- a/integration/test/Testlib/ResourcePool.hs +++ b/integration/test/Testlib/ResourcePool.hs @@ -15,6 +15,7 @@ import Control.Concurrent import Control.Monad.Catch import Control.Monad.Codensity import Control.Monad.IO.Class +import Data.Aeson import Data.Foldable (for_) import Data.Functor import Data.IORef @@ -123,7 +124,8 @@ backendResources dynConfs = berVHost = dynConf.domain, berNginzSslPort = Ports.portForDyn Ports.NginzSSL i, berNginzHttp2Port = Ports.portForDyn Ports.NginzHttp2 i, - berInternalServicePorts = Ports.internalServicePorts name + berInternalServicePorts = Ports.internalServicePorts name, + berMlsPrivateKeyPaths = dynConf.mlsPrivateKeyPaths } ) where @@ -153,7 +155,17 @@ backendA = berVHost = "backendA", berNginzSslPort = Ports.port Ports.NginzSSL BackendA, berInternalServicePorts = Ports.internalServicePorts BackendA, - berNginzHttp2Port = Ports.port Ports.NginzHttp2 BackendA + berNginzHttp2Port = Ports.port Ports.NginzHttp2 BackendA, + berMlsPrivateKeyPaths = + object + [ fromString "removal" + .= object + [ fromString "ed25519" .= "test/resources/backendA/ed25519.pem", + fromString "ecdsa_secp256r1_sha256" .= "test/resources/backendA/ecdsa_secp256r1_sha256.pem", + fromString "ecdsa_secp384r1_sha384" .= "test/resources/backendA/ecdsa_secp384r1_sha384.pem", + fromString "ecdsa_secp521r1_sha512" .= "test/resources/backendA/ecdsa_secp521r1_sha512.pem" + ] + ] } backendB :: BackendResource @@ -182,5 +194,15 @@ backendB = berVHost = "backendB", berNginzSslPort = Ports.port Ports.NginzSSL BackendB, berInternalServicePorts = Ports.internalServicePorts BackendB, - berNginzHttp2Port = Ports.port Ports.NginzHttp2 BackendB + berNginzHttp2Port = Ports.port Ports.NginzHttp2 BackendB, + berMlsPrivateKeyPaths = + object + [ fromString "removal" + .= object + [ fromString "ed25519" .= "test/resources/backendB/ed25519.pem", + fromString "ecdsa_secp256r1_sha256" .= "test/resources/backendB/ecdsa_secp256r1_sha256.pem", + fromString "ecdsa_secp384r1_sha384" .= "test/resources/backendB/ecdsa_secp384r1_sha384.pem", + fromString "ecdsa_secp521r1_sha512" .= "test/resources/backendB/ecdsa_secp521r1_sha512.pem" + ] + ] } diff --git a/integration/test/Testlib/Run.hs b/integration/test/Testlib/Run.hs index 687e80925f0..d5385a16376 100644 --- a/integration/test/Testlib/Run.hs +++ b/integration/test/Testlib/Run.hs @@ -1,35 +1,22 @@ -module Testlib.Run (main, mainI, createGlobalEnv) where +module Testlib.Run (main, mainI) where import Control.Concurrent import Control.Exception as E import Control.Monad import Control.Monad.Codensity import Control.Monad.IO.Class -import Control.Monad.Reader -import Crypto.Error -import qualified Crypto.PubKey.Ed25519 as Ed25519 -import Data.Aeson (Value) -import Data.ByteArray (convert) -import Data.ByteString (ByteString) -import qualified Data.ByteString as B import Data.Foldable import Data.Function import Data.Functor import Data.List -import qualified Data.Map as Map -import Data.PEM import Data.Time.Clock -import Data.Traversable (for) -import Debug.TimeStats (printTimeStats) import RunAllTests import System.Directory import System.Environment import System.Exit import System.FilePath -import Testlib.App import Testlib.Assertions import Testlib.Env -import Testlib.JSON import Testlib.Options import Testlib.Printing import Testlib.Types @@ -43,7 +30,12 @@ runTest ge action = lowerCodensity $ do env <- mkEnv ge liftIO $ (Right <$> runAppWithEnv env action) - `E.catches` [ E.Handler -- AssertionFailure + `E.catches` [ E.Handler $ \(e :: SomeAsyncException) -> do + -- AsyncExceptions need rethrowing + -- to prevent the last handler from handling async exceptions. + -- This ensures things like UserInterrupt are properly handled. + E.throw e, + E.Handler -- AssertionFailure (fmap Left . printFailureDetails), E.Handler (fmap Left . printExceptionDetails) @@ -95,6 +87,7 @@ main = do let f = testFilter opts cfg = opts.configFile + allTests <- mkAllTests let tests = filter (\(qname, _, _, _) -> f qname) . sortOn (\(qname, _, _, _) -> qname) @@ -107,70 +100,6 @@ main = do if opts.listTests then doListTests tests else runTests tests opts.xmlReport cfg - putStrLn "output from timestats library: (use `DEBUG_TIMESTATS_ENABLE=1` to enable)" - printTimeStats - -createGlobalEnv :: FilePath -> Codensity IO GlobalEnv -createGlobalEnv cfg = do - genv0 <- mkGlobalEnv cfg - -- Run codensity locally here, because we only need the environment to get at - -- Galley's configuration. Accessing the environment has the side effect of - -- creating a temporary mls directory, which we don't need here. - - let removalKeysDir = gTempDir genv0 "removal-keys" - keys <- liftIO . lowerCodensity $ do - env <- mkEnv genv0 - liftIO $ createDirectoryIfMissing True removalKeysDir - liftIO . runAppWithEnv env $ do - config <- readServiceConfig Galley - for - [ ("ed25519", loadEd25519Key), - ("ecdsa_secp256r1_sha256", loadEcKey "ecdsa_secp256r1_sha256" 73), - ("ecdsa_secp384r1_sha384", loadEcKey "ecdsa_secp384r1_sha384" 88), - ("ecdsa_secp521r1_sha512", loadEcKey "ecdsa_secp521r1_sha512" 108) - ] - $ \(sigScheme, load) -> do - key <- load config - let path = removalKeysDir (sigScheme <> ".key") - liftIO $ B.writeFile path key - pure (sigScheme, path) - - -- save removal key to a temporary file - pure genv0 {gRemovalKeyPaths = Map.fromList keys} - -getPrivateKeyPath :: Value -> String -> App FilePath -getPrivateKeyPath config signatureScheme = do - relPath <- config %. "settings.mlsPrivateKeyPaths.removal" %. signatureScheme & asString - asks \env' -> case env'.servicesCwdBase of - Nothing -> relPath - Just dir -> dir "galley" relPath - -loadEcKey :: String -> Int -> Value -> App ByteString -loadEcKey sigScheme offset config = do - path <- getPrivateKeyPath config sigScheme - bs <- liftIO $ B.readFile path - pems <- case pemParseBS bs of - Left err -> assertFailure $ "Could not parse removal key PEM: " <> err - Right x -> pure x - asn1 <- pemContent <$> assertOne pems - -- quick and dirty ASN.1 decoding: assume the key is of the correct - -- format, and simply skip the header - pure $ B.drop offset asn1 - -loadEd25519Key :: Value -> App ByteString -loadEd25519Key config = do - path <- getPrivateKeyPath config "ed25519" - bs <- liftIO $ B.readFile path - pems <- case pemParseBS bs of - Left err -> assertFailure $ "Could not parse removal key PEM: " <> err - Right x -> pure x - asn1 <- pemContent <$> assertOne pems - -- quick and dirty ASN.1 decoding: assume the key is of the correct - -- format, and simply skip the 16 byte header - let bytes = B.drop 16 asn1 - priv <- liftIO . throwCryptoErrorIO $ Ed25519.secretKey bytes - pure (convert (Ed25519.toPublic priv)) - runTests :: [(String, x, y, App ())] -> Maybe FilePath -> FilePath -> IO () runTests tests mXMLOutput cfg = do output <- newChan @@ -180,26 +109,26 @@ runTests tests mXMLOutput cfg = do Nothing -> pure () let writeOutput = writeChan output . Just - runCodensity (createGlobalEnv cfg) $ \genv -> + runCodensity (mkGlobalEnv cfg) $ \genv -> withAsync displayOutput $ \displayThread -> do - report <- fmap mconcat $ for tests $ \(qname, _, _, action) -> do - do - (mErr, tm) <- withTime (runTest genv action) - case mErr of - Left err -> do - writeOutput $ - "----- " - <> qname - <> colored red " FAIL" - <> " (" - <> printTime tm - <> ") -----\n" - <> err - <> "\n" - pure (TestSuiteReport [TestCaseReport qname (TestFailure err) tm]) - Right _ -> do - writeOutput $ qname <> colored green " OK" <> " (" <> printTime tm <> ")" <> "\n" - pure (TestSuiteReport [TestCaseReport qname TestSuccess tm]) + -- Currently 4 seems to be stable, more seems to create more timeouts. + report <- fmap mconcat $ pooledForConcurrentlyN 4 tests $ \(qname, _, _, action) -> do + (mErr, tm) <- withTime (runTest genv action) + case mErr of + Left err -> do + writeOutput $ + "----- " + <> qname + <> colored red " FAIL" + <> " (" + <> printTime tm + <> ") -----\n" + <> err + <> "\n" + pure (TestSuiteReport [TestCaseReport qname (TestFailure err) tm]) + Right _ -> do + writeOutput $ qname <> colored green " OK" <> " (" <> printTime tm <> ")" <> "\n" + pure (TestSuiteReport [TestCaseReport qname TestSuccess tm]) writeChan output Nothing wait displayThread printReport report diff --git a/integration/test/Testlib/RunServices.hs b/integration/test/Testlib/RunServices.hs index aca7867aff3..a06e18c9635 100644 --- a/integration/test/Testlib/RunServices.hs +++ b/integration/test/Testlib/RunServices.hs @@ -10,7 +10,6 @@ import System.Posix (getWorkingDirectory) import System.Process import Testlib.Prelude import Testlib.ResourcePool -import Testlib.Run (createGlobalEnv) parentDir :: FilePath -> Maybe FilePath parentDir path = @@ -52,11 +51,12 @@ main = do (_, _, _, ph) <- createProcess cp exitWith =<< waitForProcess ph - runCodensity (createGlobalEnv cfg >>= mkEnv) $ \env -> + runCodensity (mkGlobalEnv cfg >>= mkEnv) $ \env -> runAppWithEnv env $ - lowerCodensity $ do - _modifyEnv <- - traverseConcurrentlyCodensity - (\r -> startDynamicBackend r mempty) - [backendA, backendB] - liftIO run + lowerCodensity $ + do + _modifyEnv <- + traverseConcurrentlyCodensity + (\r -> startDynamicBackend r mempty) + [backendA, backendB] + liftIO run diff --git a/integration/test/Testlib/Types.hs b/integration/test/Testlib/Types.hs index f3b7cf16063..4a25e6f80b5 100644 --- a/integration/test/Testlib/Types.hs +++ b/integration/test/Testlib/Types.hs @@ -67,7 +67,8 @@ data BackendResource = BackendResource berVHost :: String, berNginzSslPort :: Word16, berNginzHttp2Port :: Word16, - berInternalServicePorts :: forall a. Num a => Service -> a + berInternalServicePorts :: forall a. (Num a) => Service -> a, + berMlsPrivateKeyPaths :: Value } instance Eq BackendResource where @@ -78,7 +79,8 @@ instance Ord BackendResource where data DynamicBackendConfig = DynamicBackendConfig { domain :: String, - federatorExternalPort :: Word16 + federatorExternalPort :: Word16, + mlsPrivateKeyPaths :: Value } deriving (Show, Generic) @@ -103,6 +105,7 @@ data GlobalEnv = GlobalEnv gDomain1 :: String, gDomain2 :: String, gFederationV0Domain :: String, + gFederationV1Domain :: String, gDynamicDomains :: [String], gDefaultAPIVersion :: Int, gManager :: HTTP.Manager, @@ -118,6 +121,8 @@ data IntegrationConfig = IntegrationConfig { backendOne :: BackendConfig, backendTwo :: BackendConfig, federationV0 :: BackendConfig, + federationV1 :: BackendConfig, + integrationTestHostName :: String, dynamicBackends :: Map String DynamicBackendConfig, rabbitmq :: RabbitMQConfig, cassandra :: CassandraConfig @@ -131,6 +136,8 @@ instance FromJSON IntegrationConfig where <$> parseJSON (Object o) <*> o .: fromString "backendTwo" <*> o .: fromString "federation-v0" + <*> o .: fromString "federation-v1" + <*> o .: fromString "integrationTestHostName" <*> o .: fromString "dynamicBackends" <*> o .: fromString "rabbitmq" <*> o .: fromString "cassandra" @@ -196,8 +203,10 @@ data Env = Env domain1 :: String, domain2 :: String, federationV0Domain :: String, + federationV1Domain :: String, dynamicDomains :: [String], defaultAPIVersion :: Int, + apiVersionByDomain :: Map String Int, manager :: HTTP.Manager, servicesCwdBase :: Maybe FilePath, -- | paths to removal keys by signature scheme @@ -232,6 +241,9 @@ data ClientIdentity = ClientIdentity } deriving stock (Show, Eq, Ord, Generic) +instance HasField "qualifiedUserId" ClientIdentity Aeson.Value where + getField cid = object [fromString "id" .= cid.user, fromString "domain" .= cid.domain] + newtype Ciphersuite = Ciphersuite {code :: String} deriving (Eq, Ord, Show, Generic) diff --git a/integration/test/Testlib/VersionedFed.hs b/integration/test/Testlib/VersionedFed.hs new file mode 100644 index 00000000000..7f18da0a401 --- /dev/null +++ b/integration/test/Testlib/VersionedFed.hs @@ -0,0 +1,66 @@ +module Testlib.VersionedFed where + +import Control.Monad.Reader +import Data.Proxy +import qualified Data.Text as T +import GHC.TypeLits +import System.Environment +import Testlib.PTest +import Testlib.Prelude + +data FedDomain n = FedDomain + +instance MakesValue (FedDomain 0) where + make FedDomain = asks (String . T.pack . (.federationV0Domain)) + +instance MakesValue (FedDomain 1) where + make FedDomain = asks (String . T.pack . (.federationV1Domain)) + +instance (KnownNat n) => TestCases (FedDomain n) where + mkTestCases = + let v = natVal (Proxy @n) + in map (fmap (const FedDomain)) + <$> mkFedTestCase ("[domain=fed-v" <> show v <> "]") v + +mkFedTestCase :: String -> Integer -> IO [TestCase Integer] +mkFedTestCase name n = do + v <- lookupEnv $ "ENABLE_FEDERATION_V" <> show n + if v == Just "1" + then pure [MkTestCase name n] + else pure [] + +data AnyFedDomain = AnyFedDomain Integer + +instance MakesValue AnyFedDomain where + make (AnyFedDomain 0) = asks (String . T.pack . (.federationV0Domain)) + make (AnyFedDomain 1) = asks (String . T.pack . (.federationV1Domain)) + make (AnyFedDomain _) = error "invalid federation version" + +instance TestCases AnyFedDomain where + mkTestCases = + map (fmap AnyFedDomain) + . concat + <$> traverse + (uncurry mkFedTestCase) + [("[domain=fed-v" <> show v <> "]", v) | v <- [0, 1]] + +-- | This can be used as an argument for parametrised tests. It will be bound +-- to at least 'OtherDomain', and optionally to legacy federated domains, +-- according to the values of the corresponding environment variables +-- (@ENABLE_FEDERATION_V0@ and similar). +data StaticDomain = StaticDomain | StaticFedDomain Integer + deriving (Eq) + +instance MakesValue StaticDomain where + make StaticDomain = make OtherDomain + make (StaticFedDomain n) = make (AnyFedDomain n) + +instance TestCases StaticDomain where + mkTestCases = do + feds <- + map (fmap StaticFedDomain) + . concat + <$> traverse + (uncurry mkFedTestCase) + [("[domain=fed-v" <> show v <> "]", v) | v <- [0, 1]] + pure $ [MkTestCase "[domain=other]" StaticDomain] <> feds diff --git a/libs/wire-api-federation/src/Wire/API/Federation/API/Galley.hs b/libs/wire-api-federation/src/Wire/API/Federation/API/Galley.hs index 77eaa112cad..a9784a80081 100644 --- a/libs/wire-api-federation/src/Wire/API/Federation/API/Galley.hs +++ b/libs/wire-api-federation/src/Wire/API/Federation/API/Galley.hs @@ -44,11 +44,15 @@ import Wire.API.Federation.API.Common import Wire.API.Federation.API.Galley.Notifications as Notifications import Wire.API.Federation.Endpoint import Wire.API.Federation.Version +import Wire.API.MLS.Keys import Wire.API.MLS.SubConversation import Wire.API.MakesFederatedCall import Wire.API.Message +import Wire.API.Routes.Named import Wire.API.Routes.Public.Galley.Messaging import Wire.API.Routes.SpecialiseToVersion +import Wire.API.Routes.Version qualified as ClientAPI +import Wire.API.Routes.Versioned qualified as ClientAPI import Wire.API.Util.Aeson (CustomEncoded (..)) import Wire.API.VersionInfo import Wire.Arbitrary (Arbitrary, GenericUniform (..)) @@ -66,7 +70,19 @@ type GalleyApi = FedEndpoint "on-conversation-created" (ConversationCreated ConvId) EmptyResponse -- This endpoint is called the first time a user from this backend is -- added to a remote conversation. - :<|> FedEndpoint "get-conversations" GetConversationsRequest GetConversationsResponse + :<|> Named + "get-conversations@v1" + ( UnnamedFedEndpointWithMods + '[Until 'V2] + "get-conversations" + GetConversationsRequest + GetConversationsResponse + ) + :<|> FedEndpointWithMods + '[From 'V2] + "get-conversations" + GetConversationsRequest + GetConversationsResponseV2 :<|> FedEndpointWithMods '[ MakesFederatedCall 'Galley "on-conversation-updated", MakesFederatedCall 'Galley "on-mls-message-sent", @@ -143,11 +159,19 @@ type GalleyApi = "leave-sub-conversation" LeaveSubConversationRequest LeaveSubConversationResponse + :<|> Named + "get-one2one-conversation@v1" + ( UnnamedFedEndpointWithMods + '[From 'V1, Until 'V2] + "get-one2one-conversation" + GetOne2OneConversationRequest + GetOne2OneConversationResponse + ) :<|> FedEndpointWithMods - '[From 'V1] + '[From 'V2] "get-one2one-conversation" GetOne2OneConversationRequest - GetOne2OneConversationResponse + GetOne2OneConversationResponseV2 -- All the notification endpoints that go through the queue-based -- federation client ('fedQueueClient'). :<|> GalleyNotificationAPI @@ -226,7 +250,7 @@ data RemoteConversation = RemoteConversation id :: ConvId, metadata :: ConversationMetadata, members :: RemoteConvMembers, - protocol :: Protocol + protocol :: ClientAPI.Versioned 'ClientAPI.V5 Protocol } deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform RemoteConversation) @@ -234,6 +258,42 @@ data RemoteConversation = RemoteConversation instance ToSchema RemoteConversation +-- | A conversation hosted on a remote backend. This contains the same +-- information as a 'Conversation', with the exception that conversation status +-- fields (muted\/archived\/hidden) are omitted, since they are not known by the +-- remote backend. +data RemoteConversationV2 = RemoteConversationV2 + { -- | Id of the conversation, implicitly qualified with the domain of the + -- backend that created this value. + id :: ConvId, + metadata :: ConversationMetadata, + members :: RemoteConvMembers, + protocol :: Protocol + } + deriving stock (Eq, Show, Generic) + deriving (Arbitrary) via (GenericUniform RemoteConversationV2) + deriving (FromJSON, ToJSON) via (CustomEncoded RemoteConversationV2) + +instance ToSchema RemoteConversationV2 + +remoteConversationFromV2 :: RemoteConversationV2 -> RemoteConversation +remoteConversationFromV2 rc = + RemoteConversation + { id = rc.id, + metadata = rc.metadata, + members = rc.members, + protocol = ClientAPI.Versioned rc.protocol + } + +remoteConversationToV2 :: RemoteConversation -> RemoteConversationV2 +remoteConversationToV2 rc = + RemoteConversationV2 + { id = rc.id, + metadata = rc.metadata, + members = rc.members, + protocol = rc.protocol.unVersioned + } + newtype GetConversationsResponse = GetConversationsResponse { convs :: [RemoteConversation] } @@ -243,6 +303,21 @@ newtype GetConversationsResponse = GetConversationsResponse instance ToSchema GetConversationsResponse +newtype GetConversationsResponseV2 = GetConversationsResponseV2 + { convs :: [RemoteConversationV2] + } + deriving stock (Eq, Show, Generic) + deriving (Arbitrary) via (GenericUniform GetConversationsResponseV2) + deriving (ToJSON, FromJSON) via (CustomEncoded GetConversationsResponseV2) + +instance ToSchema GetConversationsResponseV2 + +getConversationsResponseToV2 :: GetConversationsResponse -> GetConversationsResponseV2 +getConversationsResponseToV2 res = GetConversationsResponseV2 (map remoteConversationToV2 res.convs) + +getConversationsResponseFromV2 :: GetConversationsResponseV2 -> GetConversationsResponse +getConversationsResponseFromV2 res = GetConversationsResponse (map remoteConversationFromV2 res.convs) + data GetOne2OneConversationResponse = GetOne2OneConversationOk RemoteConversation | -- | This is returned when the local backend is asked for a 1-1 conversation @@ -257,6 +332,29 @@ data GetOne2OneConversationResponse instance ToSchema GetOne2OneConversationResponse +data GetOne2OneConversationResponseV2 + = GetOne2OneConversationV2Ok RemoteMLSOne2OneConversation + | -- | This is returned when the local backend is asked for a 1-1 conversation + -- that should reside on the other backend. + GetOne2OneConversationV2BackendMismatch + | -- | This is returned when a 1-1 conversation between two unconnected users + -- is requested. + GetOne2OneConversationV2NotConnected + | GetOne2OneConversationV2MLSNotEnabled + deriving stock (Eq, Show, Generic) + deriving (ToJSON, FromJSON) via (CustomEncoded GetOne2OneConversationResponseV2) + +instance ToSchema GetOne2OneConversationResponseV2 + +data RemoteMLSOne2OneConversation = RemoteMLSOne2OneConversation + { conversation :: RemoteConversationV2, + publicKeys :: MLSKeysByPurpose MLSPublicKeys + } + deriving stock (Eq, Show, Generic) + deriving (ToJSON, FromJSON) via (CustomEncoded RemoteMLSOne2OneConversation) + +instance ToSchema RemoteMLSOne2OneConversation + -- | A record type describing a new federated conversation -- -- FUTUREWORK: Think about extracting common conversation metadata into a diff --git a/libs/wire-api-federation/src/Wire/API/Federation/Client.hs b/libs/wire-api-federation/src/Wire/API/Federation/Client.hs index 37444a6a49e..79bd8d34bc1 100644 --- a/libs/wire-api-federation/src/Wire/API/Federation/Client.hs +++ b/libs/wire-api-federation/src/Wire/API/Federation/Client.hs @@ -27,6 +27,7 @@ module Wire.API.Federation.Client runVersionedFederatorClient, runFederatorClientToCodensity, runVersionedFederatorClientToCodensity, + getNegotiatedVersion, performHTTP2Request, consumeStreamingResponseWith, streamingResponseStrictBody, @@ -117,6 +118,9 @@ instance VersionedMonad Version (FederatorClient c) where v <- asks cveVersion guard (maybe True p v) +getNegotiatedVersion :: FederatorClient c (Maybe Version) +getNegotiatedVersion = asks cveVersion + liftCodensity :: Codensity IO a -> FederatorClient c a liftCodensity = FederatorClient . lift . lift . lift diff --git a/libs/wire-api-federation/src/Wire/API/Federation/Version.hs b/libs/wire-api-federation/src/Wire/API/Federation/Version.hs index a9055c7384b..5c875d809a1 100644 --- a/libs/wire-api-federation/src/Wire/API/Federation/Version.hs +++ b/libs/wire-api-federation/src/Wire/API/Federation/Version.hs @@ -22,6 +22,7 @@ module Wire.API.Federation.Version Version (..), V0Sym0, V1Sym0, + V2Sym0, intToVersion, versionInt, supportedVersions, @@ -49,13 +50,14 @@ import Data.Set qualified as Set import Data.Singletons.Base.TH import Imports -data Version = V0 | V1 +data Version = V0 | V1 | V2 deriving stock (Eq, Ord, Bounded, Enum, Show, Generic) deriving (FromJSON, ToJSON) via (Schema Version) versionInt :: Version -> Int versionInt V0 = 0 versionInt V1 = 1 +versionInt V2 = 2 intToVersion :: Int -> Maybe Version intToVersion intV = find (\v -> versionInt v == intV) [minBound ..] @@ -64,7 +66,8 @@ instance ToSchema Version where schema = enum @Integer "Version" . mconcat $ [ element 0 V0, - element 1 V1 + element 1 V1, + element 2 V2 ] supportedVersions :: Set Version diff --git a/libs/wire-api-federation/test/Test/Wire/API/Federation/Golden/GetOne2OneConversationResponse.hs b/libs/wire-api-federation/test/Test/Wire/API/Federation/Golden/GetOne2OneConversationResponse.hs new file mode 100644 index 00000000000..9ea43d45966 --- /dev/null +++ b/libs/wire-api-federation/test/Test/Wire/API/Federation/Golden/GetOne2OneConversationResponse.hs @@ -0,0 +1,130 @@ +module Test.Wire.API.Federation.Golden.GetOne2OneConversationResponse where + +import Data.Domain +import Data.Id +import Data.Json.Util +import Data.Qualified +import Data.Set qualified as Set +import Data.UUID qualified as UUID +import Imports +import Wire.API.Conversation +import Wire.API.Conversation.Protocol +import Wire.API.Conversation.Role +import Wire.API.Federation.API.Galley +import Wire.API.MLS.Keys +import Wire.API.Routes.Versioned qualified as ClientAPI + +testObject_GetOne2OneConversationResponseOk :: GetOne2OneConversationResponse +testObject_GetOne2OneConversationResponseOk = + GetOne2OneConversationOk remoteConversation + +testObject_GetOne2OneConversationResponseBackendMismatch :: GetOne2OneConversationResponse +testObject_GetOne2OneConversationResponseBackendMismatch = GetOne2OneConversationBackendMismatch + +testObject_GetOne2OneConversationResponseNotConnected :: GetOne2OneConversationResponse +testObject_GetOne2OneConversationResponseNotConnected = GetOne2OneConversationNotConnected + +testObject_GetOne2OneConversationResponseV2Ok :: GetOne2OneConversationResponseV2 +testObject_GetOne2OneConversationResponseV2Ok = + GetOne2OneConversationV2Ok $ + RemoteMLSOne2OneConversation + { conversation = remoteConversationV2, + publicKeys = + MLSKeysByPurpose + { removal = + MLSKeys + { ed25519 = + MLSPublicKey + (fromBase64TextLenient "7C8PpP91rzMnD4VHuWTI3yNuInfbzIk937uF0Cg/Piw="), + ecdsa_secp256r1_sha256 = + MLSPublicKey + (fromBase64TextLenient "ArUTSywmqya1wAGwrK+pJuA7KSpKm06y3eZq8Py2NMM="), + ecdsa_secp384r1_sha384 = + MLSPublicKey + (fromBase64TextLenient "7pKiTLf72OfpQIeVeXF0mJKfWsBnhTtMUy0zuKasYjlTQUW5fGtcyAFXinM3FahV"), + ecdsa_secp521r1_sha512 = + MLSPublicKey + (fromBase64TextLenient "9twvhZ57ytiujWXFtSmxd8I5r9iZjgdCtGtReJT3yQL2BCGZ80Vzq/MrmV+O0i7lZEI1gqbr8vL1xKk+2h2LyQ==") + } + } + } + +testObject_GetOne2OneConversationResponseV2BackendMismatch :: GetOne2OneConversationResponseV2 +testObject_GetOne2OneConversationResponseV2BackendMismatch = GetOne2OneConversationV2BackendMismatch + +testObject_GetOne2OneConversationResponseV2NotConnected :: GetOne2OneConversationResponseV2 +testObject_GetOne2OneConversationResponseV2NotConnected = GetOne2OneConversationV2NotConnected + +remoteConversation :: RemoteConversation +remoteConversation = + RemoteConversation + { id = (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000200040001"))), + metadata = + ConversationMetadata + { cnvmType = One2OneConv, + cnvmCreator = Just (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000200000001"))), + cnvmAccess = [], + cnvmAccessRoles = Set.empty, + cnvmName = Just " 0", + cnvmTeam = Just (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000002"))), + cnvmMessageTimer = Nothing, + cnvmReceiptMode = Just (ReceiptMode {unReceiptMode = -2}) + }, + members = + RemoteConvMembers + { selfRole = roleNameWireAdmin, + others = + [ OtherMember + { omQualifiedId = + Qualified + (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000200000001"))) + (Domain "example.com"), + omService = Nothing, + omConvRoleName = roleNameWireMember + } + ] + }, + protocol = + ClientAPI.Versioned . ProtocolMLS $ + ConversationMLSData + { cnvmlsGroupId = GroupId "group", + cnvmlsActiveData = Nothing + } + } + +remoteConversationV2 :: RemoteConversationV2 +remoteConversationV2 = + RemoteConversationV2 + { id = (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000200040001"))), + metadata = + ConversationMetadata + { cnvmType = One2OneConv, + cnvmCreator = Just (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000200000001"))), + cnvmAccess = [], + cnvmAccessRoles = Set.empty, + cnvmName = Just " 0", + cnvmTeam = Just (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000002"))), + cnvmMessageTimer = Nothing, + cnvmReceiptMode = Just (ReceiptMode {unReceiptMode = -2}) + }, + members = + RemoteConvMembers + { selfRole = roleNameWireAdmin, + others = + [ OtherMember + { omQualifiedId = + Qualified + (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000200000001"))) + (Domain "example.com"), + omService = Nothing, + omConvRoleName = roleNameWireMember + } + ] + }, + protocol = + ProtocolMLS $ + ConversationMLSData + { cnvmlsGroupId = GroupId "group", + cnvmlsActiveData = Nothing + } + } diff --git a/libs/wire-api-federation/test/Test/Wire/API/Federation/Golden/GoldenSpec.hs b/libs/wire-api-federation/test/Test/Wire/API/Federation/Golden/GoldenSpec.hs index b691cd8e962..038f98b0d1e 100644 --- a/libs/wire-api-federation/test/Test/Wire/API/Federation/Golden/GoldenSpec.hs +++ b/libs/wire-api-federation/test/Test/Wire/API/Federation/Golden/GoldenSpec.hs @@ -21,6 +21,7 @@ import Imports import Test.Hspec import Test.Wire.API.Federation.Golden.ConversationCreated qualified as ConversationCreated import Test.Wire.API.Federation.Golden.ConversationUpdate qualified as ConversationUpdate +import Test.Wire.API.Federation.Golden.GetOne2OneConversationResponse qualified as GetOne2OneConversationResponse import Test.Wire.API.Federation.Golden.LeaveConversationRequest qualified as LeaveConversationRequest import Test.Wire.API.Federation.Golden.LeaveConversationResponse qualified as LeaveConversationResponse import Test.Wire.API.Federation.Golden.MLSMessageSendingStatus qualified as MLSMessageSendingStatus @@ -74,3 +75,13 @@ spec = [ (ConversationCreated.testObject_ConversationCreated1, "testObject_ConversationCreated1.json"), (ConversationCreated.testObject_ConversationCreated2, "testObject_ConversationCreated2.json") ] + testObjects + [ (GetOne2OneConversationResponse.testObject_GetOne2OneConversationResponseV2Ok, "testObject_GetOne2OneConversationResponseV2Ok.json"), + (GetOne2OneConversationResponse.testObject_GetOne2OneConversationResponseV2BackendMismatch, "testObject_GetOne2OneConversationResponseV2BackendMismatch.json"), + (GetOne2OneConversationResponse.testObject_GetOne2OneConversationResponseV2NotConnected, "testObject_GetOne2OneConversationResponseV2NotConnected.json") + ] + testObjects + [ (GetOne2OneConversationResponse.testObject_GetOne2OneConversationResponseOk, "testObject_GetOne2OneConversationResponseOk.json"), + (GetOne2OneConversationResponse.testObject_GetOne2OneConversationResponseBackendMismatch, "testObject_GetOne2OneConversationResponseBackendMismatch.json"), + (GetOne2OneConversationResponse.testObject_GetOne2OneConversationResponseNotConnected, "testObject_GetOne2OneConversationResponseNotConnected.json") + ] diff --git a/libs/wire-api-federation/test/golden/testObject_GetOne2OneConversationResponseBackendMismatch.json b/libs/wire-api-federation/test/golden/testObject_GetOne2OneConversationResponseBackendMismatch.json new file mode 100644 index 00000000000..be96bcaa144 --- /dev/null +++ b/libs/wire-api-federation/test/golden/testObject_GetOne2OneConversationResponseBackendMismatch.json @@ -0,0 +1,3 @@ +{ + "tag": "GetOne2OneConversationBackendMismatch" +} \ No newline at end of file diff --git a/libs/wire-api-federation/test/golden/testObject_GetOne2OneConversationResponseNotConnected.json b/libs/wire-api-federation/test/golden/testObject_GetOne2OneConversationResponseNotConnected.json new file mode 100644 index 00000000000..3920fe3bed6 --- /dev/null +++ b/libs/wire-api-federation/test/golden/testObject_GetOne2OneConversationResponseNotConnected.json @@ -0,0 +1,3 @@ +{ + "tag": "GetOne2OneConversationNotConnected" +} \ No newline at end of file diff --git a/libs/wire-api-federation/test/golden/testObject_GetOne2OneConversationResponseOk.json b/libs/wire-api-federation/test/golden/testObject_GetOne2OneConversationResponseOk.json new file mode 100644 index 00000000000..dbfa3343671 --- /dev/null +++ b/libs/wire-api-federation/test/golden/testObject_GetOne2OneConversationResponseOk.json @@ -0,0 +1,39 @@ +{ + "contents": { + "id": "00000001-0000-0001-0000-000200040001", + "members": { + "others": [ + { + "conversation_role": "wire_member", + "id": "00000001-0000-0001-0000-000200000001", + "qualified_id": { + "domain": "example.com", + "id": "00000001-0000-0001-0000-000200000001" + }, + "status": 0 + } + ], + "self_role": "wire_admin" + }, + "metadata": { + "access": [], + "access_role": [], + "creator": "00000001-0000-0001-0000-000200000001", + "last_event": "0.0", + "last_event_time": "1970-01-01T00:00:00.000Z", + "message_timer": null, + "name": " 0", + "receipt_mode": -2, + "team": "00000001-0000-0001-0000-000100000002", + "type": 2 + }, + "protocol": { + "cipher_suite": 1, + "epoch": 0, + "epoch_timestamp": null, + "group_id": "Z3JvdXA=", + "protocol": "mls" + } + }, + "tag": "GetOne2OneConversationOk" +} \ No newline at end of file diff --git a/libs/wire-api-federation/test/golden/testObject_GetOne2OneConversationResponseV2BackendMismatch.json b/libs/wire-api-federation/test/golden/testObject_GetOne2OneConversationResponseV2BackendMismatch.json new file mode 100644 index 00000000000..49390ae4674 --- /dev/null +++ b/libs/wire-api-federation/test/golden/testObject_GetOne2OneConversationResponseV2BackendMismatch.json @@ -0,0 +1,3 @@ +{ + "tag": "GetOne2OneConversationV2BackendMismatch" +} \ No newline at end of file diff --git a/libs/wire-api-federation/test/golden/testObject_GetOne2OneConversationResponseV2NotConnected.json b/libs/wire-api-federation/test/golden/testObject_GetOne2OneConversationResponseV2NotConnected.json new file mode 100644 index 00000000000..22355bf11e6 --- /dev/null +++ b/libs/wire-api-federation/test/golden/testObject_GetOne2OneConversationResponseV2NotConnected.json @@ -0,0 +1,3 @@ +{ + "tag": "GetOne2OneConversationV2NotConnected" +} \ No newline at end of file diff --git a/libs/wire-api-federation/test/golden/testObject_GetOne2OneConversationResponseV2Ok.json b/libs/wire-api-federation/test/golden/testObject_GetOne2OneConversationResponseV2Ok.json new file mode 100644 index 00000000000..d14e627e6be --- /dev/null +++ b/libs/wire-api-federation/test/golden/testObject_GetOne2OneConversationResponseV2Ok.json @@ -0,0 +1,47 @@ +{ + "contents": { + "conversation": { + "id": "00000001-0000-0001-0000-000200040001", + "members": { + "others": [ + { + "conversation_role": "wire_member", + "id": "00000001-0000-0001-0000-000200000001", + "qualified_id": { + "domain": "example.com", + "id": "00000001-0000-0001-0000-000200000001" + }, + "status": 0 + } + ], + "self_role": "wire_admin" + }, + "metadata": { + "access": [], + "access_role": [], + "creator": "00000001-0000-0001-0000-000200000001", + "last_event": "0.0", + "last_event_time": "1970-01-01T00:00:00.000Z", + "message_timer": null, + "name": " 0", + "receipt_mode": -2, + "team": "00000001-0000-0001-0000-000100000002", + "type": 2 + }, + "protocol": { + "epoch": 0, + "group_id": "Z3JvdXA=", + "protocol": "mls" + } + }, + "public_keys": { + "removal": { + "ecdsa_secp256r1_sha256": "ArUTSywmqya1wAGwrK+pJuA7KSpKm06y3eZq8Py2NMM=", + "ecdsa_secp384r1_sha384": "7pKiTLf72OfpQIeVeXF0mJKfWsBnhTtMUy0zuKasYjlTQUW5fGtcyAFXinM3FahV", + "ecdsa_secp521r1_sha512": "9twvhZ57ytiujWXFtSmxd8I5r9iZjgdCtGtReJT3yQL2BCGZ80Vzq/MrmV+O0i7lZEI1gqbr8vL1xKk+2h2LyQ==", + "ed25519": "7C8PpP91rzMnD4VHuWTI3yNuInfbzIk937uF0Cg/Piw=" + } + } + }, + "tag": "GetOne2OneConversationV2Ok" +} \ No newline at end of file diff --git a/libs/wire-api-federation/wire-api-federation.cabal b/libs/wire-api-federation/wire-api-federation.cabal index e2490419ca0..dc7ea88d9e8 100644 --- a/libs/wire-api-federation/wire-api-federation.cabal +++ b/libs/wire-api-federation/wire-api-federation.cabal @@ -132,6 +132,7 @@ test-suite spec Test.Wire.API.Federation.API.BrigSpec Test.Wire.API.Federation.Golden.ConversationCreated Test.Wire.API.Federation.Golden.ConversationUpdate + Test.Wire.API.Federation.Golden.GetOne2OneConversationResponse Test.Wire.API.Federation.Golden.GoldenSpec Test.Wire.API.Federation.Golden.LeaveConversationRequest Test.Wire.API.Federation.Golden.LeaveConversationResponse diff --git a/libs/wire-api/src/Wire/API/Conversation.hs b/libs/wire-api/src/Wire/API/Conversation.hs index d0c9070dee8..22ad7d01c42 100644 --- a/libs/wire-api/src/Wire/API/Conversation.hs +++ b/libs/wire-api/src/Wire/API/Conversation.hs @@ -34,6 +34,7 @@ module Wire.API.Conversation cnvMessageTimer, cnvReceiptMode, cnvAccessRoles, + MLSOne2OneConversation (..), CreateGroupConversation (..), ConversationCoverView (..), ConversationList (..), @@ -97,7 +98,6 @@ import Data.List.NonEmpty (NonEmpty) import Data.List1 import Data.Map qualified as Map import Data.Misc -import Data.OpenApi (deprecated) import Data.OpenApi qualified as S import Data.Qualified import Data.Range (Range, fromRange, rangedSchema) @@ -115,6 +115,7 @@ import Wire.API.Conversation.Protocol import Wire.API.Conversation.Role (RoleName, roleNameWireAdmin) import Wire.API.Event.LeaveReason import Wire.API.MLS.Group +import Wire.API.MLS.Keys import Wire.API.Routes.MultiTablePaging import Wire.API.Routes.MultiVerb import Wire.API.Routes.Version @@ -290,6 +291,20 @@ conversationSchema v = (description ?~ "A conversation object as returned from the server") (conversationObjectSchema v) +data MLSOne2OneConversation a = MLSOne2OneConversation + { conversation :: Conversation, + publicKeys :: MLSKeysByPurpose (MLSKeys a) + } + deriving (ToJSON, FromJSON, S.ToSchema) via (Schema (MLSOne2OneConversation a)) + +instance (ToSchema a) => ToSchema (MLSOne2OneConversation a) where + schema = + let aName = maybe "" ("_" <>) $ getName (schemaDoc (schema @a)) + in object ("MLSOne2OneConversation" <> aName) $ + MLSOne2OneConversation + <$> conversation .= field "conversation" schema + <*> publicKeys .= field "public_keys" schema + -- | The public-facing conversation type extended with information on which -- remote users could not be added when creating the conversation. data CreateGroupConversation = CreateGroupConversation @@ -683,7 +698,7 @@ newConvSchema v sch = <$> newConvUsers .= ( fieldWithDocModifier "users" - ( (deprecated ?~ True) + ( (S.deprecated ?~ True) . (description ?~ usersDesc) ) (array schema) diff --git a/libs/wire-api/src/Wire/API/Conversation/Protocol.hs b/libs/wire-api/src/Wire/API/Conversation/Protocol.hs index 12b05f4c0f8..9b35a2e9a21 100644 --- a/libs/wire-api/src/Wire/API/Conversation/Protocol.hs +++ b/libs/wire-api/src/Wire/API/Conversation/Protocol.hs @@ -109,12 +109,7 @@ optionalActiveMLSConversationDataSchema (Just v) (description ?~ "The epoch number of the corresponding MLS group") schema <*> fmap (.epochTimestamp) - .= maybe_ - ( optFieldWithDocModifier - "epoch_timestamp" - (description ?~ "The timestamp of the epoch number") - utcTimeSchema - ) + .= field "epoch_timestamp" (named "Epoch Timestamp" . nullable . unnamed $ utcTimeSchema) <*> maybe MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 (.ciphersuite) .= fieldWithDocModifier "cipher_suite" @@ -251,6 +246,9 @@ protocolSchema v = instance ToSchema Protocol where schema = object "Protocol" (protocolSchema Nothing) +instance ToSchema (Versioned 'V5 Protocol) where + schema = object "Protocol" (Versioned <$> unVersioned .= protocolSchema (Just V5)) + deriving via (Schema Protocol) instance FromJSON Protocol deriving via (Schema Protocol) instance ToJSON Protocol diff --git a/libs/wire-api/src/Wire/API/Error/Galley.hs b/libs/wire-api/src/Wire/API/Error/Galley.hs index a079d35c1b5..16e1e4f4531 100644 --- a/libs/wire-api/src/Wire/API/Error/Galley.hs +++ b/libs/wire-api/src/Wire/API/Error/Galley.hs @@ -104,6 +104,7 @@ data GalleyError | MLSSubConvUnsupportedConvType | MLSSubConvClientNotInParent | MLSMigrationCriteriaNotSatisfied + | MLSFederatedOne2OneNotSupported | -- NoBindingTeamMembers | NoBindingTeam @@ -253,6 +254,8 @@ type instance MapError 'MLSSubConvClientNotInParent = 'StaticError 403 "mls-subc type instance MapError 'MLSMigrationCriteriaNotSatisfied = 'StaticError 400 "mls-migration-criteria-not-satisfied" "The migration criteria for mixed to MLS protocol transition are not satisfied for this conversation" +type instance MapError 'MLSFederatedOne2OneNotSupported = 'StaticError 400 "mls-federated-one2one-not-supported" "Federated One2One MLS conversations are only supported in API version >= 6" + type instance MapError 'NoBindingTeamMembers = 'StaticError 403 "non-binding-team-members" "Both users must be members of the same binding team" type instance MapError 'NoBindingTeam = 'StaticError 403 "no-binding-team" "Operation allowed only on binding teams" diff --git a/libs/wire-api/src/Wire/API/MLS/Keys.hs b/libs/wire-api/src/Wire/API/MLS/Keys.hs index 28a49047332..1fe66e6eff4 100644 --- a/libs/wire-api/src/Wire/API/MLS/Keys.hs +++ b/libs/wire-api/src/Wire/API/MLS/Keys.hs @@ -20,7 +20,8 @@ module Wire.API.MLS.Keys where import Crypto.ECC (Curve_P256R1, Curve_P384R1, Curve_P521R1) import Crypto.PubKey.ECDSA qualified as ECDSA import Data.Aeson (FromJSON (..), ToJSON (..)) -import Data.ByteArray +import Data.Aeson qualified as A +import Data.ByteArray qualified as BA import Data.Json.Util import Data.OpenApi qualified as S import Data.Proxy @@ -76,8 +77,20 @@ instance ToSchema MLSPublicKey where mlsKeysToPublic :: MLSPrivateKeys -> MLSPublicKeys mlsKeysToPublic (MLSPrivateKeys (_, ed) (_, ec256) (_, ec384) (_, ec521)) = MLSKeys - { ed25519 = MLSPublicKey $ convert ed, + { ed25519 = MLSPublicKey $ BA.convert ed, ecdsa_secp256r1_sha256 = MLSPublicKey $ ECDSA.encodePublic (Proxy @Curve_P256R1) ec256, ecdsa_secp384r1_sha384 = MLSPublicKey $ ECDSA.encodePublic (Proxy @Curve_P384R1) ec384, ecdsa_secp521r1_sha512 = MLSPublicKey $ ECDSA.encodePublic (Proxy @Curve_P521R1) ec521 } + +data SomeKey = SomeKey A.Value + +instance ToSchema SomeKey where + schema = mkSchema d r w + where + d = pure $ S.NamedSchema (Just "SomeKey") mempty + r = fmap SomeKey . parseJSON + w (SomeKey x) = Just (toJSON x) + +mkSomeKey :: (ToJSON a) => a -> SomeKey +mkSomeKey = SomeKey . toJSON 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 064dd35f673..26c2e1485f1 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 @@ -35,6 +35,7 @@ import Wire.API.Error import Wire.API.Error.Galley import Wire.API.Event.Conversation import Wire.API.MLS.GroupInfo +import Wire.API.MLS.Keys import Wire.API.MLS.Servant import Wire.API.MLS.SubConversation import Wire.API.MakesFederatedCall @@ -679,22 +680,23 @@ type ConversationAPI = :> ZLocalUser :> CanThrow 'MLSNotEnabled :> CanThrow 'NotConnected + :> CanThrow 'MLSFederatedOne2OneNotSupported :> "conversations" :> "one2one" :> QualifiedCapture "usr" UserId :> MultiVerb1 'GET '[JSON] (VersionedRespond 'V5 200 "MLS 1-1 conversation" Conversation) ) :<|> Named - "get-one-to-one-mls-conversation" + "get-one-to-one-mls-conversation@v6" ( Summary "Get an MLS 1:1 conversation" - :> From 'V5 + :> From 'V6 :> ZLocalUser :> CanThrow 'MLSNotEnabled :> CanThrow 'NotConnected :> "conversations" :> "one2one" :> QualifiedCapture "usr" UserId - :> MultiVerb1 'GET '[JSON] (Respond 200 "MLS 1-1 conversation" Conversation) + :> MultiVerb1 'GET '[JSON] (Respond 200 "MLS 1-1 conversation" (MLSOne2OneConversation MLSPublicKey)) ) -- This endpoint can lead to the following events being sent: -- - MemberJoin event to members diff --git a/libs/wire-api/src/Wire/API/Routes/Versioned.hs b/libs/wire-api/src/Wire/API/Routes/Versioned.hs index 7707e3441e6..98b40cee6c9 100644 --- a/libs/wire-api/src/Wire/API/Routes/Versioned.hs +++ b/libs/wire-api/src/Wire/API/Routes/Versioned.hs @@ -29,6 +29,7 @@ import Servant import Servant.API.ContentTypes import Servant.OpenApi import Servant.OpenApi.Internal +import Test.QuickCheck (Arbitrary) import Wire.API.Routes.MultiVerb import Wire.API.Routes.Version @@ -102,6 +103,7 @@ instance -- Servant. newtype Versioned (v :: Version) a = Versioned {unVersioned :: a} deriving (Eq, Show) + deriving newtype (Arbitrary) instance Functor (Versioned v) where fmap f (Versioned a) = Versioned (f a) diff --git a/libs/wire-api/test/golden/testObject_Conversation_v2_user_4.json b/libs/wire-api/test/golden/testObject_Conversation_v2_user_4.json index 6609a65f3af..cda477d4510 100644 --- a/libs/wire-api/test/golden/testObject_Conversation_v2_user_4.json +++ b/libs/wire-api/test/golden/testObject_Conversation_v2_user_4.json @@ -22,6 +22,7 @@ "cipher_suite": 1, "creator": "00000000-0000-0000-0000-000200000001", "epoch": 0, + "epoch_timestamp": null, "group_id": "dGVzdF9ncm91cA==", "id": "00000000-0000-0000-0000-000000000002", "last_event": "0.0", diff --git a/libs/wire-api/test/golden/testObject_Conversation_v5_user_4.json b/libs/wire-api/test/golden/testObject_Conversation_v5_user_4.json index 430ac682cee..e7351a003ce 100644 --- a/libs/wire-api/test/golden/testObject_Conversation_v5_user_4.json +++ b/libs/wire-api/test/golden/testObject_Conversation_v5_user_4.json @@ -21,6 +21,7 @@ "cipher_suite": 1, "creator": "00000000-0000-0000-0000-000200000001", "epoch": 0, + "epoch_timestamp": null, "group_id": "dGVzdF9ncm91cA==", "id": "00000000-0000-0000-0000-000000000002", "last_event": "0.0", diff --git a/libs/wire-api/test/golden/testObject_PublicSubConversation_v5_2.json b/libs/wire-api/test/golden/testObject_PublicSubConversation_v5_2.json index ac57e7e8e1b..a918c3161ba 100644 --- a/libs/wire-api/test/golden/testObject_PublicSubConversation_v5_2.json +++ b/libs/wire-api/test/golden/testObject_PublicSubConversation_v5_2.json @@ -1,6 +1,7 @@ { "cipher_suite": 1, "epoch": 0, + "epoch_timestamp": null, "group_id": "dGVzdF9ncm91cF8y", "members": [ { diff --git a/services/background-worker/test/Test/Wire/BackendNotificationPusherSpec.hs b/services/background-worker/test/Test/Wire/BackendNotificationPusherSpec.hs index 472f02d1f2e..dae725cf0ac 100644 --- a/services/background-worker/test/Test/Wire/BackendNotificationPusherSpec.hs +++ b/services/background-worker/test/Test/Wire/BackendNotificationPusherSpec.hs @@ -158,7 +158,7 @@ spec = do } runningFlag <- newMVar () (env, fedReqs) <- - withTempMockFederator def {versions = [0, 2]} . runTestAppT $ do + withTempMockFederator def {versions = [0, 999999]} . runTestAppT $ do wait =<< pushNotification runningFlag targetDomain (msg, envelope) ask diff --git a/services/brig/docs/swagger-v6.json b/services/brig/docs/swagger-v6.json new file mode 100644 index 00000000000..dda50318a99 --- /dev/null +++ b/services/brig/docs/swagger-v6.json @@ -0,0 +1,31394 @@ +{ + "components": { + "schemas": { + "": { + "enum": [ + "audio", + "books", + "business", + "design", + "education", + "entertainment", + "finance", + "fitness", + "food-drink", + "games", + "graphics", + "health", + "integration", + "lifestyle", + "media", + "medical", + "movies", + "music", + "news", + "photography", + "poll", + "productivity", + "quiz", + "rating", + "shopping", + "social", + "sports", + "travel", + "tutorial", + "video", + "weather" + ], + "type": "string" + }, + "ASCII": { + "example": "aGVsbG8", + "type": "string" + }, + "Access": { + "description": "How users can join conversations", + "enum": [ + "private", + "invite", + "link", + "code" + ], + "type": "string" + }, + "AccessRole": { + "description": "Which users/services can join conversations. This replaces legacy access roles and allows a more fine grained configuration of access roles, and in particular a separation of guest and services access.\n\nThis field is optional. If it is not present, the default will be `[team_member, non_team_member, service]`. Please note that an empty list is not allowed when creating a new conversation.", + "enum": [ + "team_member", + "non_team_member", + "guest", + "service" + ], + "type": "string" + }, + "AccessRoleLegacy": { + "deprecated": true, + "description": "Deprecated, please use access_role_v2", + "enum": [ + "private", + "team", + "activated", + "non_activated" + ], + "type": "string" + }, + "AccessToken": { + "properties": { + "access_token": { + "description": "The opaque access token string", + "type": "string" + }, + "expires_in": { + "description": "The number of seconds this token is valid", + "type": "integer" + }, + "token_type": { + "$ref": "#/components/schemas/TokenType" + }, + "user": { + "$ref": "#/components/schemas/UUID" + } + }, + "required": [ + "user", + "access_token", + "token_type", + "expires_in" + ], + "type": "object" + }, + "AccessTokenType": { + "enum": [ + "DPoP" + ], + "type": "string" + }, + "Action": { + "enum": [ + "add_conversation_member", + "remove_conversation_member", + "modify_conversation_name", + "modify_conversation_message_timer", + "modify_conversation_receipt_mode", + "modify_conversation_access", + "modify_other_conversation_member", + "leave_conversation", + "delete_conversation" + ], + "type": "string" + }, + "Activate": { + "description": "Data for an activation request.", + "properties": { + "code": { + "$ref": "#/components/schemas/ASCII" + }, + "dryrun": { + "description": "At least one of key, email, or phone has to be present while key takes precedence over email, and email takes precedence over phone. Whether to perform a dryrun, i.e. to only check whether activation would succeed. Dry-runs never issue access cookies or tokens on success but failures still count towards the maximum failure count.", + "type": "boolean" + }, + "email": { + "$ref": "#/components/schemas/Email" + }, + "key": { + "$ref": "#/components/schemas/ASCII" + }, + "phone": { + "$ref": "#/components/schemas/PhoneNumber" + } + }, + "required": [ + "code", + "dryrun" + ], + "type": "object" + }, + "ActivationResponse": { + "description": "Response body of a successful activation request", + "properties": { + "email": { + "$ref": "#/components/schemas/Email" + }, + "first": { + "description": "Whether this is the first successful activation (i.e. account activation).", + "type": "boolean" + }, + "phone": { + "$ref": "#/components/schemas/PhoneNumber" + }, + "sso_id": { + "$ref": "#/components/schemas/UserSSOId" + } + }, + "type": "object" + }, + "AddBot": { + "properties": { + "locale": { + "$ref": "#/components/schemas/Locale" + }, + "provider": { + "$ref": "#/components/schemas/UUID" + }, + "service": { + "$ref": "#/components/schemas/UUID" + } + }, + "required": [ + "provider", + "service" + ], + "type": "object" + }, + "AddBotResponse": { + "properties": { + "accent_id": { + "format": "int32", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + }, + "assets": { + "items": { + "$ref": "#/components/schemas/UserAsset" + }, + "type": "array" + }, + "client": { + "description": "A 64-bit unsigned integer, represented as a hexadecimal numeral. Any valid hexadecimal numeral is accepted, but the backend will only produce representations with lowercase digits and no leading zeros", + "type": "string" + }, + "event": { + "$ref": "#/components/schemas/Event" + }, + "id": { + "$ref": "#/components/schemas/UUID" + }, + "name": { + "maxLength": 128, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "id", + "client", + "name", + "accent_id", + "assets", + "event" + ], + "type": "object" + }, + "AllFeatureConfigs": { + "properties": { + "appLock": { + "$ref": "#/components/schemas/AppLockConfig.WithStatus" + }, + "classifiedDomains": { + "$ref": "#/components/schemas/ClassifiedDomainsConfig.WithStatus" + }, + "conferenceCalling": { + "$ref": "#/components/schemas/ConferenceCallingConfig.WithStatus" + }, + "conversationGuestLinks": { + "$ref": "#/components/schemas/GuestLinksConfig.WithStatus" + }, + "digitalSignatures": { + "$ref": "#/components/schemas/DigitalSignaturesConfig.WithStatus" + }, + "enforceFileDownloadLocation": { + "$ref": "#/components/schemas/EnforceFileDownloadLocation.WithStatus" + }, + "exposeInvitationURLsToTeamAdmin": { + "$ref": "#/components/schemas/ExposeInvitationURLsToTeamAdminConfig.WithStatus" + }, + "fileSharing": { + "$ref": "#/components/schemas/FileSharingConfig.WithStatus" + }, + "legalhold": { + "$ref": "#/components/schemas/LegalholdConfig.WithStatus" + }, + "limitedEventFanout": { + "$ref": "#/components/schemas/LimitedEventFanoutConfig.WithStatus" + }, + "mls": { + "$ref": "#/components/schemas/MLSConfig.WithStatus" + }, + "mlsE2EId": { + "$ref": "#/components/schemas/MlsE2EIdConfig.WithStatus" + }, + "mlsMigration": { + "$ref": "#/components/schemas/MlsMigration.WithStatus" + }, + "outlookCalIntegration": { + "$ref": "#/components/schemas/OutlookCalIntegrationConfig.WithStatus" + }, + "searchVisibility": { + "$ref": "#/components/schemas/SearchVisibilityAvailableConfig.WithStatus" + }, + "searchVisibilityInbound": { + "$ref": "#/components/schemas/SearchVisibilityInboundConfig.WithStatus" + }, + "selfDeletingMessages": { + "$ref": "#/components/schemas/SelfDeletingMessagesConfig.WithStatus" + }, + "sndFactorPasswordChallenge": { + "$ref": "#/components/schemas/SndFactorPasswordChallengeConfig.WithStatus" + }, + "sso": { + "$ref": "#/components/schemas/SSOConfig.WithStatus" + }, + "validateSAMLemails": { + "$ref": "#/components/schemas/ValidateSAMLEmailsConfig.WithStatus" + } + }, + "required": [ + "legalhold", + "sso", + "searchVisibility", + "searchVisibilityInbound", + "validateSAMLemails", + "digitalSignatures", + "appLock", + "fileSharing", + "classifiedDomains", + "conferenceCalling", + "selfDeletingMessages", + "conversationGuestLinks", + "sndFactorPasswordChallenge", + "mls", + "exposeInvitationURLsToTeamAdmin", + "outlookCalIntegration", + "mlsE2EId", + "mlsMigration", + "enforceFileDownloadLocation", + "limitedEventFanout" + ], + "type": "object" + }, + "Alpha": { + "enum": [ + "AED", + "AFN", + "ALL", + "AMD", + "ANG", + "AOA", + "ARS", + "AUD", + "AWG", + "AZN", + "BAM", + "BBD", + "BDT", + "BGN", + "BHD", + "BIF", + "BMD", + "BND", + "BOB", + "BOV", + "BRL", + "BSD", + "BTN", + "BWP", + "BYN", + "BZD", + "CAD", + "CDF", + "CHE", + "CHF", + "CHW", + "CLF", + "CLP", + "CNY", + "COP", + "COU", + "CRC", + "CUC", + "CUP", + "CVE", + "CZK", + "DJF", + "DKK", + "DOP", + "DZD", + "EGP", + "ERN", + "ETB", + "EUR", + "FJD", + "FKP", + "GBP", + "GEL", + "GHS", + "GIP", + "GMD", + "GNF", + "GTQ", + "GYD", + "HKD", + "HNL", + "HRK", + "HTG", + "HUF", + "IDR", + "ILS", + "INR", + "IQD", + "IRR", + "ISK", + "JMD", + "JOD", + "JPY", + "KES", + "KGS", + "KHR", + "KMF", + "KPW", + "KRW", + "KWD", + "KYD", + "KZT", + "LAK", + "LBP", + "LKR", + "LRD", + "LSL", + "LYD", + "MAD", + "MDL", + "MGA", + "MKD", + "MMK", + "MNT", + "MOP", + "MRO", + "MUR", + "MVR", + "MWK", + "MXN", + "MXV", + "MYR", + "MZN", + "NAD", + "NGN", + "NIO", + "NOK", + "NPR", + "NZD", + "OMR", + "PAB", + "PEN", + "PGK", + "PHP", + "PKR", + "PLN", + "PYG", + "QAR", + "RON", + "RSD", + "RUB", + "RWF", + "SAR", + "SBD", + "SCR", + "SDG", + "SEK", + "SGD", + "SHP", + "SLL", + "SOS", + "SRD", + "SSP", + "STD", + "SVC", + "SYP", + "SZL", + "THB", + "TJS", + "TMT", + "TND", + "TOP", + "TRY", + "TTD", + "TWD", + "TZS", + "UAH", + "UGX", + "USD", + "USN", + "UYI", + "UYU", + "UZS", + "VEF", + "VND", + "VUV", + "WST", + "XAF", + "XAG", + "XAU", + "XBA", + "XBB", + "XBC", + "XBD", + "XCD", + "XDR", + "XOF", + "XPD", + "XPF", + "XPT", + "XSU", + "XTS", + "XUA", + "XXX", + "YER", + "ZAR", + "ZMW", + "ZWL" + ], + "type": "string" + }, + "AppLockConfig": { + "properties": { + "enforceAppLock": { + "type": "boolean" + }, + "inactivityTimeoutSecs": { + "format": "int32", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + } + }, + "required": [ + "enforceAppLock", + "inactivityTimeoutSecs" + ], + "type": "object" + }, + "AppLockConfig.WithStatus": { + "properties": { + "config": { + "$ref": "#/components/schemas/AppLockConfig" + }, + "lockStatus": { + "$ref": "#/components/schemas/LockStatus" + }, + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status", + "lockStatus", + "config" + ], + "type": "object" + }, + "AppLockConfig.WithStatusNoLock": { + "properties": { + "config": { + "$ref": "#/components/schemas/AppLockConfig" + }, + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status", + "config" + ], + "type": "object" + }, + "ApproveLegalHoldForUserRequest": { + "properties": { + "password": { + "maxLength": 1024, + "minLength": 6, + "type": "string" + } + }, + "type": "object" + }, + "Asset": { + "properties": { + "domain": { + "$ref": "#/components/schemas/Domain" + }, + "expires": { + "$ref": "#/components/schemas/UTCTime" + }, + "key": { + "$ref": "#/components/schemas/AssetKey" + }, + "token": { + "$ref": "#/components/schemas/ASCII" + } + }, + "required": [ + "key", + "domain" + ], + "type": "object" + }, + "AssetKey": { + "example": "3-1-47de4580-ae51-4650-acbb-d10c028cb0ac", + "type": "string" + }, + "AssetSize": { + "enum": [ + "preview", + "complete" + ], + "type": "string" + }, + "AssetSource": {}, + "AssetType": { + "enum": [ + "image" + ], + "type": "string" + }, + "AuthnRequest": { + "properties": { + "iD": { + "$ref": "#/components/schemas/ID_*_AuthnRequest" + }, + "issueInstant": { + "$ref": "#/components/schemas/Time" + }, + "issuer": { + "type": "string" + }, + "nameIDPolicy": { + "$ref": "#/components/schemas/NameIdPolicy" + } + }, + "required": [ + "iD", + "issueInstant", + "issuer" + ], + "type": "object" + }, + "Base64ByteString": { + "example": "ZXhhbXBsZQo=", + "type": "string" + }, + "BaseProtocol": { + "enum": [ + "proteus", + "mls" + ], + "type": "string" + }, + "BindingNewTeamUser": { + "properties": { + "currency": { + "$ref": "#/components/schemas/Alpha" + }, + "icon": { + "$ref": "#/components/schemas/Icon" + }, + "icon_key": { + "description": "team icon asset key", + "maxLength": 256, + "minLength": 1, + "type": "string" + }, + "members": { + "description": "initial team member ids (between 1 and 127)" + }, + "name": { + "description": "team name", + "maxLength": 256, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "name", + "icon" + ], + "type": "object" + }, + "Body": {}, + "BotUserView": { + "properties": { + "accent_id": { + "format": "int32", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + }, + "handle": { + "$ref": "#/components/schemas/Handle" + }, + "id": { + "$ref": "#/components/schemas/UUID" + }, + "name": { + "maxLength": 128, + "minLength": 1, + "type": "string" + }, + "team": { + "$ref": "#/components/schemas/UUID" + } + }, + "required": [ + "id", + "name", + "accent_id" + ], + "type": "object" + }, + "CheckHandles": { + "properties": { + "handles": { + "items": { + "type": "string" + }, + "maxItems": 50, + "minItems": 1, + "type": "array" + }, + "return": { + "maximum": 10, + "minimum": 1, + "type": "integer" + } + }, + "required": [ + "handles", + "return" + ], + "type": "object" + }, + "CipherSuiteTag": { + "description": "The cipher suite of the corresponding MLS group", + "maximum": 65535, + "minimum": 0, + "type": "integer" + }, + "ClassifiedDomainsConfig": { + "properties": { + "domains": { + "items": { + "$ref": "#/components/schemas/Domain" + }, + "type": "array" + } + }, + "required": [ + "domains" + ], + "type": "object" + }, + "ClassifiedDomainsConfig.WithStatus": { + "properties": { + "config": { + "$ref": "#/components/schemas/ClassifiedDomainsConfig" + }, + "lockStatus": { + "$ref": "#/components/schemas/LockStatus" + }, + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status", + "lockStatus", + "config" + ], + "type": "object" + }, + "Client": { + "properties": { + "capabilities": { + "$ref": "#/components/schemas/ClientCapabilityList" + }, + "class": { + "$ref": "#/components/schemas/ClientClass" + }, + "cookie": { + "type": "string" + }, + "id": { + "description": "A 64-bit unsigned integer, represented as a hexadecimal numeral. Any valid hexadecimal numeral is accepted, but the backend will only produce representations with lowercase digits and no leading zeros", + "type": "string" + }, + "label": { + "type": "string" + }, + "last_active": { + "$ref": "#/components/schemas/UTCTime" + }, + "mls_public_keys": { + "$ref": "#/components/schemas/MLSPublicKeys" + }, + "model": { + "type": "string" + }, + "time": { + "$ref": "#/components/schemas/UTCTime" + }, + "type": { + "$ref": "#/components/schemas/ClientType" + } + }, + "required": [ + "id", + "type", + "time" + ], + "type": "object" + }, + "ClientCapability": { + "enum": [ + "legalhold-implicit-consent" + ], + "type": "string" + }, + "ClientCapabilityList": { + "properties": { + "capabilities": { + "description": "Hints provided by the client for the backend so it can behave in a backwards-compatible way.", + "items": { + "$ref": "#/components/schemas/ClientCapability" + }, + "type": "array" + } + }, + "required": [ + "capabilities" + ], + "type": "object" + }, + "ClientClass": { + "enum": [ + "phone", + "tablet", + "desktop", + "legalhold" + ], + "type": "string" + }, + "ClientIdentity": { + "properties": { + "client_id": { + "description": "A 64-bit unsigned integer, represented as a hexadecimal numeral. Any valid hexadecimal numeral is accepted, but the backend will only produce representations with lowercase digits and no leading zeros", + "type": "string" + }, + "domain": { + "$ref": "#/components/schemas/Domain" + }, + "user_id": { + "$ref": "#/components/schemas/UUID" + } + }, + "required": [ + "domain", + "user_id", + "client_id" + ], + "type": "object" + }, + "ClientMismatch": { + "properties": { + "deleted": { + "$ref": "#/components/schemas/UserClients" + }, + "missing": { + "$ref": "#/components/schemas/UserClients" + }, + "redundant": { + "$ref": "#/components/schemas/UserClients" + }, + "time": { + "$ref": "#/components/schemas/UTCTime" + } + }, + "required": [ + "time", + "missing", + "redundant", + "deleted" + ], + "type": "object" + }, + "ClientPrekey": { + "properties": { + "client": { + "description": "A 64-bit unsigned integer, represented as a hexadecimal numeral. Any valid hexadecimal numeral is accepted, but the backend will only produce representations with lowercase digits and no leading zeros", + "type": "string" + }, + "prekey": { + "$ref": "#/components/schemas/Prekey" + } + }, + "required": [ + "client", + "prekey" + ], + "type": "object" + }, + "ClientType": { + "enum": [ + "temporary", + "permanent", + "legalhold" + ], + "type": "string" + }, + "CodeChallengeMethod": { + "description": "The method used to encode the code challenge. Only `S256` is supported.", + "enum": [ + "S256" + ], + "type": "string" + }, + "CommitBundle": { + "description": "This object can only be parsed in TLS format. Please refer to the MLS specification for details." + }, + "CompletePasswordReset": { + "properties": { + "code": { + "$ref": "#/components/schemas/ASCII" + }, + "key": { + "$ref": "#/components/schemas/ASCII" + }, + "password": { + "maxLength": 1024, + "minLength": 6, + "type": "string" + } + }, + "required": [ + "key", + "code", + "password" + ], + "type": "object" + }, + "ConferenceCallingConfig": { + "properties": { + "useSFTForOneToOneCalls": { + "type": "boolean" + } + }, + "type": "object" + }, + "ConferenceCallingConfig.WithStatus": { + "properties": { + "config": { + "$ref": "#/components/schemas/ConferenceCallingConfig" + }, + "lockStatus": { + "$ref": "#/components/schemas/LockStatus" + }, + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status", + "lockStatus" + ], + "type": "object" + }, + "ConferenceCallingConfig.WithStatusNoLock": { + "properties": { + "config": { + "$ref": "#/components/schemas/ConferenceCallingConfig" + }, + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "Connect": { + "properties": { + "email": { + "type": "string" + }, + "message": { + "type": "string" + }, + "name": { + "type": "string" + }, + "qualified_recipient": { + "$ref": "#/components/schemas/Qualified_UserId" + }, + "recipient": { + "$ref": "#/components/schemas/UUID" + } + }, + "required": [ + "qualified_recipient" + ], + "type": "object" + }, + "ConnectionUpdate": { + "properties": { + "status": { + "$ref": "#/components/schemas/Relation" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "Connections_Page": { + "properties": { + "connections": { + "items": { + "$ref": "#/components/schemas/UserConnection" + }, + "type": "array" + }, + "has_more": { + "type": "boolean" + }, + "paging_state": { + "$ref": "#/components/schemas/Connections_PagingState" + } + }, + "required": [ + "connections", + "has_more", + "paging_state" + ], + "type": "object" + }, + "Connections_PagingState": { + "type": "string" + }, + "Contact": { + "description": "Contact discovered through search", + "properties": { + "accent_id": { + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + }, + "handle": { + "type": "string" + }, + "id": { + "$ref": "#/components/schemas/UUID" + }, + "name": { + "type": "string" + }, + "qualified_id": { + "$ref": "#/components/schemas/Qualified_UserId" + }, + "team": { + "$ref": "#/components/schemas/UUID" + } + }, + "required": [ + "qualified_id", + "name" + ], + "type": "object" + }, + "ConvMembers": { + "description": "Users of a conversation", + "properties": { + "others": { + "description": "All other current users of this conversation", + "items": { + "$ref": "#/components/schemas/OtherMember" + }, + "type": "array" + }, + "self": { + "$ref": "#/components/schemas/Member" + } + }, + "required": [ + "self", + "others" + ], + "type": "object" + }, + "ConvTeamInfo": { + "description": "Team information of this conversation", + "properties": { + "managed": { + "description": "This field MUST NOT be used by clients. It is here only for backwards compatibility of the interface." + }, + "teamid": { + "$ref": "#/components/schemas/UUID" + } + }, + "required": [ + "teamid", + "managed" + ], + "type": "object" + }, + "ConvType": { + "enum": [ + 0, + 1, + 2, + 3 + ], + "type": "integer" + }, + "Conversation": { + "description": "A conversation object as returned from the server", + "properties": { + "access": { + "items": { + "$ref": "#/components/schemas/Access" + }, + "type": "array" + }, + "access_role": { + "items": { + "$ref": "#/components/schemas/AccessRole" + }, + "type": "array" + }, + "cipher_suite": { + "$ref": "#/components/schemas/CipherSuiteTag" + }, + "creator": { + "$ref": "#/components/schemas/UUID" + }, + "epoch": { + "description": "The epoch number of the corresponding MLS group", + "format": "int64", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + }, + "epoch_timestamp": { + "$ref": "#/components/schemas/UTCTime" + }, + "group_id": { + "$ref": "#/components/schemas/GroupId" + }, + "id": { + "$ref": "#/components/schemas/UUID" + }, + "last_event": { + "type": "string" + }, + "last_event_time": { + "type": "string" + }, + "members": { + "$ref": "#/components/schemas/ConvMembers" + }, + "message_timer": { + "description": "Per-conversation message timer (can be null)", + "format": "int64", + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + }, + "name": { + "type": "string" + }, + "protocol": { + "$ref": "#/components/schemas/Protocol" + }, + "qualified_id": { + "$ref": "#/components/schemas/Qualified_ConvId" + }, + "receipt_mode": { + "description": "Conversation receipt mode", + "format": "int32", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + }, + "team": { + "$ref": "#/components/schemas/UUID" + }, + "type": { + "$ref": "#/components/schemas/ConvType" + } + }, + "required": [ + "qualified_id", + "type", + "access", + "access_role", + "members", + "group_id", + "epoch" + ], + "type": "object" + }, + "ConversationAccessData": { + "properties": { + "access": { + "items": { + "$ref": "#/components/schemas/Access" + }, + "type": "array" + }, + "access_role": { + "items": { + "$ref": "#/components/schemas/AccessRole" + }, + "type": "array" + } + }, + "required": [ + "access", + "access_role" + ], + "type": "object" + }, + "ConversationAccessDataV2": { + "properties": { + "access": { + "items": { + "$ref": "#/components/schemas/Access" + }, + "type": "array" + }, + "access_role": { + "$ref": "#/components/schemas/AccessRoleLegacy" + }, + "access_role_v2": { + "items": { + "$ref": "#/components/schemas/AccessRole" + }, + "type": "array" + } + }, + "required": [ + "access" + ], + "type": "object" + }, + "ConversationCode": { + "description": "Contains conversation properties to update", + "properties": { + "code": { + "$ref": "#/components/schemas/ASCII" + }, + "key": { + "$ref": "#/components/schemas/ASCII" + }, + "uri": { + "$ref": "#/components/schemas/HttpsUrl" + } + }, + "required": [ + "key", + "code" + ], + "type": "object" + }, + "ConversationCodeInfo": { + "description": "Contains conversation properties to update", + "properties": { + "code": { + "$ref": "#/components/schemas/ASCII" + }, + "has_password": { + "description": "Whether the conversation has a password", + "type": "boolean" + }, + "key": { + "$ref": "#/components/schemas/ASCII" + }, + "uri": { + "$ref": "#/components/schemas/HttpsUrl" + } + }, + "required": [ + "key", + "code", + "has_password" + ], + "type": "object" + }, + "ConversationCoverView": { + "description": "Limited view of Conversation.", + "properties": { + "has_password": { + "type": "boolean" + }, + "id": { + "$ref": "#/components/schemas/UUID" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "has_password" + ], + "type": "object" + }, + "ConversationIds_Page": { + "properties": { + "has_more": { + "type": "boolean" + }, + "paging_state": { + "$ref": "#/components/schemas/ConversationIds_PagingState" + }, + "qualified_conversations": { + "items": { + "$ref": "#/components/schemas/Qualified_ConvId" + }, + "type": "array" + } + }, + "required": [ + "qualified_conversations", + "has_more", + "paging_state" + ], + "type": "object" + }, + "ConversationIds_PagingState": { + "type": "string" + }, + "ConversationMessageTimerUpdate": { + "description": "Contains conversation properties to update", + "properties": { + "message_timer": { + "format": "int64", + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + } + }, + "type": "object" + }, + "ConversationReceiptModeUpdate": { + "description": "Contains conversation receipt mode to update to. Receipt mode tells clients whether certain types of receipts should be sent in the given conversation or not. How this value is interpreted is up to clients.", + "properties": { + "receipt_mode": { + "description": "Conversation receipt mode", + "format": "int32", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + } + }, + "required": [ + "receipt_mode" + ], + "type": "object" + }, + "ConversationRename": { + "properties": { + "name": { + "description": "The new conversation name", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "ConversationRole": { + "properties": { + "actions": { + "description": "The set of actions allowed for this role", + "items": { + "$ref": "#/components/schemas/Action" + }, + "type": "array" + }, + "conversation_role": { + "$ref": "#/components/schemas/RoleName" + } + } + }, + "ConversationRolesList": { + "properties": { + "conversation_roles": { + "items": { + "$ref": "#/components/schemas/ConversationRole" + }, + "type": "array" + } + }, + "required": [ + "conversation_roles" + ], + "type": "object" + }, + "ConversationV2": { + "description": "A conversation object as returned from the server", + "properties": { + "access": { + "items": { + "$ref": "#/components/schemas/Access" + }, + "type": "array" + }, + "access_role": { + "$ref": "#/components/schemas/AccessRoleLegacy" + }, + "access_role_v2": { + "items": { + "$ref": "#/components/schemas/AccessRole" + }, + "type": "array" + }, + "cipher_suite": { + "$ref": "#/components/schemas/CipherSuiteTag" + }, + "creator": { + "$ref": "#/components/schemas/UUID" + }, + "epoch": { + "description": "The epoch number of the corresponding MLS group", + "format": "int64", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + }, + "epoch_timestamp": { + "$ref": "#/components/schemas/Epoch Timestamp" + }, + "group_id": { + "$ref": "#/components/schemas/GroupId" + }, + "id": { + "$ref": "#/components/schemas/UUID" + }, + "last_event": { + "type": "string" + }, + "last_event_time": { + "type": "string" + }, + "members": { + "$ref": "#/components/schemas/ConvMembers" + }, + "message_timer": { + "description": "Per-conversation message timer (can be null)", + "format": "int64", + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + }, + "name": { + "type": "string" + }, + "protocol": { + "$ref": "#/components/schemas/Protocol" + }, + "qualified_id": { + "$ref": "#/components/schemas/Qualified_ConvId" + }, + "receipt_mode": { + "description": "Conversation receipt mode", + "format": "int32", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + }, + "team": { + "$ref": "#/components/schemas/UUID" + }, + "type": { + "$ref": "#/components/schemas/ConvType" + } + }, + "required": [ + "qualified_id", + "type", + "access", + "members", + "group_id", + "epoch", + "epoch_timestamp", + "cipher_suite" + ], + "type": "object" + }, + "ConversationsResponse": { + "description": "Response object for getting metadata of a list of conversations", + "properties": { + "failed": { + "description": "The server failed to fetch these conversations, most likely due to network issues while contacting a remote server", + "items": { + "$ref": "#/components/schemas/Qualified_ConvId" + }, + "type": "array" + }, + "found": { + "items": { + "$ref": "#/components/schemas/Conversation" + }, + "type": "array" + }, + "not_found": { + "description": "These conversations either don't exist or are deleted.", + "items": { + "$ref": "#/components/schemas/Qualified_ConvId" + }, + "type": "array" + } + }, + "required": [ + "found", + "not_found", + "failed" + ], + "type": "object" + }, + "Cookie": { + "properties": { + "created": { + "$ref": "#/components/schemas/UTCTime" + }, + "expires": { + "$ref": "#/components/schemas/UTCTime" + }, + "id": { + "format": "int32", + "maximum": 4294967295, + "minimum": 0, + "type": "integer" + }, + "label": { + "type": "string" + }, + "successor": { + "format": "int32", + "maximum": 4294967295, + "minimum": 0, + "type": "integer" + }, + "type": { + "$ref": "#/components/schemas/CookieType" + } + }, + "required": [ + "id", + "type", + "created", + "expires" + ], + "type": "object" + }, + "CookieList": { + "description": "List of cookie information", + "properties": { + "cookies": { + "items": { + "$ref": "#/components/schemas/Cookie" + }, + "type": "array" + } + }, + "required": [ + "cookies" + ], + "type": "object" + }, + "CookieType": { + "enum": [ + "session", + "persistent" + ], + "type": "string" + }, + "CreateConversationCodeRequest": { + "description": "Request body for creating a conversation code", + "properties": { + "password": { + "description": "Password for accessing the conversation via guest link. Set to null or omit for no password.", + "maxLength": 1024, + "minLength": 8, + "type": "string" + } + }, + "type": "object" + }, + "CreateGroupConversation": { + "description": "A created group-conversation object extended with a list of failed-to-add users", + "properties": { + "access": { + "items": { + "$ref": "#/components/schemas/Access" + }, + "type": "array" + }, + "access_role": { + "items": { + "$ref": "#/components/schemas/AccessRole" + }, + "type": "array" + }, + "cipher_suite": { + "$ref": "#/components/schemas/CipherSuiteTag" + }, + "creator": { + "$ref": "#/components/schemas/UUID" + }, + "epoch": { + "description": "The epoch number of the corresponding MLS group", + "format": "int64", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + }, + "epoch_timestamp": { + "$ref": "#/components/schemas/UTCTime" + }, + "failed_to_add": { + "items": { + "$ref": "#/components/schemas/Qualified_UserId" + }, + "type": "array" + }, + "group_id": { + "$ref": "#/components/schemas/GroupId" + }, + "id": { + "$ref": "#/components/schemas/UUID" + }, + "last_event": { + "type": "string" + }, + "last_event_time": { + "type": "string" + }, + "members": { + "$ref": "#/components/schemas/ConvMembers" + }, + "message_timer": { + "description": "Per-conversation message timer (can be null)", + "format": "int64", + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + }, + "name": { + "type": "string" + }, + "protocol": { + "$ref": "#/components/schemas/Protocol" + }, + "qualified_id": { + "$ref": "#/components/schemas/Qualified_ConvId" + }, + "receipt_mode": { + "description": "Conversation receipt mode", + "format": "int32", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + }, + "team": { + "$ref": "#/components/schemas/UUID" + }, + "type": { + "$ref": "#/components/schemas/ConvType" + } + }, + "required": [ + "qualified_id", + "type", + "access", + "access_role", + "members", + "group_id", + "epoch", + "failed_to_add" + ], + "type": "object" + }, + "CreateOAuthAuthorizationCodeRequest": { + "properties": { + "client_id": { + "$ref": "#/components/schemas/UUID" + }, + "code_challenge": { + "$ref": "#/components/schemas/OAuthCodeChallenge" + }, + "code_challenge_method": { + "$ref": "#/components/schemas/CodeChallengeMethod" + }, + "redirect_uri": { + "$ref": "#/components/schemas/RedirectUrl" + }, + "response_type": { + "$ref": "#/components/schemas/OAuthResponseType" + }, + "scope": { + "description": "The scopes which are requested to get authorization for, separated by a space", + "type": "string" + }, + "state": { + "description": "An opaque value used by the client to maintain state between the request and callback. The authorization server includes this value when redirecting the user-agent back to the client. The parameter SHOULD be used for preventing cross-site request forgery", + "type": "string" + } + }, + "required": [ + "client_id", + "scope", + "response_type", + "redirect_uri", + "state", + "code_challenge_method", + "code_challenge" + ], + "type": "object" + }, + "CreateScimToken": { + "properties": { + "description": { + "type": "string" + }, + "password": { + "type": "string" + }, + "verification_code": { + "type": "string" + } + }, + "required": [ + "description" + ], + "type": "object" + }, + "CreateScimTokenResponse": { + "properties": { + "info": { + "$ref": "#/components/schemas/ScimTokenInfo" + }, + "token": { + "description": "Authentication token", + "type": "string" + } + }, + "required": [ + "token", + "info" + ], + "type": "object" + }, + "CustomBackend": { + "description": "Description of a custom backend", + "properties": { + "config_json_url": { + "$ref": "#/components/schemas/HttpsUrl" + }, + "webapp_welcome_url": { + "$ref": "#/components/schemas/HttpsUrl" + } + }, + "required": [ + "config_json_url", + "webapp_welcome_url" + ], + "type": "object" + }, + "DPoPAccessToken": { + "type": "string" + }, + "DPoPAccessTokenResponse": { + "properties": { + "expires_in": { + "format": "int64", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + }, + "token": { + "$ref": "#/components/schemas/DPoPAccessToken" + }, + "type": { + "$ref": "#/components/schemas/AccessTokenType" + } + }, + "required": [ + "token", + "type", + "expires_in" + ], + "type": "object" + }, + "DeleteClient": { + "properties": { + "password": { + "description": "The password of the authenticated user for verification. The password is not required for deleting temporary clients.", + "maxLength": 1024, + "minLength": 6, + "type": "string" + } + }, + "type": "object" + }, + "DeleteKeyPackages": { + "properties": { + "key_packages": { + "items": { + "$ref": "#/components/schemas/KeyPackageRef" + }, + "maxItems": 1000, + "minItems": 1, + "type": "array" + } + }, + "required": [ + "key_packages" + ], + "type": "object" + }, + "DeleteProvider": { + "properties": { + "password": { + "maxLength": 1024, + "minLength": 6, + "type": "string" + } + }, + "required": [ + "password" + ], + "type": "object" + }, + "DeleteService": { + "properties": { + "password": { + "maxLength": 1024, + "minLength": 6, + "type": "string" + } + }, + "required": [ + "password" + ], + "type": "object" + }, + "DeleteSubConversationRequest": { + "description": "Delete an MLS subconversation", + "properties": { + "epoch": { + "format": "int64", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + }, + "group_id": { + "$ref": "#/components/schemas/GroupId" + } + }, + "required": [ + "group_id", + "epoch" + ], + "type": "object" + }, + "DeleteUser": { + "properties": { + "password": { + "maxLength": 1024, + "minLength": 6, + "type": "string" + } + }, + "type": "object" + }, + "DeletionCodeTimeout": { + "properties": { + "expires_in": { + "format": "int32", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + } + }, + "required": [ + "expires_in" + ], + "type": "object" + }, + "DeprecatedMatchingResult": { + "deprecated": true, + "properties": { + "auto-connects": { + "items": {}, + "type": "array" + }, + "results": { + "items": {}, + "type": "array" + } + }, + "required": [ + "results", + "auto-connects" + ], + "type": "object" + }, + "DigitalSignaturesConfig.WithStatus": { + "properties": { + "lockStatus": { + "$ref": "#/components/schemas/LockStatus" + }, + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status", + "lockStatus" + ], + "type": "object" + }, + "DisableLegalHoldForUserRequest": { + "properties": { + "password": { + "maxLength": 1024, + "minLength": 6, + "type": "string" + } + }, + "type": "object" + }, + "Domain": { + "example": "example.com", + "type": "string" + }, + "EdMemberLeftReason": { + "enum": [ + "left", + "user-deleted", + "removed" + ], + "type": "string" + }, + "Either_OAuthAccessTokenRequest_OAuthRefreshAccessTokenRequest": { + "oneOf": [ + { + "properties": { + "Left": { + "$ref": "#/components/schemas/OAuthAccessTokenRequest" + } + }, + "required": [ + "Left" + ], + "type": "object" + }, + { + "properties": { + "Right": { + "$ref": "#/components/schemas/OAuthRefreshAccessTokenRequest" + } + }, + "required": [ + "Right" + ], + "type": "object" + } + ] + }, + "Email": { + "type": "string" + }, + "EmailUpdate": { + "properties": { + "email": { + "$ref": "#/components/schemas/Email" + } + }, + "required": [ + "email" + ], + "type": "object" + }, + "EnforceFileDownloadLocation": { + "properties": { + "enforcedDownloadLocation": { + "type": "string" + } + }, + "type": "object" + }, + "EnforceFileDownloadLocation.WithStatus": { + "properties": { + "config": { + "$ref": "#/components/schemas/EnforceFileDownloadLocation" + }, + "lockStatus": { + "$ref": "#/components/schemas/LockStatus" + }, + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status", + "lockStatus", + "config" + ], + "type": "object" + }, + "EnforceFileDownloadLocation.WithStatusNoLock": { + "properties": { + "config": { + "$ref": "#/components/schemas/EnforceFileDownloadLocation" + }, + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status", + "config" + ], + "type": "object" + }, + "Epoch Timestamp": { + "example": "2021-05-12T10:52:02.671Z", + "format": "yyyy-mm-ddThh:MM:ss.qqq", + "type": "string" + }, + "Event": { + "properties": { + "conversation": { + "$ref": "#/components/schemas/UUID" + }, + "data": { + "description": "Encrypted message of a conversation", + "example": "ZXhhbXBsZQo=", + "properties": { + "access": { + "items": { + "$ref": "#/components/schemas/Access" + }, + "type": "array" + }, + "access_role": { + "$ref": "#/components/schemas/AccessRoleLegacy" + }, + "access_role_v2": { + "items": { + "$ref": "#/components/schemas/AccessRole" + }, + "type": "array" + }, + "cipher_suite": { + "$ref": "#/components/schemas/CipherSuiteTag" + }, + "code": { + "$ref": "#/components/schemas/ASCII" + }, + "conversation_role": { + "$ref": "#/components/schemas/RoleName" + }, + "creator": { + "$ref": "#/components/schemas/UUID" + }, + "data": { + "description": "Extra (symmetric) data (i.e. ciphertext, Base64 in JSON) that is common with all other recipients.", + "type": "string" + }, + "email": { + "type": "string" + }, + "epoch": { + "description": "The epoch number of the corresponding MLS group", + "format": "int64", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + }, + "epoch_timestamp": { + "$ref": "#/components/schemas/Epoch Timestamp" + }, + "group_id": { + "$ref": "#/components/schemas/GroupId" + }, + "has_password": { + "description": "Whether the conversation has a password", + "type": "boolean" + }, + "hidden": { + "type": "boolean" + }, + "hidden_ref": { + "type": "string" + }, + "id": { + "$ref": "#/components/schemas/UUID" + }, + "key": { + "$ref": "#/components/schemas/ASCII" + }, + "last_event": { + "type": "string" + }, + "last_event_time": { + "type": "string" + }, + "members": { + "$ref": "#/components/schemas/ConvMembers" + }, + "message": { + "type": "string" + }, + "message_timer": { + "description": "Per-conversation message timer (can be null)", + "format": "int64", + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + }, + "name": { + "type": "string" + }, + "otr_archived": { + "type": "boolean" + }, + "otr_archived_ref": { + "type": "string" + }, + "otr_muted_ref": { + "type": "string" + }, + "otr_muted_status": { + "format": "int32", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + }, + "protocol": { + "$ref": "#/components/schemas/Protocol" + }, + "qualified_id": { + "$ref": "#/components/schemas/Qualified_ConvId" + }, + "qualified_recipient": { + "$ref": "#/components/schemas/Qualified_UserId" + }, + "qualified_target": { + "$ref": "#/components/schemas/Qualified_UserId" + }, + "qualified_user_ids": { + "items": { + "$ref": "#/components/schemas/Qualified_UserId" + }, + "type": "array" + }, + "reason": { + "$ref": "#/components/schemas/EdMemberLeftReason" + }, + "receipt_mode": { + "description": "Conversation receipt mode", + "format": "int32", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + }, + "recipient": { + "description": "A 64-bit unsigned integer, represented as a hexadecimal numeral. Any valid hexadecimal numeral is accepted, but the backend will only produce representations with lowercase digits and no leading zeros", + "type": "string" + }, + "sender": { + "description": "A 64-bit unsigned integer, represented as a hexadecimal numeral. Any valid hexadecimal numeral is accepted, but the backend will only produce representations with lowercase digits and no leading zeros", + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/TypingStatus" + }, + "target": { + "$ref": "#/components/schemas/UUID" + }, + "team": { + "$ref": "#/components/schemas/UUID" + }, + "text": { + "description": "The ciphertext for the recipient (Base64 in JSON)", + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/ConvType" + }, + "uri": { + "$ref": "#/components/schemas/HttpsUrl" + }, + "user_ids": { + "deprecated": true, + "description": "Deprecated, use qualified_user_ids", + "items": { + "$ref": "#/components/schemas/UUID" + }, + "type": "array" + }, + "users": { + "items": { + "$ref": "#/components/schemas/SimpleMember" + }, + "type": "array" + } + }, + "required": [ + "users", + "reason", + "qualified_user_ids", + "user_ids", + "qualified_target", + "name", + "access", + "key", + "code", + "has_password", + "qualified_id", + "type", + "members", + "group_id", + "epoch", + "epoch_timestamp", + "cipher_suite", + "qualified_recipient", + "receipt_mode", + "sender", + "recipient", + "text", + "status" + ], + "type": "object" + }, + "from": { + "$ref": "#/components/schemas/UUID" + }, + "qualified_conversation": { + "$ref": "#/components/schemas/Qualified_ConvId" + }, + "qualified_from": { + "$ref": "#/components/schemas/Qualified_UserId" + }, + "subconv": { + "type": "string" + }, + "time": { + "$ref": "#/components/schemas/UTCTime" + }, + "type": { + "$ref": "#/components/schemas/EventType" + } + }, + "required": [ + "type", + "data", + "qualified_conversation", + "qualified_from", + "time" + ], + "type": "object" + }, + "EventType": { + "enum": [ + "conversation.member-join", + "conversation.member-leave", + "conversation.member-update", + "conversation.rename", + "conversation.access-update", + "conversation.receipt-mode-update", + "conversation.message-timer-update", + "conversation.code-update", + "conversation.code-delete", + "conversation.create", + "conversation.delete", + "conversation.connect-request", + "conversation.typing", + "conversation.otr-message-add", + "conversation.mls-message-add", + "conversation.mls-welcome", + "conversation.protocol-update" + ], + "type": "string" + }, + "ExposeInvitationURLsToTeamAdminConfig.WithStatus": { + "properties": { + "lockStatus": { + "$ref": "#/components/schemas/LockStatus" + }, + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status", + "lockStatus" + ], + "type": "object" + }, + "ExposeInvitationURLsToTeamAdminConfig.WithStatusNoLock": { + "properties": { + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "FeatureStatus": { + "enum": [ + "enabled", + "disabled" + ], + "type": "string" + }, + "FederatedUserSearchPolicy": { + "description": "Search policy that was applied when searching for users", + "enum": [ + "no_search", + "exact_handle_search", + "full_search" + ], + "type": "string" + }, + "FileSharingConfig.WithStatus": { + "properties": { + "lockStatus": { + "$ref": "#/components/schemas/LockStatus" + }, + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status", + "lockStatus" + ], + "type": "object" + }, + "FileSharingConfig.WithStatusNoLock": { + "properties": { + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "Fingerprint": { + "example": "ioy3GeIjgQRsobf2EKGO3O8mq/FofFxHRqy0T4ERIZ8=", + "type": "string" + }, + "FormRedirect": { + "properties": { + "uri": { + "type": "string" + }, + "xml": { + "$ref": "#/components/schemas/AuthnRequest" + } + }, + "type": "object" + }, + "GetPaginated_Connections": { + "description": "A request to list some or all of a user's Connections, including remote ones", + "properties": { + "paging_state": { + "$ref": "#/components/schemas/Connections_PagingState" + }, + "size": { + "description": "optional, must be <= 500, defaults to 100.", + "format": "int32", + "maximum": 500, + "minimum": 1, + "type": "integer" + } + }, + "type": "object" + }, + "GetPaginated_ConversationIds": { + "description": "A request to list some or all of a user's ConversationIds, including remote ones", + "properties": { + "paging_state": { + "$ref": "#/components/schemas/ConversationIds_PagingState" + }, + "size": { + "description": "optional, must be <= 1000, defaults to 1000.", + "format": "int32", + "maximum": 1000, + "minimum": 1, + "type": "integer" + } + }, + "type": "object" + }, + "GroupId": { + "description": "A base64-encoded MLS group ID", + "example": "ZXhhbXBsZQo=", + "type": "string" + }, + "GroupInfoData": { + "description": "This object can only be parsed in TLS format. Please refer to the MLS specification for details." + }, + "GuestLinksConfig.WithStatus": { + "properties": { + "lockStatus": { + "$ref": "#/components/schemas/LockStatus" + }, + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status", + "lockStatus" + ], + "type": "object" + }, + "GuestLinksConfig.WithStatusNoLock": { + "properties": { + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "Handle": { + "type": "string" + }, + "HandleUpdate": { + "properties": { + "handle": { + "type": "string" + } + }, + "required": [ + "handle" + ], + "type": "object" + }, + "HttpsUrl": { + "example": "https://example.com", + "type": "string" + }, + "ID_*_AuthnRequest": { + "properties": { + "iD": { + "$ref": "#/components/schemas/XmlText" + } + }, + "required": [ + "iD" + ], + "type": "object" + }, + "Icon": { + "type": "string" + }, + "Id": { + "properties": { + "id": { + "description": "A 64-bit unsigned integer, represented as a hexadecimal numeral. Any valid hexadecimal numeral is accepted, but the backend will only produce representations with lowercase digits and no leading zeros", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "IdPConfig_WireIdP": { + "properties": { + "extraInfo": { + "$ref": "#/components/schemas/WireIdP" + }, + "id": { + "$ref": "#/components/schemas/UUID" + }, + "metadata": { + "$ref": "#/components/schemas/IdPMetadata" + } + }, + "required": [ + "id", + "metadata", + "extraInfo" + ], + "type": "object" + }, + "IdPList": { + "properties": { + "providers": { + "items": { + "$ref": "#/components/schemas/IdPConfig_WireIdP" + }, + "type": "array" + } + }, + "required": [ + "providers" + ], + "type": "object" + }, + "IdPMetadata": { + "properties": { + "certAuthnResponse": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + }, + "issuer": { + "type": "string" + }, + "requestURI": { + "type": "string" + } + }, + "required": [ + "issuer", + "requestURI", + "certAuthnResponse" + ], + "type": "object" + }, + "IdPMetadataInfo": { + "maxProperties": 1, + "minProperties": 1, + "properties": { + "value": { + "type": "string" + } + }, + "type": "object" + }, + "Invitation": { + "description": "An invitation to join a team on Wire", + "properties": { + "created_at": { + "$ref": "#/components/schemas/UTCTime" + }, + "created_by": { + "$ref": "#/components/schemas/UUID" + }, + "email": { + "$ref": "#/components/schemas/Email" + }, + "id": { + "$ref": "#/components/schemas/UUID" + }, + "name": { + "description": "Name of the invitee (1 - 128 characters)", + "maxLength": 128, + "minLength": 1, + "type": "string" + }, + "phone": { + "$ref": "#/components/schemas/PhoneNumber" + }, + "role": { + "$ref": "#/components/schemas/Role" + }, + "team": { + "$ref": "#/components/schemas/UUID" + }, + "url": { + "$ref": "#/components/schemas/URIRef Absolute" + } + }, + "required": [ + "team", + "id", + "created_at", + "email" + ], + "type": "object" + }, + "InvitationList": { + "description": "A list of sent team invitations.", + "properties": { + "has_more": { + "description": "Indicator that the server has more invitations than returned.", + "type": "boolean" + }, + "invitations": { + "items": { + "$ref": "#/components/schemas/Invitation" + }, + "type": "array" + } + }, + "required": [ + "invitations", + "has_more" + ], + "type": "object" + }, + "InvitationRequest": { + "description": "A request to join a team on Wire.", + "properties": { + "email": { + "$ref": "#/components/schemas/Email" + }, + "locale": { + "$ref": "#/components/schemas/Locale" + }, + "name": { + "description": "Name of the invitee (1 - 128 characters).", + "maxLength": 128, + "minLength": 1, + "type": "string" + }, + "phone": { + "$ref": "#/components/schemas/PhoneNumber" + }, + "role": { + "$ref": "#/components/schemas/Role" + } + }, + "required": [ + "email" + ], + "type": "object" + }, + "InviteQualified": { + "properties": { + "conversation_role": { + "$ref": "#/components/schemas/RoleName" + }, + "qualified_users": { + "items": { + "$ref": "#/components/schemas/Qualified_UserId" + }, + "minItems": 1, + "type": "array" + } + }, + "required": [ + "qualified_users" + ], + "type": "object" + }, + "JoinConversationByCode": { + "description": "Request body for joining a conversation by code", + "properties": { + "code": { + "$ref": "#/components/schemas/ASCII" + }, + "key": { + "$ref": "#/components/schemas/ASCII" + }, + "password": { + "maxLength": 1024, + "minLength": 8, + "type": "string" + }, + "uri": { + "$ref": "#/components/schemas/HttpsUrl" + } + }, + "required": [ + "key", + "code" + ], + "type": "object" + }, + "KeyPackage": { + "example": "a2V5IHBhY2thZ2UgZGF0YQo=", + "type": "string" + }, + "KeyPackageBundle": { + "properties": { + "key_packages": { + "items": { + "$ref": "#/components/schemas/KeyPackageBundleEntry" + }, + "type": "array" + } + }, + "required": [ + "key_packages" + ], + "type": "object" + }, + "KeyPackageBundleEntry": { + "properties": { + "client": { + "description": "A 64-bit unsigned integer, represented as a hexadecimal numeral. Any valid hexadecimal numeral is accepted, but the backend will only produce representations with lowercase digits and no leading zeros", + "type": "string" + }, + "domain": { + "$ref": "#/components/schemas/Domain" + }, + "key_package": { + "$ref": "#/components/schemas/KeyPackage" + }, + "key_package_ref": { + "$ref": "#/components/schemas/KeyPackageRef" + }, + "user": { + "$ref": "#/components/schemas/UUID" + } + }, + "required": [ + "domain", + "user", + "client", + "key_package_ref", + "key_package" + ], + "type": "object" + }, + "KeyPackageRef": { + "example": "ZXhhbXBsZQo=", + "type": "string" + }, + "KeyPackageUpload": { + "properties": { + "key_packages": { + "items": { + "$ref": "#/components/schemas/KeyPackage" + }, + "type": "array" + } + }, + "required": [ + "key_packages" + ], + "type": "object" + }, + "LHServiceStatus": { + "enum": [ + "configured", + "not_configured", + "disabled" + ], + "type": "string" + }, + "LegalholdConfig.WithStatus": { + "properties": { + "lockStatus": { + "$ref": "#/components/schemas/LockStatus" + }, + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status", + "lockStatus" + ], + "type": "object" + }, + "LegalholdConfig.WithStatusNoLock": { + "properties": { + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "LimitedEventFanoutConfig.WithStatus": { + "properties": { + "lockStatus": { + "$ref": "#/components/schemas/LockStatus" + }, + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status", + "lockStatus" + ], + "type": "object" + }, + "LimitedQualifiedUserIdList_500": { + "properties": { + "qualified_users": { + "items": { + "$ref": "#/components/schemas/Qualified_UserId" + }, + "type": "array" + } + }, + "required": [ + "qualified_users" + ], + "type": "object" + }, + "List1": { + "items": { + "$ref": "#/components/schemas/ASCII" + }, + "minItems": 1, + "type": "array" + }, + "ListConversations": { + "description": "A request to list some of a user's conversations, including remote ones. Maximum 1000 qualified conversation IDs", + "properties": { + "qualified_ids": { + "items": { + "$ref": "#/components/schemas/Qualified_ConvId" + }, + "maxItems": 1000, + "minItems": 1, + "type": "array" + } + }, + "required": [ + "qualified_ids" + ], + "type": "object" + }, + "ListType": { + "description": "true if 'members' doesn't contain all team members", + "enum": [ + true, + false + ], + "type": "boolean" + }, + "ListUsersById": { + "properties": { + "failed": { + "items": { + "$ref": "#/components/schemas/Qualified_UserId" + }, + "minItems": 1, + "type": "array" + }, + "found": { + "items": { + "$ref": "#/components/schemas/UserProfile" + }, + "type": "array" + } + }, + "required": [ + "found" + ], + "type": "object" + }, + "ListUsersQuery": { + "description": "exactly one of qualified_ids or qualified_handles must be provided.", + "example": { + "qualified_ids": [ + { + "domain": "example.com", + "id": "00000000-0000-0000-0000-000000000000" + } + ] + }, + "properties": { + "qualified_handles": { + "items": { + "$ref": "#/components/schemas/Qualified_Handle" + }, + "type": "array" + }, + "qualified_ids": { + "items": { + "$ref": "#/components/schemas/Qualified_UserId" + }, + "type": "array" + } + }, + "type": "object" + }, + "Locale": { + "type": "string" + }, + "LocaleUpdate": { + "properties": { + "locale": { + "$ref": "#/components/schemas/Locale" + } + }, + "required": [ + "locale" + ], + "type": "object" + }, + "LockStatus": { + "enum": [ + "locked", + "unlocked" + ], + "type": "string" + }, + "Login": { + "properties": { + "code": { + "$ref": "#/components/schemas/LoginCode" + }, + "email": { + "$ref": "#/components/schemas/Email" + }, + "handle": { + "$ref": "#/components/schemas/Handle" + }, + "label": { + "description": "This label can be used to delete all cookies matching it (cf. /cookies/remove)", + "type": "string" + }, + "password": { + "maxLength": 1024, + "minLength": 6, + "type": "string" + }, + "phone": { + "$ref": "#/components/schemas/PhoneNumber" + }, + "verification_code": { + "$ref": "#/components/schemas/ASCII" + } + }, + "required": [ + "password", + "phone", + "code" + ], + "type": "object" + }, + "LoginCode": { + "type": "string" + }, + "LoginCodeTimeout": { + "description": "A response for a successfully sent login code", + "properties": { + "expires_in": { + "description": "Number of seconds before the login code expires", + "format": "int32", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + } + }, + "required": [ + "expires_in" + ], + "type": "object" + }, + "MLSConfig": { + "properties": { + "allowedCipherSuites": { + "items": { + "$ref": "#/components/schemas/CipherSuiteTag" + }, + "type": "array" + }, + "defaultCipherSuite": { + "$ref": "#/components/schemas/CipherSuiteTag" + }, + "defaultProtocol": { + "$ref": "#/components/schemas/Protocol" + }, + "protocolToggleUsers": { + "description": "allowlist of users that may change protocols", + "items": { + "$ref": "#/components/schemas/UUID" + }, + "type": "array" + }, + "supportedProtocols": { + "items": { + "$ref": "#/components/schemas/Protocol" + }, + "type": "array" + } + }, + "required": [ + "protocolToggleUsers", + "defaultProtocol", + "allowedCipherSuites", + "defaultCipherSuite", + "supportedProtocols" + ], + "type": "object" + }, + "MLSConfig.WithStatus": { + "properties": { + "config": { + "$ref": "#/components/schemas/MLSConfig" + }, + "lockStatus": { + "$ref": "#/components/schemas/LockStatus" + }, + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status", + "lockStatus", + "config" + ], + "type": "object" + }, + "MLSConfig.WithStatusNoLock": { + "properties": { + "config": { + "$ref": "#/components/schemas/MLSConfig" + }, + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status", + "config" + ], + "type": "object" + }, + "MLSKeys": { + "properties": { + "ecdsa_secp256r1_sha256": { + "$ref": "#/components/schemas/MLSPublicKey" + }, + "ecdsa_secp384r1_sha384": { + "$ref": "#/components/schemas/MLSPublicKey" + }, + "ecdsa_secp521r1_sha512": { + "$ref": "#/components/schemas/MLSPublicKey" + }, + "ed25519": { + "$ref": "#/components/schemas/MLSPublicKey" + } + }, + "required": [ + "ed25519", + "ecdsa_secp256r1_sha256", + "ecdsa_secp384r1_sha384", + "ecdsa_secp521r1_sha512" + ], + "type": "object" + }, + "MLSKeysByPurpose": { + "properties": { + "removal": { + "$ref": "#/components/schemas/MLSKeys" + } + }, + "required": [ + "removal" + ], + "type": "object" + }, + "MLSMessage": { + "description": "This object can only be parsed in TLS format. Please refer to the MLS specification for details." + }, + "MLSMessageSendingStatus": { + "properties": { + "events": { + "description": "A list of events caused by sending the message.", + "items": { + "$ref": "#/components/schemas/Event" + }, + "type": "array" + }, + "time": { + "$ref": "#/components/schemas/UTCTime" + } + }, + "required": [ + "events", + "time" + ], + "type": "object" + }, + "MLSOne2OneConversation_MLSPublicKey": { + "properties": { + "conversation": { + "$ref": "#/components/schemas/Conversation" + }, + "public_keys": { + "$ref": "#/components/schemas/MLSKeysByPurpose" + } + }, + "required": [ + "conversation", + "public_keys" + ], + "type": "object" + }, + "MLSPublicKey": { + "example": "ZXhhbXBsZQo=", + "type": "string" + }, + "MLSPublicKeys": { + "additionalProperties": { + "example": "ZXhhbXBsZQo=", + "type": "string" + }, + "description": "Mapping from signature scheme (tags) to public key data", + "example": { + "ecdsa_secp256r1_sha256": "ZXhhbXBsZQo=", + "ecdsa_secp384r1_sha384": "ZXhhbXBsZQo=", + "ecdsa_secp521r1_sha512": "ZXhhbXBsZQo=", + "ed25519": "ZXhhbXBsZQo=" + }, + "type": "object" + }, + "ManagedBy": { + "enum": [ + "wire", + "scim" + ], + "type": "string" + }, + "Member": { + "description": "The user ID of the requestor", + "properties": { + "conversation_role": { + "$ref": "#/components/schemas/RoleName" + }, + "hidden": { + "type": "boolean" + }, + "hidden_ref": { + "type": "string" + }, + "id": { + "$ref": "#/components/schemas/UUID" + }, + "otr_archived": { + "type": "boolean" + }, + "otr_archived_ref": { + "type": "string" + }, + "otr_muted_ref": { + "type": "string" + }, + "otr_muted_status": { + "format": "int32", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + }, + "qualified_id": { + "$ref": "#/components/schemas/Qualified_UserId" + }, + "service": { + "$ref": "#/components/schemas/ServiceRef" + }, + "status": {}, + "status_ref": {}, + "status_time": {} + }, + "required": [ + "qualified_id" + ], + "type": "object" + }, + "MemberUpdate": { + "properties": { + "hidden": { + "type": "boolean" + }, + "hidden_ref": { + "type": "string" + }, + "otr_archived": { + "type": "boolean" + }, + "otr_archived_ref": { + "type": "string" + }, + "otr_muted_ref": { + "type": "string" + }, + "otr_muted_status": { + "format": "int32", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + } + }, + "type": "object" + }, + "MemberUpdateData": { + "properties": { + "conversation_role": { + "$ref": "#/components/schemas/RoleName" + }, + "hidden": { + "type": "boolean" + }, + "hidden_ref": { + "type": "string" + }, + "otr_archived": { + "type": "boolean" + }, + "otr_archived_ref": { + "type": "string" + }, + "otr_muted_ref": { + "type": "string" + }, + "otr_muted_status": { + "format": "int32", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + }, + "qualified_target": { + "$ref": "#/components/schemas/Qualified_UserId" + }, + "target": { + "$ref": "#/components/schemas/UUID" + } + }, + "required": [ + "qualified_target" + ], + "type": "object" + }, + "MessageSendingStatus": { + "description": "The Proteus message sending status. It has these fields:\n- `time`: Time of sending message.\n- `missing`: Clients that the message /should/ have been encrypted for, but wasn't.\n- `redundant`: Clients that the message /should not/ have been encrypted for, but was.\n- `deleted`: Clients that were deleted.\n- `failed_to_send`: When message sending fails for some clients but succeeds for others, e.g., because a remote backend is unreachable, this field will contain the list of clients for which the message sending failed. This list should be empty when message sending is not even tried, like when some clients are missing.", + "properties": { + "deleted": { + "$ref": "#/components/schemas/QualifiedUserClients" + }, + "failed_to_confirm_clients": { + "$ref": "#/components/schemas/QualifiedUserClients" + }, + "failed_to_send": { + "$ref": "#/components/schemas/QualifiedUserClients" + }, + "missing": { + "$ref": "#/components/schemas/QualifiedUserClients" + }, + "redundant": { + "$ref": "#/components/schemas/QualifiedUserClients" + }, + "time": { + "$ref": "#/components/schemas/UTCTime" + } + }, + "required": [ + "time", + "missing", + "redundant", + "deleted", + "failed_to_send", + "failed_to_confirm_clients" + ], + "type": "object" + }, + "MlsE2EIdConfig": { + "properties": { + "acmeDiscoveryUrl": { + "$ref": "#/components/schemas/HttpsUrl" + }, + "crlProxy": { + "$ref": "#/components/schemas/HttpsUrl" + }, + "useProxyOnMobile": { + "type": "boolean" + }, + "verificationExpiration": { + "description": "When a client first tries to fetch or renew a certificate, they may need to login to an identity provider (IdP) depending on their IdP domain authentication policy. The user may have a grace period during which they can \"snooze\" this login. The duration of this grace period (in seconds) is set in the `verificationDuration` parameter, which is enforced separately by each client. After the grace period has expired, the client will not allow the user to use the application until they have logged to refresh the certificate. The default value is 1 day (86400s). The client enrolls using the Automatic Certificate Management Environment (ACME) protocol. The `acmeDiscoveryUrl` parameter must be set to the HTTPS URL of the ACME server discovery endpoint for this team. It is of the form \"https://acme.{backendDomain}/acme/{provisionerName}/discovery\". For example: `https://acme.example.com/acme/provisioner1/discovery`.", + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + } + }, + "required": [ + "verificationExpiration" + ], + "type": "object" + }, + "MlsE2EIdConfig.WithStatus": { + "properties": { + "config": { + "$ref": "#/components/schemas/MlsE2EIdConfig" + }, + "lockStatus": { + "$ref": "#/components/schemas/LockStatus" + }, + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status", + "lockStatus", + "config" + ], + "type": "object" + }, + "MlsE2EIdConfig.WithStatusNoLock": { + "properties": { + "config": { + "$ref": "#/components/schemas/MlsE2EIdConfig" + }, + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status", + "config" + ], + "type": "object" + }, + "MlsMigration": { + "properties": { + "finaliseRegardlessAfter": { + "$ref": "#/components/schemas/UTCTime" + }, + "startTime": { + "$ref": "#/components/schemas/UTCTime" + } + }, + "type": "object" + }, + "MlsMigration.WithStatus": { + "properties": { + "config": { + "$ref": "#/components/schemas/MlsMigration" + }, + "lockStatus": { + "$ref": "#/components/schemas/LockStatus" + }, + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status", + "lockStatus", + "config" + ], + "type": "object" + }, + "MlsMigration.WithStatusNoLock": { + "properties": { + "config": { + "$ref": "#/components/schemas/MlsMigration" + }, + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status", + "config" + ], + "type": "object" + }, + "NameIDFormat": { + "enum": [ + "NameIDFUnspecified", + "NameIDFEmail", + "NameIDFX509", + "NameIDFWindows", + "NameIDFKerberos", + "NameIDFEntity", + "NameIDFPersistent", + "NameIDFTransient" + ], + "type": "string" + }, + "NameIdPolicy": { + "properties": { + "allowCreate": { + "type": "boolean" + }, + "format": { + "$ref": "#/components/schemas/NameIDFormat" + }, + "spNameQualifier": { + "$ref": "#/components/schemas/XmlText" + } + }, + "required": [ + "format", + "allowCreate" + ], + "type": "object" + }, + "NewAssetToken": { + "properties": { + "token": { + "$ref": "#/components/schemas/ASCII" + } + }, + "required": [ + "token" + ], + "type": "object" + }, + "NewClient": { + "properties": { + "capabilities": { + "description": "Hints provided by the client for the backend so it can behave in a backwards-compatible way.", + "items": { + "$ref": "#/components/schemas/ClientCapability" + }, + "type": "array" + }, + "class": { + "$ref": "#/components/schemas/ClientClass" + }, + "cookie": { + "description": "The cookie label, i.e. the label used when logging in.", + "type": "string" + }, + "label": { + "type": "string" + }, + "lastkey": { + "$ref": "#/components/schemas/Prekey" + }, + "mls_public_keys": { + "$ref": "#/components/schemas/MLSPublicKeys" + }, + "model": { + "type": "string" + }, + "password": { + "description": "The password of the authenticated user for verification. Note: Required for registration of the 2nd, 3rd, ... client.", + "maxLength": 1024, + "minLength": 6, + "type": "string" + }, + "prekeys": { + "description": "Prekeys for other clients to establish OTR sessions.", + "items": { + "$ref": "#/components/schemas/Prekey" + }, + "type": "array" + }, + "type": { + "$ref": "#/components/schemas/ClientType" + }, + "verification_code": { + "$ref": "#/components/schemas/ASCII" + } + }, + "required": [ + "prekeys", + "lastkey", + "type" + ], + "type": "object" + }, + "NewConv": { + "description": "JSON object to create a new conversation. When using 'qualified_users' (preferred), you can omit 'users'", + "properties": { + "access": { + "items": { + "$ref": "#/components/schemas/Access" + }, + "type": "array" + }, + "access_role": { + "items": { + "$ref": "#/components/schemas/AccessRole" + }, + "type": "array" + }, + "conversation_role": { + "$ref": "#/components/schemas/RoleName" + }, + "message_timer": { + "description": "Per-conversation message timer", + "format": "int64", + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + }, + "name": { + "maxLength": 256, + "minLength": 1, + "type": "string" + }, + "protocol": { + "$ref": "#/components/schemas/BaseProtocol" + }, + "qualified_users": { + "description": "List of qualified user IDs (excluding the requestor) to be part of this conversation", + "items": { + "$ref": "#/components/schemas/Qualified_UserId" + }, + "type": "array" + }, + "receipt_mode": { + "description": "Conversation receipt mode", + "format": "int32", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + }, + "team": { + "$ref": "#/components/schemas/ConvTeamInfo" + }, + "users": { + "deprecated": true, + "description": "List of user IDs (excluding the requestor) to be part of this conversation (deprecated)", + "items": { + "$ref": "#/components/schemas/UUID" + }, + "type": "array" + } + }, + "type": "object" + }, + "NewLegalHoldService": { + "properties": { + "auth_token": { + "$ref": "#/components/schemas/ASCII" + }, + "base_url": { + "$ref": "#/components/schemas/HttpsUrl" + }, + "public_key": { + "$ref": "#/components/schemas/ServiceKeyPEM" + } + }, + "required": [ + "base_url", + "public_key", + "auth_token" + ], + "type": "object" + }, + "NewPasswordReset": { + "description": "Data to initiate a password reset", + "properties": { + "email": { + "$ref": "#/components/schemas/Email" + }, + "phone": { + "$ref": "#/components/schemas/PhoneNumber" + } + }, + "type": "object" + }, + "NewProvider": { + "properties": { + "description": { + "maxLength": 1024, + "minLength": 1, + "type": "string" + }, + "email": { + "$ref": "#/components/schemas/Email" + }, + "name": { + "maxLength": 128, + "minLength": 1, + "type": "string" + }, + "password": { + "maxLength": 1024, + "minLength": 6, + "type": "string" + }, + "url": { + "$ref": "#/components/schemas/HttpsUrl" + } + }, + "required": [ + "name", + "email", + "url", + "description" + ], + "type": "object" + }, + "NewProviderResponse": { + "properties": { + "id": { + "$ref": "#/components/schemas/UUID" + }, + "password": { + "maxLength": 1024, + "minLength": 8, + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "NewService": { + "properties": { + "assets": { + "items": { + "$ref": "#/components/schemas/UserAsset" + }, + "type": "array" + }, + "auth_token": { + "$ref": "#/components/schemas/ASCII" + }, + "base_url": { + "$ref": "#/components/schemas/HttpsUrl" + }, + "description": { + "maxLength": 1024, + "minLength": 1, + "type": "string" + }, + "name": { + "maxLength": 128, + "minLength": 1, + "type": "string" + }, + "public_key": { + "$ref": "#/components/schemas/ServiceKeyPEM" + }, + "summary": { + "maxLength": 128, + "minLength": 1, + "type": "string" + }, + "tags": { + "items": { + "$ref": "#/components/schemas/" + }, + "maxItems": 3, + "minItems": 1, + "type": "array" + } + }, + "required": [ + "name", + "summary", + "description", + "base_url", + "public_key", + "assets", + "tags" + ], + "type": "object" + }, + "NewServiceResponse": { + "properties": { + "auth_token": { + "$ref": "#/components/schemas/ASCII" + }, + "id": { + "$ref": "#/components/schemas/UUID" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "NewTeamMember": { + "description": "Required data when creating new team members", + "properties": { + "member": { + "description": "the team member to add (the legalhold_status field must be null or missing!)", + "properties": { + "created_at": { + "$ref": "#/components/schemas/UTCTime" + }, + "created_by": { + "$ref": "#/components/schemas/UUID" + }, + "permissions": { + "$ref": "#/components/schemas/Permissions" + }, + "user": { + "$ref": "#/components/schemas/UUID" + } + }, + "required": [ + "user", + "permissions" + ], + "type": "object" + } + }, + "required": [ + "member" + ], + "type": "object" + }, + "NewUser": { + "properties": { + "accent_id": { + "format": "int32", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + }, + "assets": { + "items": { + "$ref": "#/components/schemas/UserAsset" + }, + "type": "array" + }, + "email": { + "$ref": "#/components/schemas/Email" + }, + "email_code": { + "$ref": "#/components/schemas/ASCII" + }, + "expires_in": { + "maximum": 604800, + "minimum": 1, + "type": "integer" + }, + "invitation_code": { + "$ref": "#/components/schemas/ASCII" + }, + "label": { + "type": "string" + }, + "locale": { + "$ref": "#/components/schemas/Locale" + }, + "managed_by": { + "$ref": "#/components/schemas/ManagedBy" + }, + "name": { + "maxLength": 128, + "minLength": 1, + "type": "string" + }, + "password": { + "maxLength": 1024, + "minLength": 8, + "type": "string" + }, + "phone": { + "$ref": "#/components/schemas/PhoneNumber" + }, + "phone_code": { + "$ref": "#/components/schemas/ASCII" + }, + "picture": { + "$ref": "#/components/schemas/Pict" + }, + "sso_id": { + "$ref": "#/components/schemas/UserSSOId" + }, + "supported_protocols": { + "items": { + "$ref": "#/components/schemas/BaseProtocol" + }, + "type": "array" + }, + "team": { + "$ref": "#/components/schemas/BindingNewTeamUser" + }, + "team_code": { + "$ref": "#/components/schemas/ASCII" + }, + "team_id": { + "$ref": "#/components/schemas/UUID" + }, + "uuid": { + "$ref": "#/components/schemas/UUID" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "OAuthAccessTokenRequest": { + "properties": { + "client_id": { + "$ref": "#/components/schemas/UUID" + }, + "code": { + "$ref": "#/components/schemas/OAuthAuthorizationCode" + }, + "code_verifier": { + "description": "The code verifier to complete the code challenge", + "maxLength": 128, + "minLength": 43, + "type": "string" + }, + "grant_type": { + "$ref": "#/components/schemas/OAuthGrantType" + }, + "redirect_uri": { + "$ref": "#/components/schemas/RedirectUrl" + } + }, + "required": [ + "grant_type", + "client_id", + "code_verifier", + "code", + "redirect_uri" + ], + "type": "object" + }, + "OAuthAccessTokenResponse": { + "properties": { + "access_token": { + "description": "The access token, which has a relatively short lifetime", + "type": "string" + }, + "expires_in": { + "description": "The lifetime of the access token in seconds", + "format": "int32", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + }, + "refresh_token": { + "description": "The refresh token, which has a relatively long lifetime, and can be used to obtain a new access token", + "type": "string" + }, + "token_type": { + "$ref": "#/components/schemas/OAuthAccessTokenType" + } + }, + "required": [ + "access_token", + "token_type", + "expires_in", + "refresh_token" + ], + "type": "object" + }, + "OAuthAccessTokenType": { + "description": "The type of the access token. Currently only `Bearer` is supported.", + "enum": [ + "Bearer" + ], + "type": "string" + }, + "OAuthApplication": { + "properties": { + "id": { + "$ref": "#/components/schemas/UUID" + }, + "name": { + "description": "The OAuth client's name", + "maxLength": 256, + "minLength": 6, + "type": "string" + } + }, + "required": [ + "id", + "name" + ], + "type": "object" + }, + "OAuthAuthorizationCode": { + "description": "The authorization code", + "type": "string" + }, + "OAuthClient": { + "properties": { + "application_name": { + "maxLength": 256, + "minLength": 6, + "type": "string" + }, + "client_id": { + "$ref": "#/components/schemas/UUID" + }, + "redirect_url": { + "$ref": "#/components/schemas/RedirectUrl" + } + }, + "required": [ + "client_id", + "application_name", + "redirect_url" + ], + "type": "object" + }, + "OAuthCodeChallenge": { + "description": "Generated by the client from the code verifier (unpadded base64url-encoded SHA256 hash of the code verifier)", + "type": "string" + }, + "OAuthGrantType": { + "description": "Indicates which authorization flow to use. Use `authorization_code` for authorization code flow.", + "enum": [ + "authorization_code", + "refresh_token" + ], + "type": "string" + }, + "OAuthRefreshAccessTokenRequest": { + "properties": { + "client_id": { + "$ref": "#/components/schemas/UUID" + }, + "grant_type": { + "$ref": "#/components/schemas/OAuthGrantType" + }, + "refresh_token": { + "description": "The refresh token", + "type": "string" + } + }, + "required": [ + "grant_type", + "client_id", + "refresh_token" + ], + "type": "object" + }, + "OAuthResponseType": { + "description": "Indicates which authorization flow to use. Use `code` for authorization code flow.", + "enum": [ + "code" + ], + "type": "string" + }, + "OAuthRevokeRefreshTokenRequest": { + "properties": { + "client_id": { + "$ref": "#/components/schemas/UUID" + }, + "refresh_token": { + "description": "The refresh token", + "type": "string" + } + }, + "required": [ + "client_id", + "refresh_token" + ], + "type": "object" + }, + "Object": { + "additionalProperties": true, + "description": "A single notification event", + "properties": { + "type": { + "description": "Event type", + "type": "string" + } + }, + "title": "Event", + "type": "object" + }, + "OtherMember": { + "properties": { + "conversation_role": { + "$ref": "#/components/schemas/RoleName" + }, + "id": { + "$ref": "#/components/schemas/UUID" + }, + "qualified_id": { + "$ref": "#/components/schemas/Qualified_UserId" + }, + "service": { + "$ref": "#/components/schemas/ServiceRef" + }, + "status": { + "deprecated": true, + "description": "deprecated", + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + } + }, + "required": [ + "qualified_id" + ], + "type": "object" + }, + "OtherMemberUpdate": { + "description": "Update user properties of other members relative to a conversation", + "properties": { + "conversation_role": { + "$ref": "#/components/schemas/RoleName" + } + }, + "type": "object" + }, + "OtrMessage": { + "description": "Encrypted message of a conversation", + "properties": { + "data": { + "description": "Extra (symmetric) data (i.e. ciphertext, Base64 in JSON) that is common with all other recipients.", + "type": "string" + }, + "recipient": { + "description": "A 64-bit unsigned integer, represented as a hexadecimal numeral. Any valid hexadecimal numeral is accepted, but the backend will only produce representations with lowercase digits and no leading zeros", + "type": "string" + }, + "sender": { + "description": "A 64-bit unsigned integer, represented as a hexadecimal numeral. Any valid hexadecimal numeral is accepted, but the backend will only produce representations with lowercase digits and no leading zeros", + "type": "string" + }, + "text": { + "description": "The ciphertext for the recipient (Base64 in JSON)", + "type": "string" + } + }, + "required": [ + "sender", + "recipient", + "text" + ], + "type": "object" + }, + "OutlookCalIntegrationConfig.WithStatus": { + "properties": { + "lockStatus": { + "$ref": "#/components/schemas/LockStatus" + }, + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status", + "lockStatus" + ], + "type": "object" + }, + "OutlookCalIntegrationConfig.WithStatusNoLock": { + "properties": { + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "OwnKeyPackages": { + "properties": { + "count": { + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + } + }, + "required": [ + "count" + ], + "type": "object" + }, + "PagingState": { + "description": "Paging state that should be supplied to retrieve the next page of results", + "type": "string" + }, + "PasswordChange": { + "properties": { + "new_password": { + "maxLength": 1024, + "minLength": 6, + "type": "string" + }, + "old_password": { + "maxLength": 1024, + "minLength": 6, + "type": "string" + } + }, + "required": [ + "old_password", + "new_password" + ], + "type": "object" + }, + "PasswordReset": { + "properties": { + "email": { + "$ref": "#/components/schemas/Email" + } + }, + "required": [ + "email" + ], + "type": "object" + }, + "Permissions": { + "description": "This is just a complicated way of representing a team role. self and copy always have to contain the same integer, and only the following integers are allowed: 1025 (partner), 1587 (member), 5951 (admin), 8191 (owner). Unit tests of the galley-types package in wire-server contain an authoritative list.", + "properties": { + "copy": { + "format": "int64", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + }, + "self": { + "format": "int64", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "self", + "copy" + ], + "type": "object" + }, + "PhoneNumber": { + "description": "Phone number of the invitee, in the E.164 format", + "type": "string" + }, + "PhoneUpdate": { + "properties": { + "phone": { + "$ref": "#/components/schemas/PhoneNumber" + } + }, + "required": [ + "phone" + ], + "type": "object" + }, + "Pict": { + "items": {}, + "maxItems": 10, + "minItems": 0, + "type": "array" + }, + "Prekey": { + "properties": { + "id": { + "maximum": 65535, + "minimum": 0, + "type": "integer" + }, + "key": { + "type": "string" + } + }, + "required": [ + "id", + "key" + ], + "type": "object" + }, + "PrekeyBundle": { + "properties": { + "clients": { + "items": { + "$ref": "#/components/schemas/ClientPrekey" + }, + "type": "array" + }, + "user": { + "$ref": "#/components/schemas/UUID" + } + }, + "required": [ + "user", + "clients" + ], + "type": "object" + }, + "Priority": { + "enum": [ + "low", + "high" + ], + "type": "string" + }, + "PropertyKeysAndValues": { + "type": "object" + }, + "PropertyValue": { + "description": "An arbitrary JSON value for a property" + }, + "Protocol": { + "enum": [ + "proteus", + "mls", + "mixed" + ], + "type": "string" + }, + "ProtocolUpdate": { + "properties": { + "protocol": { + "$ref": "#/components/schemas/Protocol" + } + }, + "type": "object" + }, + "Provider": { + "properties": { + "description": { + "type": "string" + }, + "email": { + "$ref": "#/components/schemas/Email" + }, + "id": { + "$ref": "#/components/schemas/UUID" + }, + "name": { + "maxLength": 128, + "minLength": 1, + "type": "string" + }, + "url": { + "$ref": "#/components/schemas/HttpsUrl" + } + }, + "required": [ + "id", + "name", + "email", + "url", + "description" + ], + "type": "object" + }, + "ProviderActivationResponse": { + "properties": { + "email": { + "$ref": "#/components/schemas/Email" + } + }, + "required": [ + "email" + ], + "type": "object" + }, + "ProviderLogin": { + "properties": { + "email": { + "$ref": "#/components/schemas/Email" + }, + "password": { + "maxLength": 1024, + "minLength": 6, + "type": "string" + } + }, + "required": [ + "email", + "password" + ], + "type": "object" + }, + "PubClient": { + "properties": { + "class": { + "$ref": "#/components/schemas/ClientClass" + }, + "id": { + "description": "A 64-bit unsigned integer, represented as a hexadecimal numeral. Any valid hexadecimal numeral is accepted, but the backend will only produce representations with lowercase digits and no leading zeros", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "PublicSubConversation": { + "description": "An MLS subconversation", + "properties": { + "cipher_suite": { + "$ref": "#/components/schemas/CipherSuiteTag" + }, + "epoch": { + "description": "The epoch number of the corresponding MLS group", + "format": "int64", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + }, + "epoch_timestamp": { + "$ref": "#/components/schemas/UTCTime" + }, + "group_id": { + "$ref": "#/components/schemas/GroupId" + }, + "members": { + "items": { + "$ref": "#/components/schemas/ClientIdentity" + }, + "type": "array" + }, + "parent_qualified_id": { + "$ref": "#/components/schemas/Qualified_ConvId" + }, + "subconv_id": { + "type": "string" + } + }, + "required": [ + "parent_qualified_id", + "subconv_id", + "group_id", + "epoch", + "members" + ], + "type": "object" + }, + "PushToken": { + "description": "Native Push Token", + "properties": { + "app": { + "description": "Application", + "type": "string" + }, + "client": { + "description": "Client ID", + "type": "string" + }, + "token": { + "description": "Access Token", + "type": "string" + }, + "transport": { + "$ref": "#/components/schemas/Transport" + } + }, + "required": [ + "transport", + "app", + "token", + "client" + ], + "type": "object" + }, + "PushTokenList": { + "description": "List of Native Push Tokens", + "properties": { + "tokens": { + "description": "Push tokens", + "items": { + "$ref": "#/components/schemas/PushToken" + }, + "type": "array" + } + }, + "required": [ + "tokens" + ], + "type": "object" + }, + "QualifiedNewOtrMessage": { + "description": "This object can only be parsed from Protobuf.\nThe specification for the protobuf types is here: \nhttps://github.com/wireapp/generic-message-proto/blob/master/proto/otr.proto." + }, + "QualifiedUserClientPrekeyMapV4": { + "properties": { + "failed_to_list": { + "items": { + "$ref": "#/components/schemas/Qualified_UserId" + }, + "type": "array" + }, + "qualified_user_client_prekeys": { + "additionalProperties": { + "$ref": "#/components/schemas/UserClientPrekeyMap" + }, + "type": "object" + } + }, + "required": [ + "qualified_user_client_prekeys" + ], + "type": "object" + }, + "QualifiedUserClients": { + "additionalProperties": { + "additionalProperties": { + "items": { + "description": "A 64-bit unsigned integer, represented as a hexadecimal numeral. Any valid hexadecimal numeral is accepted, but the backend will only produce representations with lowercase digits and no leading zeros", + "type": "string" + }, + "type": "array" + }, + "type": "object" + }, + "description": "Map of Domain to UserClients", + "example": { + "domain1.example.com": { + "1d51e2d6-9c70-605f-efc8-ff85c3dabdc7": [ + "60f85e4b15ad3786", + "6e323ab31554353b" + ] + } + }, + "type": "object" + }, + "QualifiedUserIdList with EdMemberLeftReason": { + "properties": { + "qualified_user_ids": { + "items": { + "$ref": "#/components/schemas/Qualified_UserId" + }, + "type": "array" + }, + "reason": { + "$ref": "#/components/schemas/EdMemberLeftReason" + }, + "user_ids": { + "deprecated": true, + "description": "Deprecated, use qualified_user_ids", + "items": { + "$ref": "#/components/schemas/UUID" + }, + "type": "array" + } + }, + "required": [ + "reason", + "qualified_user_ids", + "user_ids" + ], + "type": "object" + }, + "QualifiedUserMap_Set_PubClient": { + "additionalProperties": { + "$ref": "#/components/schemas/UserMap_Set_PubClient" + }, + "description": "Map of Domain to (UserMap (Set_PubClient)).", + "example": { + "domain1.example.com": { + "1d51e2d6-9c70-605f-efc8-ff85c3dabdc7": [ + { + "class": "legalhold", + "id": "d0" + } + ] + } + }, + "type": "object" + }, + "Qualified_ConvId": { + "properties": { + "domain": { + "$ref": "#/components/schemas/Domain" + }, + "id": { + "$ref": "#/components/schemas/UUID" + } + }, + "required": [ + "domain", + "id" + ], + "type": "object" + }, + "Qualified_Handle": { + "properties": { + "domain": { + "$ref": "#/components/schemas/Domain" + }, + "handle": { + "$ref": "#/components/schemas/Handle" + } + }, + "required": [ + "domain", + "handle" + ], + "type": "object" + }, + "Qualified_UserId": { + "properties": { + "domain": { + "$ref": "#/components/schemas/Domain" + }, + "id": { + "$ref": "#/components/schemas/UUID" + } + }, + "required": [ + "domain", + "id" + ], + "type": "object" + }, + "QueuedNotification": { + "description": "A single notification", + "properties": { + "id": { + "$ref": "#/components/schemas/UUID" + }, + "payload": { + "description": "List of events", + "items": { + "$ref": "#/components/schemas/Object" + }, + "minItems": 1, + "type": "array" + } + }, + "required": [ + "id", + "payload" + ], + "type": "object" + }, + "QueuedNotificationList": { + "description": "Zero or more notifications", + "properties": { + "has_more": { + "description": "Whether there are still more notifications.", + "type": "boolean" + }, + "notifications": { + "description": "Notifications", + "items": { + "$ref": "#/components/schemas/QueuedNotification" + }, + "type": "array" + }, + "time": { + "$ref": "#/components/schemas/UTCTime" + } + }, + "required": [ + "notifications" + ], + "type": "object" + }, + "RTCConfiguration": { + "description": "A subset of the WebRTC 'RTCConfiguration' dictionary", + "properties": { + "ice_servers": { + "description": "Array of 'RTCIceServer' objects", + "items": { + "$ref": "#/components/schemas/RTCIceServer" + }, + "minItems": 1, + "type": "array" + }, + "sft_servers": { + "description": "Array of 'SFTServer' objects (optional)", + "items": { + "$ref": "#/components/schemas/SftServer" + }, + "minItems": 1, + "type": "array" + }, + "sft_servers_all": { + "description": "Array of all SFT servers", + "items": { + "$ref": "#/components/schemas/SftServer" + }, + "type": "array" + }, + "ttl": { + "description": "Number of seconds after which the configuration should be refreshed (advisory)", + "format": "int32", + "maximum": 4294967295, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "ice_servers", + "ttl" + ], + "type": "object" + }, + "RTCIceServer": { + "description": "A subset of the WebRTC 'RTCIceServer' object", + "properties": { + "credential": { + "$ref": "#/components/schemas/ASCII" + }, + "urls": { + "description": "Array of TURN server addresses of the form 'turn::'", + "items": { + "$ref": "#/components/schemas/TurnURI" + }, + "minItems": 1, + "type": "array" + }, + "username": { + "$ref": "#/components/schemas/" + } + }, + "required": [ + "urls", + "username", + "credential" + ], + "type": "object" + }, + "RedirectUrl": { + "description": "The URL must match the URL that was used to generate the authorization code.", + "type": "string" + }, + "Relation": { + "enum": [ + "accepted", + "blocked", + "pending", + "ignored", + "sent", + "cancelled", + "missing-legalhold-consent" + ], + "type": "string" + }, + "RemoveBotResponse": { + "properties": { + "event": { + "$ref": "#/components/schemas/Event" + } + }, + "required": [ + "event" + ], + "type": "object" + }, + "RemoveCookies": { + "description": "Data required to remove cookies", + "properties": { + "ids": { + "description": "A list of cookie IDs to revoke", + "items": { + "format": "int32", + "maximum": 4294967295, + "minimum": 0, + "type": "integer" + }, + "type": "array" + }, + "labels": { + "description": "A list of cookie labels for which to revoke the cookies", + "items": { + "type": "string" + }, + "type": "array" + }, + "password": { + "description": "The user's password", + "maxLength": 1024, + "minLength": 6, + "type": "string" + } + }, + "required": [ + "password" + ], + "type": "object" + }, + "RemoveLegalHoldSettingsRequest": { + "properties": { + "password": { + "maxLength": 1024, + "minLength": 6, + "type": "string" + } + }, + "type": "object" + }, + "RichField": { + "properties": { + "type": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "type", + "value" + ], + "type": "object" + }, + "RichInfoAssocList": { + "description": "json object with case-insensitive fields.", + "properties": { + "fields": { + "items": { + "$ref": "#/components/schemas/RichField" + }, + "type": "array" + }, + "version": { + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + } + }, + "required": [ + "version", + "fields" + ], + "type": "object" + }, + "Role": { + "description": "Role of the invited user", + "enum": [ + "owner", + "admin", + "member", + "partner" + ], + "type": "string" + }, + "RoleName": { + "description": "Role name, between 2 and 128 chars, 'wire_' prefix is reserved for roles designed by Wire (i.e., no custom roles can have the same prefix)", + "type": "string" + }, + "SSOConfig.WithStatus": { + "properties": { + "lockStatus": { + "$ref": "#/components/schemas/LockStatus" + }, + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status", + "lockStatus" + ], + "type": "object" + }, + "ScimTokenInfo": { + "properties": { + "created_at": { + "$ref": "#/components/schemas/UTCTime" + }, + "description": { + "type": "string" + }, + "id": { + "$ref": "#/components/schemas/UUID" + }, + "idp": { + "$ref": "#/components/schemas/UUID" + }, + "team": { + "$ref": "#/components/schemas/UUID" + } + }, + "required": [ + "team", + "id", + "created_at", + "description" + ], + "type": "object" + }, + "ScimTokenList": { + "properties": { + "tokens": { + "items": { + "$ref": "#/components/schemas/ScimTokenInfo" + }, + "type": "array" + } + }, + "required": [ + "tokens" + ], + "type": "object" + }, + "SearchResult": { + "properties": { + "documents": { + "description": "List of contacts found", + "items": { + "$ref": "#/components/schemas/TeamContact" + }, + "type": "array" + }, + "found": { + "description": "Total number of hits", + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + }, + "has_more": { + "description": "Indicates whether there are more results to be fetched", + "type": "boolean" + }, + "paging_state": { + "$ref": "#/components/schemas/PagingState" + }, + "returned": { + "description": "Total number of hits returned", + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + }, + "search_policy": { + "$ref": "#/components/schemas/FederatedUserSearchPolicy" + }, + "took": { + "description": "Search time in ms", + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + } + }, + "required": [ + "found", + "returned", + "took", + "documents", + "search_policy" + ], + "type": "object" + }, + "SearchVisibilityAvailableConfig.WithStatus": { + "properties": { + "lockStatus": { + "$ref": "#/components/schemas/LockStatus" + }, + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status", + "lockStatus" + ], + "type": "object" + }, + "SearchVisibilityAvailableConfig.WithStatusNoLock": { + "properties": { + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "SearchVisibilityInboundConfig.WithStatus": { + "properties": { + "lockStatus": { + "$ref": "#/components/schemas/LockStatus" + }, + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status", + "lockStatus" + ], + "type": "object" + }, + "SearchVisibilityInboundConfig.WithStatusNoLock": { + "properties": { + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "SelfDeletingMessagesConfig": { + "properties": { + "enforcedTimeoutSeconds": { + "format": "int32", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + } + }, + "required": [ + "enforcedTimeoutSeconds" + ], + "type": "object" + }, + "SelfDeletingMessagesConfig.WithStatus": { + "properties": { + "config": { + "$ref": "#/components/schemas/SelfDeletingMessagesConfig" + }, + "lockStatus": { + "$ref": "#/components/schemas/LockStatus" + }, + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status", + "lockStatus", + "config" + ], + "type": "object" + }, + "SelfDeletingMessagesConfig.WithStatusNoLock": { + "properties": { + "config": { + "$ref": "#/components/schemas/SelfDeletingMessagesConfig" + }, + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status", + "config" + ], + "type": "object" + }, + "SendActivationCode": { + "description": "Data for requesting an email or phone activation code to be sent. One of 'email' or 'phone' must be present.", + "properties": { + "email": { + "$ref": "#/components/schemas/Email" + }, + "locale": { + "$ref": "#/components/schemas/Locale" + }, + "phone": { + "$ref": "#/components/schemas/PhoneNumber" + }, + "voice_call": { + "description": "Request the code with a call instead (default is SMS).", + "type": "boolean" + } + }, + "type": "object" + }, + "SendLoginCode": { + "description": "Payload for requesting a login code to be sent", + "properties": { + "force": { + "type": "boolean" + }, + "phone": { + "description": "E.164 phone number to send the code to", + "type": "string" + }, + "voice_call": { + "description": "Request the code with a call instead (default is SMS)", + "type": "boolean" + } + }, + "required": [ + "phone" + ], + "type": "object" + }, + "SendVerificationCode": { + "properties": { + "action": { + "$ref": "#/components/schemas/VerificationAction" + }, + "email": { + "$ref": "#/components/schemas/Email" + } + }, + "required": [ + "action", + "email" + ], + "type": "object" + }, + "Service": { + "properties": { + "assets": { + "items": { + "$ref": "#/components/schemas/UserAsset" + }, + "type": "array" + }, + "auth_tokens": { + "$ref": "#/components/schemas/List1" + }, + "base_url": { + "$ref": "#/components/schemas/HttpsUrl" + }, + "description": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "id": { + "$ref": "#/components/schemas/UUID" + }, + "name": { + "maxLength": 128, + "minLength": 1, + "type": "string" + }, + "public_keys": { + "$ref": "#/components/schemas/List1" + }, + "summary": { + "type": "string" + }, + "tags": { + "items": { + "$ref": "#/components/schemas/" + }, + "type": "array" + } + }, + "required": [ + "id", + "name", + "summary", + "description", + "base_url", + "auth_tokens", + "public_keys", + "assets", + "tags", + "enabled" + ], + "type": "object" + }, + "ServiceKey": { + "properties": { + "pem": { + "$ref": "#/components/schemas/ServiceKeyPEM" + }, + "size": { + "format": "int32", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + }, + "type": { + "$ref": "#/components/schemas/ServiceKeyType" + } + }, + "required": [ + "type", + "size", + "pem" + ], + "type": "object" + }, + "ServiceKeyPEM": { + "example": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu+Kg/PHHU3atXrUbKnw0\nG06FliXcNt3lMwl2os5twEDcPPFw/feGiAKymxp+7JqZDrseS5D9THGrW+OQRIPH\nWvUBdiLfGrZqJO223DB6D8K2Su/odmnjZJ2z23rhXoEArTplu+Dg9K+c2LVeXTKV\nVPOaOzgtAB21XKRiQ4ermqgi3/njr03rXyq/qNkuNd6tNcg+HAfGxfGvvCSYBfiS\nbUKr/BeArYRcjzr/h5m1In6fG/if9GEI6m8dxHT9JbY53wiksowy6ajCuqskIFg8\n7X883H+LA/d6X5CTiPv1VMxXdBUiGPuC9IT/6CNQ1/LFt0P37ax58+LGYlaFo7la\nnQIDAQAB\n-----END PUBLIC KEY-----\n", + "type": "string" + }, + "ServiceKeyType": { + "enum": [ + "rsa" + ], + "type": "string" + }, + "ServiceProfile": { + "properties": { + "has_more": { + "type": "boolean" + }, + "services": { + "items": { + "$ref": "#/components/schemas/ServiceProfile" + }, + "type": "array" + } + }, + "required": [ + "has_more", + "services" + ], + "type": "object" + }, + "ServiceRef": { + "properties": { + "id": { + "$ref": "#/components/schemas/UUID" + }, + "provider": { + "$ref": "#/components/schemas/UUID" + } + }, + "required": [ + "id", + "provider" + ], + "type": "object" + }, + "ServiceTagList": { + "items": { + "$ref": "#/components/schemas/" + }, + "type": "array" + }, + "SftServer": { + "description": "Inspired by WebRTC 'RTCIceServer' object, contains details of SFT servers", + "properties": { + "urls": { + "description": "Array containing exactly one SFT server address of the form 'https://:'", + "items": { + "$ref": "#/components/schemas/HttpsUrl" + }, + "type": "array" + } + }, + "required": [ + "urls" + ], + "type": "object" + }, + "SimpleMember": { + "properties": { + "conversation_role": { + "$ref": "#/components/schemas/RoleName" + }, + "id": { + "$ref": "#/components/schemas/UUID" + }, + "qualified_id": { + "$ref": "#/components/schemas/Qualified_UserId" + } + }, + "required": [ + "qualified_id" + ], + "type": "object" + }, + "SimpleMembers": { + "properties": { + "user_ids": { + "deprecated": true, + "description": "deprecated", + "items": { + "$ref": "#/components/schemas/UUID" + }, + "type": "array" + }, + "users": { + "items": { + "$ref": "#/components/schemas/SimpleMember" + }, + "type": "array" + } + }, + "required": [ + "users" + ], + "type": "object" + }, + "SndFactorPasswordChallengeConfig.WithStatus": { + "properties": { + "lockStatus": { + "$ref": "#/components/schemas/LockStatus" + }, + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status", + "lockStatus" + ], + "type": "object" + }, + "SndFactorPasswordChallengeConfig.WithStatusNoLock": { + "properties": { + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "Sso": { + "properties": { + "issuer": { + "type": "string" + }, + "nameid": { + "type": "string" + } + }, + "required": [ + "issuer", + "nameid" + ], + "type": "object" + }, + "SsoSettings": { + "properties": { + "default_sso_code": { + "$ref": "#/components/schemas/UUID" + } + }, + "type": "object" + }, + "SupportedProtocolUpdate": { + "properties": { + "supported_protocols": { + "items": { + "$ref": "#/components/schemas/BaseProtocol" + }, + "type": "array" + } + }, + "required": [ + "supported_protocols" + ], + "type": "object" + }, + "SystemSettings": { + "properties": { + "setEnableMls": { + "description": "Whether MLS is enabled or not", + "type": "boolean" + }, + "setRestrictUserCreation": { + "description": "Do not allow certain user creation flows", + "type": "boolean" + } + }, + "required": [ + "setRestrictUserCreation", + "setEnableMls" + ], + "type": "object" + }, + "SystemSettingsPublic": { + "properties": { + "setRestrictUserCreation": { + "description": "Do not allow certain user creation flows", + "type": "boolean" + } + }, + "required": [ + "setRestrictUserCreation" + ], + "type": "object" + }, + "Team": { + "description": "`binding` is deprecated, and should be ignored. The non-binding teams API is not used (and will not be supported from API version V4 onwards), and `binding` will always be `true`.", + "properties": { + "binding": { + "$ref": "#/components/schemas/TeamBinding" + }, + "creator": { + "$ref": "#/components/schemas/UUID" + }, + "icon": { + "$ref": "#/components/schemas/Icon" + }, + "icon_key": { + "type": "string" + }, + "id": { + "$ref": "#/components/schemas/UUID" + }, + "name": { + "type": "string" + }, + "splash_screen": { + "$ref": "#/components/schemas/Icon" + } + }, + "required": [ + "id", + "creator", + "name", + "icon" + ], + "type": "object" + }, + "TeamBinding": { + "deprecated": true, + "description": "Deprecated, please ignore.", + "enum": [ + true, + false + ], + "type": "boolean" + }, + "TeamContact": { + "properties": { + "accent_id": { + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + }, + "created_at": { + "$ref": "#/components/schemas/UTCTime" + }, + "email": { + "$ref": "#/components/schemas/Email" + }, + "email_unvalidated": { + "$ref": "#/components/schemas/Email" + }, + "handle": { + "type": "string" + }, + "id": { + "$ref": "#/components/schemas/UUID" + }, + "managed_by": { + "$ref": "#/components/schemas/ManagedBy" + }, + "name": { + "type": "string" + }, + "role": { + "$ref": "#/components/schemas/Role" + }, + "saml_idp": { + "type": "string" + }, + "scim_external_id": { + "type": "string" + }, + "sso": { + "$ref": "#/components/schemas/Sso" + }, + "team": { + "$ref": "#/components/schemas/UUID" + } + }, + "required": [ + "id", + "name" + ], + "type": "object" + }, + "TeamConversation": { + "description": "Team conversation data", + "properties": { + "conversation": { + "$ref": "#/components/schemas/UUID" + }, + "managed": { + "description": "This field MUST NOT be used by clients. It is here only for backwards compatibility of the interface." + } + }, + "required": [ + "conversation", + "managed" + ], + "type": "object" + }, + "TeamConversationList": { + "description": "Team conversation list", + "properties": { + "conversations": { + "items": { + "$ref": "#/components/schemas/TeamConversation" + }, + "type": "array" + } + }, + "required": [ + "conversations" + ], + "type": "object" + }, + "TeamDeleteData": { + "properties": { + "password": { + "maxLength": 1024, + "minLength": 6, + "type": "string" + }, + "verification_code": { + "$ref": "#/components/schemas/ASCII" + } + }, + "type": "object" + }, + "TeamMember": { + "description": "team member data", + "properties": { + "created_at": { + "$ref": "#/components/schemas/UTCTime" + }, + "created_by": { + "$ref": "#/components/schemas/UUID" + }, + "legalhold_status": { + "$ref": "#/components/schemas/UserLegalHoldStatus" + }, + "permissions": { + "$ref": "#/components/schemas/Permissions" + }, + "user": { + "$ref": "#/components/schemas/UUID" + } + }, + "required": [ + "user" + ], + "type": "object" + }, + "TeamMemberDeleteData": { + "description": "Data for a team member deletion request in case of binding teams.", + "properties": { + "password": { + "description": "The account password to authorise the deletion.", + "maxLength": 1024, + "minLength": 6, + "type": "string" + } + }, + "type": "object" + }, + "TeamMemberList": { + "description": "list of team member", + "properties": { + "hasMore": { + "$ref": "#/components/schemas/ListType" + }, + "members": { + "description": "the array of team members", + "items": { + "$ref": "#/components/schemas/TeamMember" + }, + "type": "array" + } + }, + "required": [ + "members", + "hasMore" + ], + "type": "object" + }, + "TeamMembersPage": { + "properties": { + "hasMore": { + "type": "boolean" + }, + "members": { + "items": { + "$ref": "#/components/schemas/TeamMember" + }, + "type": "array" + }, + "pagingState": { + "$ref": "#/components/schemas/TeamMembers_PagingState" + } + }, + "required": [ + "members", + "hasMore", + "pagingState" + ], + "type": "object" + }, + "TeamMembers_PagingState": { + "type": "string" + }, + "TeamSearchVisibility": { + "description": "value of visibility", + "enum": [ + "standard", + "no-name-outside-team" + ], + "type": "string" + }, + "TeamSearchVisibilityView": { + "description": "Search visibility value for the team", + "properties": { + "search_visibility": { + "$ref": "#/components/schemas/TeamSearchVisibility" + } + }, + "required": [ + "search_visibility" + ], + "type": "object" + }, + "TeamSize": { + "description": "A simple object with a total number of team members.", + "properties": { + "teamSize": { + "description": "Team size.", + "exclusiveMinimum": false, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "teamSize" + ], + "type": "object" + }, + "TeamUpdateData": { + "properties": { + "icon": { + "$ref": "#/components/schemas/Icon" + }, + "icon_key": { + "maxLength": 256, + "minLength": 1, + "type": "string" + }, + "name": { + "maxLength": 256, + "minLength": 1, + "type": "string" + }, + "splash_screen": { + "$ref": "#/components/schemas/Icon" + } + }, + "type": "object" + }, + "Time": { + "properties": { + "time": { + "$ref": "#/components/schemas/UTCTime" + } + }, + "required": [ + "time" + ], + "type": "object" + }, + "TokenType": { + "enum": [ + "Bearer" + ], + "type": "string" + }, + "Transport": { + "description": "Transport", + "enum": [ + "GCM", + "APNS", + "APNS_SANDBOX" + ], + "type": "string" + }, + "TurnURI": { + "type": "string" + }, + "TypingData": { + "properties": { + "status": { + "$ref": "#/components/schemas/TypingStatus" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "TypingStatus": { + "enum": [ + "started", + "stopped" + ], + "type": "string" + }, + "URIRef Absolute": { + "description": "URL of the invitation link to be sent to the invitee", + "type": "string" + }, + "UTCTime": { + "example": "2021-05-12T10:52:02.671Z", + "format": "yyyy-mm-ddThh:MM:ss.qqq", + "type": "string" + }, + "UUID": { + "description": "The OAuth client's ID", + "example": "99db9768-04e3-4b5d-9268-831b6a25c4ab", + "format": "uuid", + "type": "string" + }, + "Unnamed": { + "properties": { + "created_at": { + "$ref": "#/components/schemas/UTCTime" + }, + "created_by": { + "$ref": "#/components/schemas/UUID" + }, + "permissions": { + "$ref": "#/components/schemas/Permissions" + }, + "user": { + "$ref": "#/components/schemas/UUID" + } + }, + "required": [ + "user", + "permissions" + ], + "type": "object" + }, + "UpdateBotPrekeys": { + "properties": { + "prekeys": { + "items": { + "$ref": "#/components/schemas/Prekey" + }, + "type": "array" + } + }, + "required": [ + "prekeys" + ], + "type": "object" + }, + "UpdateClient": { + "properties": { + "capabilities": { + "description": "Hints provided by the client for the backend so it can behave in a backwards-compatible way.", + "items": { + "$ref": "#/components/schemas/ClientCapability" + }, + "type": "array" + }, + "label": { + "description": "A new name for this client.", + "type": "string" + }, + "lastkey": { + "$ref": "#/components/schemas/Prekey" + }, + "mls_public_keys": { + "$ref": "#/components/schemas/MLSPublicKeys" + }, + "prekeys": { + "description": "New prekeys for other clients to establish OTR sessions.", + "items": { + "$ref": "#/components/schemas/Prekey" + }, + "type": "array" + } + }, + "type": "object" + }, + "UpdateProvider": { + "properties": { + "description": { + "type": "string" + }, + "name": { + "maxLength": 128, + "minLength": 1, + "type": "string" + }, + "url": { + "$ref": "#/components/schemas/HttpsUrl" + } + }, + "type": "object" + }, + "UpdateService": { + "properties": { + "assets": { + "items": { + "$ref": "#/components/schemas/UserAsset" + }, + "type": "array" + }, + "description": { + "maxLength": 1024, + "minLength": 1, + "type": "string" + }, + "name": { + "maxLength": 128, + "minLength": 1, + "type": "string" + }, + "summary": { + "maxLength": 128, + "minLength": 1, + "type": "string" + }, + "tags": { + "items": { + "$ref": "#/components/schemas/" + }, + "maxItems": 3, + "minItems": 1, + "type": "array" + } + }, + "type": "object" + }, + "UpdateServiceConn": { + "properties": { + "auth_tokens": { + "items": { + "$ref": "#/components/schemas/ASCII" + }, + "maxItems": 2, + "minItems": 1, + "type": "array" + }, + "base_url": { + "$ref": "#/components/schemas/HttpsUrl" + }, + "enabled": { + "type": "boolean" + }, + "password": { + "maxLength": 1024, + "minLength": 6, + "type": "string" + }, + "public_keys": { + "items": { + "$ref": "#/components/schemas/ServiceKeyPEM" + }, + "maxItems": 2, + "minItems": 1, + "type": "array" + } + }, + "required": [ + "password" + ], + "type": "object" + }, + "UpdateServiceWhitelist": { + "properties": { + "id": { + "$ref": "#/components/schemas/UUID" + }, + "provider": { + "$ref": "#/components/schemas/UUID" + }, + "whitelisted": { + "type": "boolean" + } + }, + "required": [ + "provider", + "id", + "whitelisted" + ], + "type": "object" + }, + "User": { + "properties": { + "accent_id": { + "format": "int32", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + }, + "assets": { + "items": { + "$ref": "#/components/schemas/UserAsset" + }, + "type": "array" + }, + "deleted": { + "type": "boolean" + }, + "email": { + "$ref": "#/components/schemas/Email" + }, + "expires_at": { + "$ref": "#/components/schemas/UTCTime" + }, + "handle": { + "$ref": "#/components/schemas/Handle" + }, + "id": { + "$ref": "#/components/schemas/UUID" + }, + "locale": { + "$ref": "#/components/schemas/Locale" + }, + "managed_by": { + "$ref": "#/components/schemas/ManagedBy" + }, + "name": { + "maxLength": 128, + "minLength": 1, + "type": "string" + }, + "phone": { + "$ref": "#/components/schemas/PhoneNumber" + }, + "picture": { + "$ref": "#/components/schemas/Pict" + }, + "qualified_id": { + "$ref": "#/components/schemas/Qualified_UserId" + }, + "service": { + "$ref": "#/components/schemas/ServiceRef" + }, + "sso_id": { + "$ref": "#/components/schemas/UserSSOId" + }, + "supported_protocols": { + "items": { + "$ref": "#/components/schemas/BaseProtocol" + }, + "type": "array" + }, + "team": { + "$ref": "#/components/schemas/UUID" + } + }, + "required": [ + "id", + "qualified_id", + "name", + "accent_id", + "locale" + ], + "type": "object" + }, + "UserAsset": { + "properties": { + "key": { + "$ref": "#/components/schemas/AssetKey" + }, + "size": { + "$ref": "#/components/schemas/AssetSize" + }, + "type": { + "$ref": "#/components/schemas/AssetType" + } + }, + "required": [ + "key", + "type" + ], + "type": "object" + }, + "UserClientMap": { + "additionalProperties": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "type": "object" + }, + "UserClientPrekeyMap": { + "additionalProperties": { + "additionalProperties": { + "properties": { + "id": { + "maximum": 65535, + "minimum": 0, + "type": "integer" + }, + "key": { + "type": "string" + } + }, + "required": [ + "id", + "key" + ], + "type": "object" + }, + "type": "object" + }, + "example": { + "1d51e2d6-9c70-605f-efc8-ff85c3dabdc7": { + "44901fb0712e588f": { + "id": 1, + "key": "pQABAQECoQBYIOjl7hw0D8YRNq..." + } + } + }, + "type": "object" + }, + "UserClients": { + "additionalProperties": { + "items": { + "description": "A 64-bit unsigned integer, represented as a hexadecimal numeral. Any valid hexadecimal numeral is accepted, but the backend will only produce representations with lowercase digits and no leading zeros", + "type": "string" + }, + "type": "array" + }, + "description": "Map of user id to list of client ids.", + "example": { + "1d51e2d6-9c70-605f-efc8-ff85c3dabdc7": [ + "60f85e4b15ad3786", + "6e323ab31554353b" + ] + }, + "type": "object" + }, + "UserConnection": { + "properties": { + "conversation": { + "$ref": "#/components/schemas/UUID" + }, + "from": { + "$ref": "#/components/schemas/UUID" + }, + "last_update": { + "$ref": "#/components/schemas/UTCTime" + }, + "qualified_conversation": { + "$ref": "#/components/schemas/Qualified_ConvId" + }, + "qualified_to": { + "$ref": "#/components/schemas/Qualified_UserId" + }, + "status": { + "$ref": "#/components/schemas/Relation" + }, + "to": { + "$ref": "#/components/schemas/UUID" + } + }, + "required": [ + "from", + "qualified_to", + "status", + "last_update" + ], + "type": "object" + }, + "UserIdList": { + "properties": { + "user_ids": { + "items": { + "$ref": "#/components/schemas/UUID" + }, + "type": "array" + } + }, + "required": [ + "user_ids" + ], + "type": "object" + }, + "UserLegalHoldStatus": { + "description": "The state of Legal Hold compliance for the member", + "enum": [ + "enabled", + "pending", + "disabled", + "no_consent" + ], + "type": "string" + }, + "UserLegalHoldStatusResponse": { + "properties": { + "client": { + "$ref": "#/components/schemas/Id" + }, + "last_prekey": { + "$ref": "#/components/schemas/Prekey" + }, + "status": { + "$ref": "#/components/schemas/UserLegalHoldStatus" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "UserMap_Set_PubClient": { + "additionalProperties": { + "items": { + "$ref": "#/components/schemas/PubClient" + }, + "type": "array", + "uniqueItems": true + }, + "description": "Map of UserId to (Set PubClient)", + "example": { + "1d51e2d6-9c70-605f-efc8-ff85c3dabdc7": [ + { + "class": "legalhold", + "id": "d0" + } + ] + }, + "type": "object" + }, + "UserProfile": { + "properties": { + "accent_id": { + "format": "int32", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + }, + "assets": { + "items": { + "$ref": "#/components/schemas/UserAsset" + }, + "type": "array" + }, + "deleted": { + "type": "boolean" + }, + "email": { + "$ref": "#/components/schemas/Email" + }, + "expires_at": { + "$ref": "#/components/schemas/UTCTime" + }, + "handle": { + "$ref": "#/components/schemas/Handle" + }, + "id": { + "$ref": "#/components/schemas/UUID" + }, + "legalhold_status": { + "$ref": "#/components/schemas/UserLegalHoldStatus" + }, + "name": { + "maxLength": 128, + "minLength": 1, + "type": "string" + }, + "picture": { + "$ref": "#/components/schemas/Pict" + }, + "qualified_id": { + "$ref": "#/components/schemas/Qualified_UserId" + }, + "service": { + "$ref": "#/components/schemas/ServiceRef" + }, + "supported_protocols": { + "items": { + "$ref": "#/components/schemas/BaseProtocol" + }, + "type": "array" + }, + "team": { + "$ref": "#/components/schemas/UUID" + } + }, + "required": [ + "qualified_id", + "name", + "accent_id", + "legalhold_status" + ], + "type": "object" + }, + "UserSSOId": { + "properties": { + "scim_external_id": { + "type": "string" + }, + "subject": { + "type": "string" + }, + "tenant": { + "type": "string" + } + }, + "type": "object" + }, + "UserUpdate": { + "properties": { + "accent_id": { + "format": "int32", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + }, + "assets": { + "items": { + "$ref": "#/components/schemas/UserAsset" + }, + "type": "array" + }, + "name": { + "maxLength": 128, + "minLength": 1, + "type": "string" + }, + "picture": { + "$ref": "#/components/schemas/Pict" + } + }, + "type": "object" + }, + "ValidateSAMLEmailsConfig.WithStatus": { + "properties": { + "lockStatus": { + "$ref": "#/components/schemas/LockStatus" + }, + "status": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "ttl": { + "example": "unlimited", + "maximum": 18446744073709551615, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status", + "lockStatus" + ], + "type": "object" + }, + "VerificationAction": { + "enum": [ + "create_scim_token", + "login", + "delete_team" + ], + "type": "string" + }, + "VerifyDeleteUser": { + "description": "Data for verifying an account deletion.", + "properties": { + "code": { + "$ref": "#/components/schemas/ASCII" + }, + "key": { + "$ref": "#/components/schemas/ASCII" + } + }, + "required": [ + "key", + "code" + ], + "type": "object" + }, + "VersionInfo": { + "example": { + "development": [ + 6 + ], + "domain": "example.com", + "federation": false, + "supported": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6 + ] + }, + "properties": { + "development": { + "items": { + "$ref": "#/components/schemas/VersionNumber" + }, + "type": "array" + }, + "domain": { + "$ref": "#/components/schemas/Domain" + }, + "federation": { + "type": "boolean" + }, + "supported": { + "items": { + "$ref": "#/components/schemas/VersionNumber" + }, + "type": "array" + } + }, + "required": [ + "supported", + "development", + "federation", + "domain" + ], + "type": "object" + }, + "VersionNumber": { + "enum": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6 + ], + "type": "integer" + }, + "ViewLegalHoldService": { + "properties": { + "settings": { + "$ref": "#/components/schemas/ViewLegalHoldServiceInfo" + }, + "status": { + "$ref": "#/components/schemas/LHServiceStatus" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "ViewLegalHoldServiceInfo": { + "properties": { + "auth_token": { + "$ref": "#/components/schemas/ASCII" + }, + "base_url": { + "$ref": "#/components/schemas/HttpsUrl" + }, + "fingerprint": { + "$ref": "#/components/schemas/Fingerprint" + }, + "public_key": { + "$ref": "#/components/schemas/ServiceKeyPEM" + }, + "team_id": { + "$ref": "#/components/schemas/UUID" + } + }, + "required": [ + "team_id", + "base_url", + "fingerprint", + "auth_token", + "public_key" + ], + "type": "object" + }, + "WireIdP": { + "properties": { + "apiVersion": { + "$ref": "#/components/schemas/WireIdPAPIVersion" + }, + "handle": { + "type": "string" + }, + "oldIssuers": { + "items": { + "type": "string" + }, + "type": "array" + }, + "replacedBy": { + "$ref": "#/components/schemas/UUID" + }, + "team": { + "$ref": "#/components/schemas/UUID" + } + }, + "required": [ + "team", + "oldIssuers", + "handle" + ], + "type": "object" + }, + "WireIdPAPIVersion": { + "enum": [ + "WireIdPAPIV1", + "WireIdPAPIV2" + ], + "type": "string" + }, + "XmlText": { + "properties": { + "fromXmlText": { + "type": "string" + } + }, + "required": [ + "fromXmlText" + ], + "type": "object" + }, + "new-otr-message": { + "properties": { + "data": { + "type": "string" + }, + "native_priority": { + "$ref": "#/components/schemas/Priority" + }, + "native_push": { + "type": "boolean" + }, + "recipients": { + "$ref": "#/components/schemas/UserClientMap" + }, + "report_missing": { + "items": { + "$ref": "#/components/schemas/UUID" + }, + "type": "array" + }, + "sender": { + "description": "A 64-bit unsigned integer, represented as a hexadecimal numeral. Any valid hexadecimal numeral is accepted, but the backend will only produce representations with lowercase digits and no leading zeros", + "type": "string" + }, + "transient": { + "type": "boolean" + } + }, + "required": [ + "sender", + "recipients" + ], + "type": "object" + } + }, + "securitySchemes": { + "ZAuth": { + "description": "Must be a token retrieved by calling 'POST /login' or 'POST /access'. It must be presented in this format: 'Bearer \\'.", + "in": "header", + "name": "Authorization", + "type": "apiKey" + } + } + }, + "info": { + "description": "## Authentication / Authorization\n\nThe end-points in this API support differing authorization protocols:\nsome are unauthenticated (`/api-version`, `/login`), some require\n[zauth](), and some support both [zauth]() and [oauth]().\n\nThe end-points that require zauth are labelled so in the description\nbelow. The end-points that support oauth as an alternative to zauth\nhave the required oauth scopes listed in the same description.\n\nFuther reading:\n- https://docs.wire.com/developer/reference/oauth.html\n- https://github.com/wireapp/wire-server/blob/develop/libs/wire-api/src/Wire/API/Routes/Public.hs (search for HasSwagger instances)\n- `curl https://staging-nginz-https.zinfra.io/v4/api/swagger.json | jq '.security, .securityDefinitions`\n\n### SSO Endpoints\n\n#### Overview\n\n`/sso/metadata` will be requested by the IdPs to learn how to talk to wire.\n\n`/sso/initiate-login`, `/sso/finalize-login` are for the SAML authentication handshake performed by a user in order to log into wire. They are not exactly standard in their details: they may return HTML or XML; redirect to error URLs instead of throwing errors, etc.\n\n`/identity-providers` end-points are for use in the team settings page when IdPs are registered. They talk json.\n\n\n#### Configuring IdPs\n\nIdPs usually allow you to copy the metadata into your clipboard. That should contain all the details you need to post the idp in your team under `/identity-providers`. (Team id is derived from the authorization credentials of the request.)\n\n##### okta.com\n\nOkta will ask you to provide two URLs when you set it up for talking to wireapp:\n\n1. The `Single sign on URL`. This is the end-point that accepts the user's credentials after successful authentication against the IdP. Choose `/sso/finalize-login` with schema and hostname of the wire server you are configuring.\n\n2. The `Audience URI`. You can find this in the metadata returned by the `/sso/metadata` end-point. It is the contents of the `md:OrganizationURL` element.\n\n##### centrify.com\n\nCentrify allows you to upload the metadata xml document that you get from the `/sso/metadata` end-point. You can also enter the metadata url and have centrify retrieve the xml, but to guarantee integrity of the setup, the metadata should be copied from the team settings page and pasted into the centrify setup page without any URL indirections.\n\n## Federation errors\n\nEndpoints involving federated calls to other domains can return some extra failure responses, common to all endpoints. Instead of listing them as possible responses for each endpoint, we document them here.\n\nFor errors that are more likely to be transient, we suggest clients to retry whatever request resulted in the error. Transient errors are indicated explicitly below.\n\n**Note**: when a failure occurs as a result of making a federated RPC to another backend, the error response contains the following extra fields:\n\n - `type`: \"federation\" (just the literal string in quotes, which can be used as an error type identifier when parsing errors)\n - `domain`: the target backend of the RPC that failed;\n - `path`: the path of the RPC that failed.\n\n### Domain errors\n\nErrors in this category result from trying to communicate with a backend that is considered non-existent or invalid. They can result from invalid user input or client issues, but they can also be a symptom of misconfiguration in one or multiple backends. These errors have a 4xx status code.\n\n - **Remote backend not found** (status: 422, label: `invalid-domain`): This backend attempted to contact a backend which does not exist or is not properly configured. For the most part, clients can consider this error equivalent to a domain not existing, although it should be noted that certain mistakes in the DNS configuration on a remote backend can lead to the backend not being recognized, and hence to this error. It is therefore not advisable to take any destructive action upon encountering this error, such as deleting remote users from conversations.\n - **Federation denied locally** (status: 400, label: `federation-denied`): This backend attempted an RPC to a non-whitelisted backend. Similar considerations as for the previous error apply.\n - **Federation not enabled** (status: 400, label: `federation-not-enabled`): Federation has not been configured for this backend. This will happen if a federation-aware client tries to talk to a backend for which federation is disabled, or if federation was disabled on the backend after reaching a federation-specific state (e.g. conversations with remote users). There is no way to cleanly recover from these errors at this point.\n\n### Local federation errors\n\nAn error in this category likely indicates an issue with the configuration of federation on the local backend. Possibly transient errors are indicated explicitly below. All these errors have a 500 status code.\n\n - **Federation unavailable** (status: 500, label: `federation-not-available`): Federation is configured for this backend, but the local federator cannot be reached. This can be transient, so clients should retry the request.\n - **Federation not implemented** (status: 500, label: `federation-not-implemented`): Federated behaviour for a certain endpoint is not yet implemented.\n - **Federator discovery failed** (status: 400, label: `discovery-failure`): A DNS error occurred during discovery of a remote backend. This can be transient, so clients should retry the request.\n - **Local federation error** (status: 500, label: `federation-local-error`): An error occurred in the communication between this backend and its local federator. These errors are most likely caused by bugs in the backend, and should be reported as such.\n\n### Remote federation errors\n\nErrors in this category are returned in case of communication issues between the local backend and a remote one, or if the remote side encountered an error while processing an RPC. Some errors in this category might be caused by incorrect client behaviour, wrong user input, or incorrect certificate configuration. Possibly transient errors are indicated explicitly. We use non-standard 5xx status codes for these errors.\n\n - **HTTP2 error** (status: 533, label: `federation-http2-error`): The current federator encountered an error when making an HTTP2 request to a remote one. Check the error message for more details.\n - **Connection refused** (status: 521, label: `federation-connection-refused`): The local federator could not connect to a remote one. This could be transient, so clients should retry the request.\n - **TLS failure**: (status: 525, label: `federation-tls-error`): An error occurred during the TLS handshake between the local federator and a remote one. This is most likely due to an issue with the certificate on the remote end.\n - **Remote federation error** (status: 533, label: `federation-remote-error`): The remote backend could not process a request coming from this backend. Check the error message for more details.\n - **Version negotiation error** (status: 533, label: `federation-version-error`): The remote backend returned invalid version information.\n\n### Backend compatibility errors\n\nAn error in this category will be returned when this backend makes an invalid or unsupported RPC to another backend. This can indicate some incompatibility between backends or a backend bug. These errors are unlikely to be transient, so retrying requests is *not* advised.\n\n - **Version mismatch** (status: 531, label: `federation-version-mismatch`): A remote backend is running an unsupported version of the federator.\n - **Invalid content type** (status: 533, label: `federation-invalid-content-type`): An RPC to another backend returned with an invalid content type.\n - **Unsupported content type** (status: 533, label: `federation-unsupported-content-type`): An RPC to another backend returned with an unsupported content type.\n", + "title": "Wire-Server API", + "version": "" + }, + "openapi": "3.0.0", + "paths": { + "/": { + "get": { + "description": " [internal route ID: \"get-services-tags\"]\n\n", + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ServiceTagList" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "access-denied", + "message": "Access denied." + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Access denied. (label: `access-denied`)" + } + }, + "summary": "Get services tags" + } + }, + "/access": { + "post": { + "description": " [internal route ID: \"access\"]\n\nYou can provide only a cookie or a cookie and token. Every other combination is invalid. Access tokens can be given as query parameter or authorisation header, with the latter being preferred.", + "parameters": [ + { + "in": "query", + "name": "client_id", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccessToken" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/AccessToken" + } + } + }, + "description": "OK", + "headers": { + "Set-Cookie": { + "schema": { + "type": "string" + } + } + } + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-credentials", + "message": "Authentication failed" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-credentials" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Authentication failed (label: `invalid-credentials`)" + } + }, + "summary": "Obtain an access tokens for a cookie" + } + }, + "/access/logout": { + "post": { + "description": " [internal route ID: \"logout\"]\n\nCalling this endpoint will effectively revoke the given cookie and subsequent calls to /access with the same cookie will result in a 403.", + "responses": { + "200": { + "description": "Logout" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-credentials", + "message": "Authentication failed" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-credentials" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Authentication failed (label: `invalid-credentials`)" + } + }, + "summary": "Log out in order to remove a cookie from the server" + } + }, + "/access/self/email": { + "put": { + "description": " [internal route ID: \"change-self-email\"]\n\n", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/EmailUpdate" + } + } + }, + "required": true + }, + "responses": { + "202": { + "content": { + "application/json": { + "schema": { + "example": [], + "items": {}, + "maxItems": 0, + "type": "array" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": [], + "items": {}, + "maxItems": 0, + "type": "array" + } + } + }, + "description": "Update accepted and pending activation of the new email" + }, + "204": { + "content": { + "application/json": { + "schema": { + "example": [], + "items": {}, + "maxItems": 0, + "type": "array" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": [], + "items": {}, + "maxItems": 0, + "type": "array" + } + } + }, + "description": "No update, current and new email address are the same" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-email", + "message": "Invalid e-mail address." + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-email" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid e-mail address. (label: `invalid-email`) or `body`" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-credentials", + "message": "Authentication failed" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-credentials", + "blacklisted-phone", + "blacklisted-email" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Authentication failed (label: `invalid-credentials`)\n\nThe given phone number has been blacklisted due to suspected abuse or a complaint (label: `blacklisted-phone`)\n\nThe given e-mail address has been blacklisted due to a permanent bounce or a complaint. (label: `blacklisted-email`)" + }, + "409": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 409, + "label": "key-exists", + "message": "The given e-mail address or phone number is in use." + }, + "properties": { + "code": { + "enum": [ + 409 + ], + "type": "integer" + }, + "label": { + "enum": [ + "key-exists" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The given e-mail address or phone number is in use. (label: `key-exists`)" + } + }, + "summary": "Change your email address" + } + }, + "/activate": { + "get": { + "description": " [internal route ID: \"get-activate\"]\n\nSee also 'POST /activate' which has a larger feature set.", + "parameters": [ + { + "description": "Activation key", + "in": "query", + "name": "key", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Activation code", + "in": "query", + "name": "code", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ActivationResponse" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ActivationResponse" + } + } + }, + "description": "Activation successful.\n\nActivation successful. (Dry run)\n\nActivation successful." + }, + "204": { + "description": "A recent activation was already successful." + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-phone", + "message": "Invalid mobile phone number" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-phone", + "invalid-email" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `code` or `key`\n\nInvalid mobile phone number (label: `invalid-phone`)\n\nInvalid e-mail address. (label: `invalid-email`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "invalid-code", + "message": "Invalid activation code" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-code" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid activation code (label: `invalid-code`)\n\nUser does not exist (label: `invalid-code`)" + }, + "409": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 409, + "label": "key-exists", + "message": "The given e-mail address or phone number is in use." + }, + "properties": { + "code": { + "enum": [ + 409 + ], + "type": "integer" + }, + "label": { + "enum": [ + "key-exists" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The given e-mail address or phone number is in use. (label: `key-exists`)" + } + }, + "summary": "Activate (i.e. confirm) an email address or phone number." + }, + "post": { + "description": " [internal route ID: \"post-activate\"]\n\nActivation only succeeds once and the number of failed attempts for a valid key is limited.", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Activate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ActivationResponse" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ActivationResponse" + } + } + }, + "description": "Activation successful.\n\nActivation successful. (Dry run)\n\nActivation successful." + }, + "204": { + "description": "A recent activation was already successful." + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-phone", + "message": "Invalid mobile phone number" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-phone", + "invalid-email" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nInvalid mobile phone number (label: `invalid-phone`)\n\nInvalid e-mail address. (label: `invalid-email`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "invalid-code", + "message": "Invalid activation code" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-code" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid activation code (label: `invalid-code`)\n\nUser does not exist (label: `invalid-code`)" + }, + "409": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 409, + "label": "key-exists", + "message": "The given e-mail address or phone number is in use." + }, + "properties": { + "code": { + "enum": [ + 409 + ], + "type": "integer" + }, + "label": { + "enum": [ + "key-exists" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The given e-mail address or phone number is in use. (label: `key-exists`)" + } + }, + "summary": "Activate (i.e. confirm) an email address or phone number." + } + }, + "/activate/send": { + "post": { + "description": " [internal route ID: \"post-activate-send\"]\n\n", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/SendActivationCode" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Activation code sent." + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-phone", + "message": "Invalid mobile phone number" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-phone", + "invalid-email" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nInvalid mobile phone number (label: `invalid-phone`)\n\nInvalid e-mail address. (label: `invalid-email`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "blacklisted-phone", + "message": "The given phone number has been blacklisted due to suspected abuse or a complaint" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "blacklisted-phone", + "blacklisted-email" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The given phone number has been blacklisted due to suspected abuse or a complaint (label: `blacklisted-phone`)\n\nThe given e-mail address has been blacklisted due to a permanent bounce or a complaint. (label: `blacklisted-email`)" + }, + "409": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 409, + "label": "key-exists", + "message": "The given e-mail address or phone number is in use." + }, + "properties": { + "code": { + "enum": [ + 409 + ], + "type": "integer" + }, + "label": { + "enum": [ + "key-exists" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The given e-mail address or phone number is in use. (label: `key-exists`)" + }, + "451": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 451, + "label": "domain-blocked-for-registration", + "message": "[Customer extension] the email domain example.com that you are attempting to register a user with has been blocked for creating wire users. Please contact your IT department." + }, + "properties": { + "code": { + "enum": [ + 451 + ], + "type": "integer" + }, + "label": { + "enum": [ + "domain-blocked-for-registration" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "[Customer extension] the email domain example.com that you are attempting to register a user with has been blocked for creating wire users. Please contact your IT department. (label: `domain-blocked-for-registration`)" + } + }, + "summary": "Send (or resend) an email or phone activation code." + } + }, + "/api-version": { + "get": { + "description": " [internal route ID: \"get-version\"]\n\n", + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/VersionInfo" + } + } + }, + "description": "" + } + } + } + }, + "/assets": { + "post": { + "requestBody": { + "content": { + "multipart/mixed": { + "schema": { + "$ref": "#/components/schemas/AssetSource" + } + } + }, + "description": "A body with content type `multipart/mixed body`. The first section's content type should be `application/json`. The second section's content type should be always be `application/octet-stream`. Other content types will be ignored by the server." + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Asset" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Asset" + } + } + }, + "description": "Asset posted", + "headers": { + "Location": { + "description": "Asset location", + "schema": { + "format": "url", + "type": "string" + } + } + } + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-length", + "message": "Invalid content length" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-length" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nInvalid content length (label: `invalid-length`)" + }, + "413": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 413, + "label": "client-error", + "message": "Asset too large" + }, + "properties": { + "code": { + "enum": [ + 413 + ], + "type": "integer" + }, + "label": { + "enum": [ + "client-error" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Asset too large (label: `client-error`)" + } + }, + "summary": "Upload an asset" + } + }, + "/assets/{key_domain}/{key}": { + "delete": { + "description": "**Note**: only local assets can be deleted.", + "parameters": [ + { + "in": "path", + "name": "key_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Asset deleted" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "unauthorised", + "message": "Unauthorised operation" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "unauthorised" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Unauthorised operation (label: `unauthorised`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Asset not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`key_domain` or `key` not found\n\nAsset not found (label: `not-found`)" + } + }, + "summary": "Delete an asset" + }, + "get": { + "description": "**Note**: local assets result in a redirect, while remote assets are streamed directly.Calls federation service cargohold on stream-asset
Calls federation service cargohold on get-asset", + "parameters": [ + { + "in": "path", + "name": "key_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "key", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "header", + "name": "Asset-Token", + "required": false, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "asset_token", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Asset returned directly with content type `application/octet-stream`" + }, + "302": { + "description": "Asset found", + "headers": { + "Location": { + "description": "Asset location", + "schema": { + "format": "url", + "type": "string" + } + } + } + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Asset not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`key_domain` or `key` or Asset not found (label: `not-found`)\n\nAsset not found (label: `not-found`)" + } + }, + "summary": "Download an asset" + } + }, + "/assets/{key}/token": { + "delete": { + "description": "**Note**: deleting the token makes the asset public.", + "parameters": [ + { + "in": "path", + "name": "key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Asset token deleted" + } + }, + "summary": "Delete an asset token" + }, + "post": { + "parameters": [ + { + "in": "path", + "name": "key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/NewAssetToken" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "unauthorised", + "message": "Unauthorised operation" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "unauthorised" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Unauthorised operation (label: `unauthorised`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Asset not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`key` not found\n\nAsset not found (label: `not-found`)" + } + }, + "summary": "Renew an asset token" + } + }, + "/await": { + "get": { + "description": " [internal route ID: \"await-notifications\"]\n\n", + "externalDocs": { + "description": "RFC 6455", + "url": "https://datatracker.ietf.org/doc/html/rfc6455" + }, + "parameters": [ + { + "description": "Client ID", + "in": "query", + "name": "client", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "101": { + "description": "Connection upgraded." + }, + "426": { + "description": "Upgrade required." + } + }, + "summary": "Establish websocket connection" + } + }, + "/bot/assets": { + "post": { + "requestBody": { + "content": { + "multipart/mixed": { + "schema": { + "$ref": "#/components/schemas/AssetSource" + } + } + }, + "description": "A body with content type `multipart/mixed body`. The first section's content type should be `application/json`. The second section's content type should be always be `application/octet-stream`. Other content types will be ignored by the server." + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Asset" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Asset" + } + } + }, + "description": "Asset posted", + "headers": { + "Location": { + "description": "Asset location", + "schema": { + "format": "url", + "type": "string" + } + } + } + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-length", + "message": "Invalid content length" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-length" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nInvalid content length (label: `invalid-length`)" + }, + "413": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 413, + "label": "client-error", + "message": "Asset too large" + }, + "properties": { + "code": { + "enum": [ + 413 + ], + "type": "integer" + }, + "label": { + "enum": [ + "client-error" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Asset too large (label: `client-error`)" + } + }, + "summary": "Upload an asset" + } + }, + "/bot/assets/{key}": { + "delete": { + "parameters": [ + { + "in": "path", + "name": "key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Asset deleted" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "unauthorised", + "message": "Unauthorised operation" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "unauthorised" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Unauthorised operation (label: `unauthorised`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Asset not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`key` not found\n\nAsset not found (label: `not-found`)" + } + }, + "summary": "Delete an asset" + }, + "get": { + "parameters": [ + { + "in": "path", + "name": "key", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "header", + "name": "Asset-Token", + "required": false, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "asset_token", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "302": { + "description": "Asset found", + "headers": { + "Location": { + "description": "Asset location", + "schema": { + "format": "url", + "type": "string" + } + } + } + }, + "404": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Asset not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Asset not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`key` or Asset not found (label: `not-found`)" + } + }, + "summary": "Download an asset" + } + }, + "/bot/client": { + "get": { + "description": " [internal route ID: \"bot-get-client\"]\n\n", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Client" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Client" + } + } + }, + "description": "Client found" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "access-denied", + "message": "Access denied." + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Access denied. (label: `access-denied`)" + }, + "404": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 404, + "label": "client-not-found", + "message": "Client not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "client-not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "client-not-found", + "message": "Client not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "client-not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Client not found (label: `client-not-found`)\n\nClient not found (label: `client-not-found`)" + } + }, + "summary": "Get client for bot" + } + }, + "/bot/client/prekeys": { + "get": { + "description": " [internal route ID: \"bot-list-prekeys\"]\n\n", + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "items": { + "maximum": 65535, + "minimum": 0, + "type": "integer" + }, + "type": "array" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "access-denied", + "message": "Access denied." + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Access denied. (label: `access-denied`)" + } + }, + "summary": "List prekeys for bot" + }, + "post": { + "description": " [internal route ID: \"bot-update-prekeys\"]\n\n", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/UpdateBotPrekeys" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "access-denied", + "message": "Access denied." + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Access denied. (label: `access-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "client-not-found", + "message": "Client not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "client-not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Client not found (label: `client-not-found`)" + } + }, + "summary": "Update prekeys for bot" + } + }, + "/bot/messages": { + "post": { + "description": " [internal route ID: \"post-bot-message-unqualified\"]\n\nCalls federation service brig on get-user-clients
Calls federation service galley on on-message-sent", + "parameters": [ + { + "in": "query", + "name": "ignore_missing", + "required": false, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "report_missing", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/new-otr-message" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClientMismatch" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ClientMismatch" + } + } + }, + "description": "Message sent" + }, + "403": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 403, + "label": "unknown-client", + "message": "Unknown Client" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "unknown-client", + "missing-legalhold-consent-old-clients", + "missing-legalhold-consent" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "unknown-client", + "message": "Unknown Client" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "unknown-client", + "missing-legalhold-consent-old-clients", + "missing-legalhold-consent" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Unknown Client (label: `unknown-client`)\n\nFailed to connect to a user or to invite a user to a group because somebody is under legalhold and somebody else has old clients that do not support legalhold's UI requirements (label: `missing-legalhold-consent-old-clients`)\n\nFailed to connect to a user or to invite a user to a group because somebody is under legalhold and somebody else has not granted consent (label: `missing-legalhold-consent`)" + }, + "404": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Conversation not found (label: `no-conversation`)\n\nConversation not found (label: `no-conversation`)" + }, + "412": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClientMismatch" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ClientMismatch" + } + } + }, + "description": "Missing clients" + } + } + } + }, + "/bot/self": { + "delete": { + "description": " [internal route ID: \"bot-delete-self\"]\n\n", + "responses": { + "200": { + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-bot", + "message": "The targeted user is not a bot." + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-bot", + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The targeted user is not a bot. (label: `invalid-bot`)\n\nAccess denied. (label: `access-denied`)" + } + }, + "summary": "Delete self" + }, + "get": { + "description": " [internal route ID: \"bot-get-self\"]\n\n", + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/UserProfile" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "access-denied", + "message": "Access denied." + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Access denied. (label: `access-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "User not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "User not found (label: `not-found`)" + } + }, + "summary": "Get self" + } + }, + "/bot/users": { + "get": { + "description": " [internal route ID: \"bot-list-users\"]\n\n", + "parameters": [ + { + "in": "query", + "name": "ids", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "items": { + "$ref": "#/components/schemas/BotUserView" + }, + "type": "array" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "access-denied", + "message": "Access denied." + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Access denied. (label: `access-denied`)" + } + }, + "summary": "List users" + } + }, + "/bot/users/prekeys": { + "post": { + "description": " [internal route ID: \"bot-claim-users-prekeys\"]\n\n", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/UserClients" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/UserClientPrekeyMap" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "missing-legalhold-consent", + "message": "Failed to connect to a user or to invite a user to a group because somebody is under legalhold and somebody else has not granted consent" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "missing-legalhold-consent", + "missing-legalhold-consent-old-clients", + "too-many-clients", + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Failed to connect to a user or to invite a user to a group because somebody is under legalhold and somebody else has not granted consent (label: `missing-legalhold-consent`)\n\nFailed to connect to a user or to invite a user to a group because somebody is under legalhold and somebody else has old clients that do not support legalhold's UI requirements (label: `missing-legalhold-consent-old-clients`)\n\nToo many clients (label: `too-many-clients`)\n\nAccess denied. (label: `access-denied`)" + } + }, + "summary": "Claim users prekeys" + } + }, + "/bot/users/{User ID}/clients": { + "get": { + "description": " [internal route ID: \"bot-get-user-clients\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "User ID", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "items": { + "$ref": "#/components/schemas/PubClient" + }, + "type": "array" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "access-denied", + "message": "Access denied." + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Access denied. (label: `access-denied`)" + } + }, + "summary": "Get user clients" + } + }, + "/broadcast/otr/messages": { + "post": { + "description": " [internal route ID: \"post-otr-broadcast-unqualified\"]\n\nThis endpoint ensures that the list of clients is correct and only sends the message if the list is correct.\nTo override this, the endpoint accepts two query params:\n- `ignore_missing`: Can be 'true' 'false' or a comma separated list of user IDs.\n - When 'true' all missing clients are ignored.\n - When 'false' all missing clients are reported.\n - When comma separated list of user-ids, only clients for listed users are ignored.\n- `report_missing`: Can be 'true' 'false' or a comma separated list of user IDs.\n - When 'true' all missing clients are reported.\n - When 'false' all missing clients are ignored.\n - When comma separated list of user-ids, only clients for listed users are reported.\n\nApart from these, the request body also accepts `report_missing` which can only be a list of user ids and behaves the same way as the query parameter.\n\nAll three of these should be considered mutually exclusive. The server however does not error if more than one is specified, it reads them in this order of precedence:\n- `report_missing` in the request body has highest precedence.\n- `ignore_missing` in the query param is the next.\n- `report_missing` in the query param has the lowest precedence.\n\nThis endpoint can lead to OtrMessageAdd event being sent to the recipients.\n\n**NOTE:** The protobuf definitions of the request body can be found at https://github.com/wireapp/generic-message-proto/blob/master/proto/otr.proto.", + "parameters": [ + { + "in": "query", + "name": "ignore_missing", + "required": false, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "report_missing", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/new-otr-message" + } + }, + "application/x-protobuf": { + "schema": { + "$ref": "#/components/schemas/new-otr-message" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClientMismatch" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ClientMismatch" + } + } + }, + "description": "Message sent" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "too-many-users-to-broadcast", + "message": "Too many users to fan out the broadcast event to" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "too-many-users-to-broadcast" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body` or `report_missing` or `ignore_missing`\n\nToo many users to fan out the broadcast event to (label: `too-many-users-to-broadcast`)" + }, + "403": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 403, + "label": "unknown-client", + "message": "Unknown Client" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "unknown-client", + "missing-legalhold-consent-old-clients", + "missing-legalhold-consent" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "unknown-client", + "message": "Unknown Client" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "unknown-client", + "missing-legalhold-consent-old-clients", + "missing-legalhold-consent" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Unknown Client (label: `unknown-client`)\n\nFailed to connect to a user or to invite a user to a group because somebody is under legalhold and somebody else has old clients that do not support legalhold's UI requirements (label: `missing-legalhold-consent-old-clients`)\n\nFailed to connect to a user or to invite a user to a group because somebody is under legalhold and somebody else has not granted consent (label: `missing-legalhold-consent`)" + }, + "404": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation", + "non-binding-team", + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Conversation not found (label: `no-conversation`)\n\nNot a member of a binding team (label: `non-binding-team`)\n\nTeam not found (label: `no-team`)" + }, + "412": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClientMismatch" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ClientMismatch" + } + } + }, + "description": "Missing clients" + } + }, + "summary": "Broadcast an encrypted message to all team members and all contacts (accepts JSON or Protobuf)" + } + }, + "/broadcast/proteus/messages": { + "post": { + "description": " [internal route ID: \"post-proteus-broadcast\"]\n\nThis endpoint ensures that the list of clients is correct and only sends the message if the list is correct.\nTo override this, the endpoint accepts `client_mismatch_strategy` in the body. It can have these values:\n- `report_all`: When set, the message is not sent if any clients are missing. The missing clients are reported in the response.\n- `ignore_all`: When set, no checks about missing clients are carried out.\n- `report_only`: Takes a list of qualified UserIDs. If any clients of the listed users are missing, the message is not sent. The missing clients are reported in the response.\n- `ignore_only`: Takes a list of qualified UserIDs. If any clients of the non-listed users are missing, the message is not sent. The missing clients are reported in the response.\n\nThe sending of messages in a federated conversation could theoretically fail partially. To make this case unlikely, the backend first gets a list of clients from all the involved backends and then tries to send a message. So, if any backend is down, the message is not propagated to anyone. But the actual message fan out to multiple backends could still fail partially. This type of failure is reported as a 201, the clients for which the message sending failed are part of the response body.\n\nThis endpoint can lead to OtrMessageAdd event being sent to the recipients.\n\n**NOTE:** The protobuf definitions of the request body can be found at https://github.com/wireapp/generic-message-proto/blob/master/proto/otr.proto.", + "requestBody": { + "content": { + "application/x-protobuf": { + "schema": { + "$ref": "#/components/schemas/QualifiedNewOtrMessage" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MessageSendingStatus" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/MessageSendingStatus" + } + } + }, + "description": "Message sent" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "too-many-users-to-broadcast", + "message": "Too many users to fan out the broadcast event to" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "too-many-users-to-broadcast" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nToo many users to fan out the broadcast event to (label: `too-many-users-to-broadcast`)" + }, + "403": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 403, + "label": "unknown-client", + "message": "Unknown Client" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "unknown-client", + "missing-legalhold-consent-old-clients", + "missing-legalhold-consent" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "unknown-client", + "message": "Unknown Client" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "unknown-client", + "missing-legalhold-consent-old-clients", + "missing-legalhold-consent" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Unknown Client (label: `unknown-client`)\n\nFailed to connect to a user or to invite a user to a group because somebody is under legalhold and somebody else has old clients that do not support legalhold's UI requirements (label: `missing-legalhold-consent-old-clients`)\n\nFailed to connect to a user or to invite a user to a group because somebody is under legalhold and somebody else has not granted consent (label: `missing-legalhold-consent`)" + }, + "404": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation", + "non-binding-team", + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Conversation not found (label: `no-conversation`)\n\nNot a member of a binding team (label: `non-binding-team`)\n\nTeam not found (label: `no-team`)" + }, + "412": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MessageSendingStatus" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/MessageSendingStatus" + } + } + }, + "description": "Missing clients" + } + }, + "summary": "Post an encrypted message to all team members and all contacts (accepts only Protobuf)" + } + }, + "/calls/config": { + "get": { + "deprecated": true, + "description": " [internal route ID: \"get-calls-config\"]\n\n", + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/RTCConfiguration" + } + } + }, + "description": "" + } + }, + "summary": "Retrieve TURN server addresses and credentials for IP addresses, scheme `turn` and transport `udp` only (deprecated)" + } + }, + "/calls/config/v2": { + "get": { + "description": " [internal route ID: \"get-calls-config-v2\"]\n\n", + "parameters": [ + { + "description": "Limit resulting list. Allowed values [1..10]", + "in": "query", + "name": "limit", + "required": false, + "schema": { + "maximum": 10, + "minimum": 1, + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/RTCConfiguration" + } + } + }, + "description": "" + } + }, + "summary": "Retrieve all TURN server addresses and credentials. Clients are expected to do a DNS lookup to resolve the IP addresses of the given hostnames " + } + }, + "/clients": { + "get": { + "description": " [internal route ID: \"list-clients\"]\n\n", + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "items": { + "$ref": "#/components/schemas/Client" + }, + "type": "array" + } + } + }, + "description": "" + } + }, + "summary": "List the registered clients" + }, + "post": { + "description": " [internal route ID: \"add-client\"]\n\n", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/NewClient" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Client" + } + } + }, + "description": "", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "bad-request", + "message": "Malformed prekeys uploaded" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "bad-request" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nMalformed prekeys uploaded (label: `bad-request`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "code-authentication-required", + "message": "Code authentication is required" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "code-authentication-required", + "code-authentication-failed", + "missing-auth", + "too-many-clients" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Code authentication is required (label: `code-authentication-required`)\n\nCode authentication failed (label: `code-authentication-failed`)\n\nRe-authentication via password required (label: `missing-auth`)\n\nToo many clients (label: `too-many-clients`)" + } + }, + "summary": "Register a new client" + } + }, + "/clients/{cid}/access-token": { + "post": { + "description": " [internal route ID: \"create-access-token\"]\n\nCreate an JWT DPoP access token for the client CSR, given a JWT DPoP proof, specified in the `DPoP` header. The access token will be returned as JWT DPoP token in the `DPoP` header.", + "parameters": [ + { + "description": "ClientId", + "in": "path", + "name": "cid", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "header", + "name": "DPoP", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DPoPAccessTokenResponse" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/DPoPAccessTokenResponse" + } + } + }, + "description": "Access token created", + "headers": { + "Cache-Control": { + "schema": { + "type": "string" + } + } + } + } + }, + "summary": "Create a JWT DPoP access token" + } + }, + "/clients/{client}": { + "delete": { + "description": " [internal route ID: \"delete-client\"]\n\n", + "parameters": [ + { + "description": "ClientId", + "in": "path", + "name": "client", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/DeleteClient" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Client deleted" + } + }, + "summary": "Delete an existing client" + }, + "get": { + "description": " [internal route ID: \"get-client\"]\n\n", + "parameters": [ + { + "description": "ClientId", + "in": "path", + "name": "client", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Client" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Client" + } + } + }, + "description": "Client found" + }, + "404": { + "description": "`client` or Client not found(**Note**: This error has an empty body for legacy reasons)" + } + }, + "summary": "Get a registered client by ID" + }, + "put": { + "description": " [internal route ID: \"update-client\"]\n\n", + "parameters": [ + { + "description": "ClientId", + "in": "path", + "name": "client", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/UpdateClient" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Client updated" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "bad-request", + "message": "Malformed prekeys uploaded" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "bad-request" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nMalformed prekeys uploaded (label: `bad-request`)" + } + }, + "summary": "Update a registered client" + } + }, + "/clients/{client}/capabilities": { + "get": { + "description": " [internal route ID: \"get-client-capabilities\"]\n\n", + "parameters": [ + { + "description": "ClientId", + "in": "path", + "name": "client", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ClientCapabilityList" + } + } + }, + "description": "" + } + }, + "summary": "Read back what the client has been posting about itself" + } + }, + "/clients/{client}/nonce": { + "get": { + "description": " [internal route ID: \"get-nonce\"]\n\nGet a new nonce for a client CSR, specified in the response header `Replay-Nonce` as a uuidv4 in base64url encoding.", + "parameters": [ + { + "description": "ClientId", + "in": "path", + "name": "client", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No Content", + "headers": { + "Cache-Control": { + "schema": { + "type": "string" + } + }, + "Replay-Nonce": { + "schema": { + "type": "string" + } + } + } + } + }, + "summary": "Get a new nonce for a client CSR" + }, + "head": { + "description": " [internal route ID: \"head-nonce\"]\n\nGet a new nonce for a client CSR, specified in the response header `Replay-Nonce` as a uuidv4 in base64url encoding.", + "parameters": [ + { + "description": "ClientId", + "in": "path", + "name": "client", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "No Content", + "headers": { + "Cache-Control": { + "schema": { + "type": "string" + } + }, + "Replay-Nonce": { + "schema": { + "type": "string" + } + } + } + } + }, + "summary": "Get a new nonce for a client CSR" + } + }, + "/clients/{client}/prekeys": { + "get": { + "description": " [internal route ID: \"get-client-prekeys\"]\n\n", + "parameters": [ + { + "description": "ClientId", + "in": "path", + "name": "client", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "items": { + "maximum": 65535, + "minimum": 0, + "type": "integer" + }, + "type": "array" + } + } + }, + "description": "" + } + }, + "summary": "List the remaining prekey IDs of a client" + } + }, + "/connections/{uid_domain}/{uid}": { + "get": { + "description": " [internal route ID: \"get-connection\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "uid_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "User Id", + "in": "path", + "name": "uid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserConnection" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/UserConnection" + } + } + }, + "description": "Connection found" + }, + "404": { + "description": "`uid_domain` or `uid` or Connection not found(**Note**: This error has an empty body for legacy reasons)" + } + }, + "summary": "Get an existing connection to another user (local or remote)" + }, + "post": { + "description": " [internal route ID: \"create-connection\"]\n\nYou can have no more than 1000 connections in accepted or sent state
Calls federation service brig on send-connection-action
Calls federation service brig on get-users-by-ids", + "parameters": [ + { + "in": "path", + "name": "uid_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "User Id", + "in": "path", + "name": "uid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserConnection" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/UserConnection" + } + } + }, + "description": "Connection existed" + }, + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserConnection" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/UserConnection" + } + } + }, + "description": "Connection was created" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-user", + "message": "Invalid user" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-user" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid user (label: `invalid-user`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-identity", + "message": "The user has no verified identity (email or phone number)" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-identity", + "connection-limit", + "missing-legalhold-consent", + "missing-legalhold-consent-old-clients" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The user has no verified identity (email or phone number) (label: `no-identity`)\n\nToo many sent/accepted connections (label: `connection-limit`)\n\nFailed to connect to a user or to invite a user to a group because somebody is under legalhold and somebody else has not granted consent (label: `missing-legalhold-consent`)\n\nFailed to connect to a user or to invite a user to a group because somebody is under legalhold and somebody else has old clients that do not support legalhold's UI requirements (label: `missing-legalhold-consent-old-clients`)" + } + }, + "summary": "Create a connection to another user" + }, + "put": { + "description": " [internal route ID: \"update-connection\"]\n\nCalls federation service brig on send-connection-action
Calls federation service brig on get-users-by-ids", + "parameters": [ + { + "in": "path", + "name": "uid_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "User Id", + "in": "path", + "name": "uid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ConnectionUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserConnection" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/UserConnection" + } + } + }, + "description": "Connection updated" + }, + "204": { + "description": "Connection unchanged" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-user", + "message": "Invalid user" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-user" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nInvalid user (label: `invalid-user`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-identity", + "message": "The user has no verified identity (email or phone number)" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-identity", + "bad-conn-update", + "not-connected", + "connection-limit", + "missing-legalhold-consent", + "missing-legalhold-consent-old-clients" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The user has no verified identity (email or phone number) (label: `no-identity`)\n\nInvalid status transition (label: `bad-conn-update`)\n\nUsers are not connected (label: `not-connected`)\n\nToo many sent/accepted connections (label: `connection-limit`)\n\nFailed to connect to a user or to invite a user to a group because somebody is under legalhold and somebody else has not granted consent (label: `missing-legalhold-consent`)\n\nFailed to connect to a user or to invite a user to a group because somebody is under legalhold and somebody else has old clients that do not support legalhold's UI requirements (label: `missing-legalhold-consent-old-clients`)" + } + }, + "summary": "Update a connection to another user" + } + }, + "/conversations": { + "post": { + "description": " [internal route ID: \"create-group-conversation\"]\n\nThis returns 201 when a new conversation is created, and 200 when the conversation already existed
Calls federation service galley on on-conversation-updated
Calls federation service galley on on-conversation-created
Calls federation service brig on get-not-fully-connected-backends
Calls federation service brig on api-version", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/NewConv" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Conversation" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Conversation" + } + } + }, + "description": "Conversation existed", + "headers": { + "Location": { + "description": "Conversation ID", + "schema": { + "format": "uuid", + "type": "string" + } + } + } + }, + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateGroupConversation" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/CreateGroupConversation" + } + } + }, + "description": "Conversation created", + "headers": { + "Location": { + "description": "Conversation ID", + "schema": { + "format": "uuid", + "type": "string" + } + } + } + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "mls-not-enabled", + "message": "MLS is not configured on this backend. See docs.wire.com for instructions on how to enable it" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "mls-not-enabled", + "non-empty-member-list" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nMLS is not configured on this backend. See docs.wire.com for instructions on how to enable it (label: `mls-not-enabled`)\n\nAttempting to add group members outside MLS (label: `non-empty-member-list`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "missing-legalhold-consent", + "message": "Failed to connect to a user or to invite a user to a group because somebody is under legalhold and somebody else has not granted consent" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "missing-legalhold-consent", + "operation-denied", + "no-team-member", + "not-connected", + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Failed to connect to a user or to invite a user to a group because somebody is under legalhold and somebody else has not granted consent (label: `missing-legalhold-consent`)\n\nInsufficient permissions (label: `operation-denied`)\n\nRequesting user is not a team member (label: `no-team-member`)\n\nUsers are not connected (label: `not-connected`)\n\nConversation access denied (label: `access-denied`)" + }, + "409": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "properties": { + "non_federating_backends": { + "items": { + "$ref": "#/components/schemas/Domain" + }, + "type": "array" + } + }, + "required": [ + "non_federating_backends" + ], + "type": "object" + } + } + }, + "description": "Adding members to the conversation is not possible because the backends involved do not form a fully connected graph" + }, + "533": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "properties": { + "unreachable_backends": { + "items": { + "$ref": "#/components/schemas/Domain" + }, + "type": "array" + } + }, + "required": [ + "unreachable_backends" + ], + "type": "object" + } + } + }, + "description": "Some domains are unreachable" + } + }, + "summary": "Create a new conversation" + } + }, + "/conversations/code-check": { + "post": { + "description": " [internal route ID: \"code-check\"]\n\nIf the guest links team feature is disabled, this will fail with 404 CodeNotFound.Note that this is currently inconsistent (for backwards compatibility reasons) with `POST /conversations/join` which responds with 409 GuestLinksDisabled if guest links are disabled.", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ConversationCode" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Valid" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-conversation-password", + "message": "Invalid conversation password" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-conversation-password" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid conversation password (label: `invalid-conversation-password`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation", + "no-conversation-code" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Conversation not found (label: `no-conversation`)\n\nConversation code not found (label: `no-conversation-code`)" + } + }, + "summary": "Check validity of a conversation code." + } + }, + "/conversations/join": { + "get": { + "description": " [internal route ID: \"get-conversation-by-reusable-code\"]\n\n", + "parameters": [ + { + "in": "query", + "name": "key", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "code", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ConversationCoverView" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "access-denied", + "invalid-conversation-password" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nConversation access denied (label: `access-denied`)\n\nInvalid conversation password (label: `invalid-conversation-password`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation", + "no-conversation-code" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Conversation not found (label: `no-conversation`)\n\nConversation code not found (label: `no-conversation-code`)" + }, + "409": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 409, + "label": "guest-links-disabled", + "message": "The guest link feature is disabled and all guest links have been revoked" + }, + "properties": { + "code": { + "enum": [ + 409 + ], + "type": "integer" + }, + "label": { + "enum": [ + "guest-links-disabled" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The guest link feature is disabled and all guest links have been revoked (label: `guest-links-disabled`)" + } + }, + "summary": "Get limited conversation information by key/code pair" + }, + "post": { + "description": " [internal route ID: \"join-conversation-by-code-unqualified\"]\n\nIf the guest links team feature is disabled, this will fail with 409 GuestLinksDisabled.Note that this is currently inconsistent (for backwards compatibility reasons) with `POST /conversations/code-check` which responds with 404 CodeNotFound if guest links are disabled.Calls federation service galley on on-conversation-updated", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/JoinConversationByCode" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Event" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Event" + } + } + }, + "description": "Conversation joined" + }, + "204": { + "description": "Conversation unchanged" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "too-many-members", + "message": "Maximum number of members per conversation reached" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "too-many-members", + "no-team-member", + "invalid-op", + "access-denied", + "invalid-conversation-password" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Maximum number of members per conversation reached (label: `too-many-members`)\n\nRequesting user is not a team member (label: `no-team-member`)\n\nInvalid operation (label: `invalid-op`)\n\nConversation access denied (label: `access-denied`)\n\nInvalid conversation password (label: `invalid-conversation-password`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation", + "no-conversation-code" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Conversation not found (label: `no-conversation`)\n\nConversation code not found (label: `no-conversation-code`)" + }, + "409": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 409, + "label": "guest-links-disabled", + "message": "The guest link feature is disabled and all guest links have been revoked" + }, + "properties": { + "code": { + "enum": [ + 409 + ], + "type": "integer" + }, + "label": { + "enum": [ + "guest-links-disabled" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The guest link feature is disabled and all guest links have been revoked (label: `guest-links-disabled`)" + } + }, + "summary": "Join a conversation using a reusable code" + } + }, + "/conversations/list": { + "post": { + "description": " [internal route ID: \"list-conversations\"]\n\nCalls federation service galley on get-conversations", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ListConversations" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ConversationsResponse" + } + } + }, + "description": "" + } + }, + "summary": "Get conversation metadata for a list of conversation ids" + } + }, + "/conversations/list-ids": { + "post": { + "description": " [internal route ID: \"list-conversation-ids\"]\n\nThe IDs returned by this endpoint are paginated. To get the first page, make a call with the `paging_state` field set to `null` (or omitted). Whenever the `has_more` field of the response is set to `true`, more results are available, and they can be obtained by calling the endpoint again, but this time passing the value of `paging_state` returned by the previous call. One can continue in this fashion until all results are returned, which is indicated by `has_more` being `false`. Note that `paging_state` should be considered an opaque token. It should not be inspected, or stored, or reused across multiple unrelated invocations of the endpoint.", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/GetPaginated_ConversationIds" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ConversationIds_Page" + } + } + }, + "description": "" + } + }, + "summary": "Get all conversation IDs." + } + }, + "/conversations/mls-self": { + "get": { + "description": " [internal route ID: \"get-mls-self-conversation\"]\n\n", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Conversation" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Conversation" + } + } + }, + "description": "The MLS self-conversation" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "mls-not-enabled", + "message": "MLS is not configured on this backend. See docs.wire.com for instructions on how to enable it" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "mls-not-enabled" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "MLS is not configured on this backend. See docs.wire.com for instructions on how to enable it (label: `mls-not-enabled`)" + } + }, + "summary": "Get the user's MLS self-conversation" + } + }, + "/conversations/one2one": { + "post": { + "description": " [internal route ID: \"create-one-to-one-conversation\"]\n\nCalls federation service galley on on-conversation-created", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/NewConv" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Conversation" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Conversation" + } + } + }, + "description": "Conversation existed", + "headers": { + "Location": { + "description": "Conversation ID", + "schema": { + "format": "uuid", + "type": "string" + } + } + } + }, + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Conversation" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Conversation" + } + } + }, + "description": "Conversation created", + "headers": { + "Location": { + "description": "Conversation ID", + "schema": { + "format": "uuid", + "type": "string" + } + } + } + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "missing-legalhold-consent", + "message": "Failed to connect to a user or to invite a user to a group because somebody is under legalhold and somebody else has not granted consent" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "missing-legalhold-consent", + "operation-denied", + "not-connected", + "no-team-member", + "non-binding-team-members", + "invalid-op", + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Failed to connect to a user or to invite a user to a group because somebody is under legalhold and somebody else has not granted consent (label: `missing-legalhold-consent`)\n\nInsufficient permissions (label: `operation-denied`)\n\nUsers are not connected (label: `not-connected`)\n\nRequesting user is not a team member (label: `no-team-member`)\n\nBoth users must be members of the same binding team (label: `non-binding-team-members`)\n\nInvalid operation (label: `invalid-op`)\n\nConversation access denied (label: `access-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team", + "non-binding-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Team not found (label: `no-team`)\n\nNot a member of a binding team (label: `non-binding-team`)" + }, + "533": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "properties": { + "unreachable_backends": { + "items": { + "$ref": "#/components/schemas/Domain" + }, + "type": "array" + } + }, + "required": [ + "unreachable_backends" + ], + "type": "object" + } + } + }, + "description": "Some domains are unreachable" + } + }, + "summary": "Create a 1:1 conversation" + } + }, + "/conversations/one2one/{usr_domain}/{usr}": { + "get": { + "description": " [internal route ID: \"get-one-to-one-mls-conversation@v6\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "usr_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "usr", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MLSOne2OneConversation_MLSPublicKey" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/MLSOne2OneConversation_MLSPublicKey" + } + } + }, + "description": "MLS 1-1 conversation" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "mls-not-enabled", + "message": "MLS is not configured on this backend. See docs.wire.com for instructions on how to enable it" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "mls-not-enabled" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "MLS is not configured on this backend. See docs.wire.com for instructions on how to enable it (label: `mls-not-enabled`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "not-connected", + "message": "Users are not connected" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-connected" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Users are not connected (label: `not-connected`)" + } + }, + "summary": "Get an MLS 1:1 conversation" + } + }, + "/conversations/self": { + "post": { + "description": " [internal route ID: \"create-self-conversation\"]\n\n", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Conversation" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Conversation" + } + } + }, + "description": "Conversation existed", + "headers": { + "Location": { + "description": "Conversation ID", + "schema": { + "format": "uuid", + "type": "string" + } + } + } + }, + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Conversation" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Conversation" + } + } + }, + "description": "Conversation created", + "headers": { + "Location": { + "description": "Conversation ID", + "schema": { + "format": "uuid", + "type": "string" + } + } + } + } + }, + "summary": "Create a self-conversation" + } + }, + "/conversations/{Conversation ID}/bots": { + "post": { + "description": " [internal route ID: \"add-bot\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "Conversation ID", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/AddBot" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddBotResponse" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/AddBotResponse" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "service-disabled", + "message": "The desired service is currently disabled." + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "service-disabled", + "too-many-members", + "invalid-conversation", + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The desired service is currently disabled. (label: `service-disabled`)\n\nMaximum number of members per conversation reached. (label: `too-many-members`)\n\nThe operation is not allowed in this conversation. (label: `invalid-conversation`)\n\nAccess denied. (label: `access-denied`)" + } + }, + "summary": "Add bot" + } + }, + "/conversations/{Conversation ID}/bots/{Bot ID}": { + "delete": { + "description": " [internal route ID: \"remove-bot\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "Conversation ID", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "in": "path", + "name": "Bot ID", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RemoveBotResponse" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/RemoveBotResponse" + } + } + }, + "description": "User found" + }, + "204": { + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-conversation", + "message": "The operation is not allowed in this conversation." + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-conversation", + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The operation is not allowed in this conversation. (label: `invalid-conversation`)\n\nAccess denied. (label: `access-denied`)" + } + }, + "summary": "Remove bot" + } + }, + "/conversations/{cnv_domain}/{cnv}": { + "get": { + "description": " [internal route ID: \"get-conversation\"]\n\nCalls federation service galley on get-conversations", + "parameters": [ + { + "in": "path", + "name": "cnv_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Conversation" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "access-denied", + "message": "Conversation access denied" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Conversation access denied (label: `access-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv_domain` or `cnv` not found\n\nConversation not found (label: `no-conversation`)" + } + }, + "summary": "Get a conversation by ID" + } + }, + "/conversations/{cnv_domain}/{cnv}/access": { + "put": { + "description": " [internal route ID: \"update-conversation-access\"]\n\nCalls federation service brig on get-users-by-ids
Calls federation service galley on on-mls-message-sent
Calls federation service galley on on-conversation-updated", + "parameters": [ + { + "in": "path", + "name": "cnv_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Conversation ID", + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ConversationAccessData" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Event" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Event" + } + } + }, + "description": "Access updated" + }, + "204": { + "description": "Access unchanged" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-op", + "message": "Invalid target access" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-op", + "access-denied", + "action-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid target access (label: `invalid-op`)\n\nInvalid operation (label: `invalid-op`)\n\nConversation access denied (label: `access-denied`)\n\nInsufficient authorization (missing remove_conversation_member) (label: `action-denied`)\n\nInsufficient authorization (missing modify_conversation_access) (label: `action-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv_domain` or `cnv` not found\n\nConversation not found (label: `no-conversation`)" + } + }, + "summary": "Update access modes for a conversation" + } + }, + "/conversations/{cnv_domain}/{cnv}/groupinfo": { + "get": { + "description": " [internal route ID: \"get-group-info\"]\n\nCalls federation service galley on query-group-info", + "parameters": [ + { + "in": "path", + "name": "cnv_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "message/mls": { + "schema": { + "$ref": "#/components/schemas/GroupInfoData" + } + } + }, + "description": "The group information" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "mls-not-enabled", + "message": "MLS is not configured on this backend. See docs.wire.com for instructions on how to enable it" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "mls-not-enabled" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "MLS is not configured on this backend. See docs.wire.com for instructions on how to enable it (label: `mls-not-enabled`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "mls-missing-group-info", + "message": "The conversation has no group information" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "mls-missing-group-info", + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv_domain` or `cnv` not found\n\nThe conversation has no group information (label: `mls-missing-group-info`)\n\nConversation not found (label: `no-conversation`)" + } + }, + "summary": "Get MLS group information" + } + }, + "/conversations/{cnv_domain}/{cnv}/members": { + "post": { + "description": " [internal route ID: \"add-members-to-conversation\"]\n\nCalls federation service galley on on-mls-message-sent
Calls federation service galley on on-conversation-updated", + "parameters": [ + { + "in": "path", + "name": "cnv_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/InviteQualified" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Event" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Event" + } + } + }, + "description": "Conversation updated" + }, + "204": { + "description": "Conversation unchanged" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "missing-legalhold-consent", + "message": "Failed to connect to a user or to invite a user to a group because somebody is under legalhold and somebody else has not granted consent" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "missing-legalhold-consent", + "not-connected", + "no-team-member", + "access-denied", + "too-many-members", + "invalid-op", + "action-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Failed to connect to a user or to invite a user to a group because somebody is under legalhold and somebody else has not granted consent (label: `missing-legalhold-consent`)\n\nUsers are not connected (label: `not-connected`)\n\nRequesting user is not a team member (label: `no-team-member`)\n\nConversation access denied (label: `access-denied`)\n\nMaximum number of members per conversation reached (label: `too-many-members`)\n\nInvalid operation (label: `invalid-op`)\n\nInsufficient authorization (missing leave_conversation) (label: `action-denied`)\n\nInsufficient authorization (missing add_conversation_member) (label: `action-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv_domain` or `cnv` not found\n\nConversation not found (label: `no-conversation`)" + }, + "409": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "properties": { + "non_federating_backends": { + "items": { + "$ref": "#/components/schemas/Domain" + }, + "type": "array" + } + }, + "required": [ + "non_federating_backends" + ], + "type": "object" + } + } + }, + "description": "Adding members to the conversation is not possible because the backends involved do not form a fully connected graph" + }, + "533": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "properties": { + "unreachable_backends": { + "items": { + "$ref": "#/components/schemas/Domain" + }, + "type": "array" + } + }, + "required": [ + "unreachable_backends" + ], + "type": "object" + } + } + }, + "description": "Some domains are unreachable" + } + }, + "summary": "Add qualified members to an existing conversation." + } + }, + "/conversations/{cnv_domain}/{cnv}/members/{usr_domain}/{usr}": { + "delete": { + "description": " [internal route ID: \"remove-member\"]\n\nCalls federation service brig on get-users-by-ids
Calls federation service galley on on-mls-message-sent
Calls federation service galley on on-conversation-updated
Calls federation service galley on leave-conversation", + "parameters": [ + { + "in": "path", + "name": "cnv_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Conversation ID", + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "in": "path", + "name": "usr_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Target User ID", + "in": "path", + "name": "usr", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Event" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Event" + } + } + }, + "description": "Member removed" + }, + "204": { + "description": "No change" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-op", + "message": "Invalid operation" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-op", + "action-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid operation (label: `invalid-op`)\n\nInsufficient authorization (missing remove_conversation_member) (label: `action-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv_domain` or `cnv` or `usr_domain` or `usr` not found\n\nConversation not found (label: `no-conversation`)" + } + }, + "summary": "Remove a member from a conversation" + }, + "put": { + "description": " [internal route ID: \"update-other-member\"]\n\n**Note**: at least one field has to be provided.Calls federation service brig on get-users-by-ids
Calls federation service galley on on-mls-message-sent
Calls federation service galley on on-conversation-updated", + "parameters": [ + { + "in": "path", + "name": "cnv_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Conversation ID", + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "in": "path", + "name": "usr_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Target User ID", + "in": "path", + "name": "usr", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/OtherMemberUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Membership updated" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-op", + "message": "Invalid operation" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-op", + "action-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid operation (label: `invalid-op`)\n\nInvalid target (label: `invalid-op`)\n\nInsufficient authorization (missing modify_other_conversation_member) (label: `action-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation-member", + "message": "Conversation member not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation-member", + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv_domain` or `cnv` or `usr_domain` or `usr` not found\n\nConversation member not found (label: `no-conversation-member`)\n\nConversation not found (label: `no-conversation`)" + } + }, + "summary": "Update membership of the specified user" + } + }, + "/conversations/{cnv_domain}/{cnv}/message-timer": { + "put": { + "description": " [internal route ID: \"update-conversation-message-timer\"]\n\nCalls federation service brig on get-users-by-ids
Calls federation service galley on on-mls-message-sent
Calls federation service galley on on-conversation-updated", + "parameters": [ + { + "in": "path", + "name": "cnv_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Conversation ID", + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ConversationMessageTimerUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Event" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Event" + } + } + }, + "description": "Message timer updated" + }, + "204": { + "description": "Message timer unchanged" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-op", + "message": "Invalid operation" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-op", + "access-denied", + "action-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid operation (label: `invalid-op`)\n\nConversation access denied (label: `access-denied`)\n\nInsufficient authorization (missing modify_conversation_message_timer) (label: `action-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv_domain` or `cnv` not found\n\nConversation not found (label: `no-conversation`)" + } + }, + "summary": "Update the message timer for a conversation" + } + }, + "/conversations/{cnv_domain}/{cnv}/name": { + "put": { + "description": " [internal route ID: \"update-conversation-name\"]\n\nCalls federation service brig on get-users-by-ids
Calls federation service galley on on-mls-message-sent
Calls federation service galley on on-conversation-updated", + "parameters": [ + { + "in": "path", + "name": "cnv_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Conversation ID", + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ConversationRename" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Event" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Event" + } + } + }, + "description": "Name unchanged" + }, + "204": { + "description": "Name updated" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-op", + "message": "Invalid operation" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-op", + "action-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid operation (label: `invalid-op`)\n\nInsufficient authorization (missing modify_conversation_name) (label: `action-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv_domain` or `cnv` not found\n\nConversation not found (label: `no-conversation`)" + } + }, + "summary": "Update conversation name" + } + }, + "/conversations/{cnv_domain}/{cnv}/proteus/messages": { + "post": { + "description": " [internal route ID: \"post-proteus-message\"]\n\nThis endpoint ensures that the list of clients is correct and only sends the message if the list is correct.\nTo override this, the endpoint accepts `client_mismatch_strategy` in the body. It can have these values:\n- `report_all`: When set, the message is not sent if any clients are missing. The missing clients are reported in the response.\n- `ignore_all`: When set, no checks about missing clients are carried out.\n- `report_only`: Takes a list of qualified UserIDs. If any clients of the listed users are missing, the message is not sent. The missing clients are reported in the response.\n- `ignore_only`: Takes a list of qualified UserIDs. If any clients of the non-listed users are missing, the message is not sent. The missing clients are reported in the response.\n\nThe sending of messages in a federated conversation could theoretically fail partially. To make this case unlikely, the backend first gets a list of clients from all the involved backends and then tries to send a message. So, if any backend is down, the message is not propagated to anyone. But the actual message fan out to multiple backends could still fail partially. This type of failure is reported as a 201, the clients for which the message sending failed are part of the response body.\n\nThis endpoint can lead to OtrMessageAdd event being sent to the recipients.\n\n**NOTE:** The protobuf definitions of the request body can be found at https://github.com/wireapp/generic-message-proto/blob/master/proto/otr.proto.Calls federation service galley on send-message
Calls federation service galley on on-message-sent
Calls federation service brig on get-user-clients", + "parameters": [ + { + "in": "path", + "name": "cnv_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/x-protobuf": { + "schema": { + "$ref": "#/components/schemas/QualifiedNewOtrMessage" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MessageSendingStatus" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/MessageSendingStatus" + } + } + }, + "description": "Message sent" + }, + "403": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 403, + "label": "unknown-client", + "message": "Unknown Client" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "unknown-client", + "missing-legalhold-consent-old-clients", + "missing-legalhold-consent" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "unknown-client", + "message": "Unknown Client" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "unknown-client", + "missing-legalhold-consent-old-clients", + "missing-legalhold-consent" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Unknown Client (label: `unknown-client`)\n\nFailed to connect to a user or to invite a user to a group because somebody is under legalhold and somebody else has old clients that do not support legalhold's UI requirements (label: `missing-legalhold-consent-old-clients`)\n\nFailed to connect to a user or to invite a user to a group because somebody is under legalhold and somebody else has not granted consent (label: `missing-legalhold-consent`)" + }, + "404": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv_domain` or `cnv` or Conversation not found (label: `no-conversation`)" + }, + "412": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MessageSendingStatus" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/MessageSendingStatus" + } + } + }, + "description": "Missing clients" + } + }, + "summary": "Post an encrypted message to a conversation (accepts only Protobuf)" + } + }, + "/conversations/{cnv_domain}/{cnv}/protocol": { + "put": { + "description": " [internal route ID: \"update-conversation-protocol\"]\n\n**Note**: Only proteus->mixed upgrade is supported.", + "parameters": [ + { + "in": "path", + "name": "cnv_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Conversation ID", + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ProtocolUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Event" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Event" + } + } + }, + "description": "Conversation updated" + }, + "204": { + "description": "Conversation unchanged" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "mls-migration-criteria-not-satisfied", + "message": "The migration criteria for mixed to MLS protocol transition are not satisfied for this conversation" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "mls-migration-criteria-not-satisfied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nThe migration criteria for mixed to MLS protocol transition are not satisfied for this conversation (label: `mls-migration-criteria-not-satisfied`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "operation-denied", + "message": "Insufficient permissions" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "operation-denied", + "no-team-member", + "invalid-op", + "action-denied", + "invalid-protocol-transition" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Insufficient permissions (label: `operation-denied`)\n\nRequesting user is not a team member (label: `no-team-member`)\n\nInvalid operation (label: `invalid-op`)\n\nInsufficient authorization (missing leave_conversation) (label: `action-denied`)\n\nProtocol transition is invalid (label: `invalid-protocol-transition`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team", + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv_domain` or `cnv` not found\n\nTeam not found (label: `no-team`)\n\nConversation not found (label: `no-conversation`)" + } + }, + "summary": "Update the protocol of the conversation" + } + }, + "/conversations/{cnv_domain}/{cnv}/receipt-mode": { + "put": { + "description": " [internal route ID: \"update-conversation-receipt-mode\"]\n\nCalls federation service brig on get-users-by-ids
Calls federation service galley on update-conversation
Calls federation service galley on on-mls-message-sent
Calls federation service galley on on-conversation-updated", + "parameters": [ + { + "in": "path", + "name": "cnv_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Conversation ID", + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ConversationReceiptModeUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Event" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Event" + } + } + }, + "description": "Receipt mode updated" + }, + "204": { + "description": "Receipt mode unchanged" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-op", + "message": "Invalid operation" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-op", + "access-denied", + "action-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid operation (label: `invalid-op`)\n\nConversation access denied (label: `access-denied`)\n\nInsufficient authorization (missing modify_conversation_receipt_mode) (label: `action-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv_domain` or `cnv` not found\n\nConversation not found (label: `no-conversation`)" + } + }, + "summary": "Update receipt mode for a conversation" + } + }, + "/conversations/{cnv_domain}/{cnv}/self": { + "put": { + "description": " [internal route ID: \"update-conversation-self\"]\n\n**Note**: at least one field has to be provided.", + "parameters": [ + { + "in": "path", + "name": "cnv_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Conversation ID", + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/MemberUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Update successful" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv_domain` or `cnv` not found\n\nConversation not found (label: `no-conversation`)" + } + }, + "summary": "Update self membership properties" + } + }, + "/conversations/{cnv_domain}/{cnv}/subconversations/{subconv}": { + "delete": { + "description": " [internal route ID: \"delete-subconversation\"]\n\nCalls federation service galley on delete-sub-conversation", + "parameters": [ + { + "in": "path", + "name": "cnv_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "in": "path", + "name": "subconv", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/DeleteSubConversationRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "example": [], + "items": {}, + "maxItems": 0, + "type": "array" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": [], + "items": {}, + "maxItems": 0, + "type": "array" + } + } + }, + "description": "Deletion successful" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "mls-not-enabled", + "message": "MLS is not configured on this backend. See docs.wire.com for instructions on how to enable it" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "mls-not-enabled" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nMLS is not configured on this backend. See docs.wire.com for instructions on how to enable it (label: `mls-not-enabled`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "access-denied", + "message": "Conversation access denied" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Conversation access denied (label: `access-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv_domain` or `cnv` or `subconv` not found\n\nConversation not found (label: `no-conversation`)" + }, + "409": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 409, + "label": "mls-stale-message", + "message": "The conversation epoch in a message is too old" + }, + "properties": { + "code": { + "enum": [ + 409 + ], + "type": "integer" + }, + "label": { + "enum": [ + "mls-stale-message" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The conversation epoch in a message is too old (label: `mls-stale-message`)" + } + }, + "summary": "Delete an MLS subconversation" + }, + "get": { + "description": " [internal route ID: \"get-subconversation\"]\n\nCalls federation service galley on get-sub-conversation", + "parameters": [ + { + "in": "path", + "name": "cnv_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "in": "path", + "name": "subconv", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicSubConversation" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/PublicSubConversation" + } + } + }, + "description": "Subconversation" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "mls-subconv-unsupported-convtype", + "message": "MLS subconversations are only supported for regular conversations" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "mls-subconv-unsupported-convtype", + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "MLS subconversations are only supported for regular conversations (label: `mls-subconv-unsupported-convtype`)\n\nConversation access denied (label: `access-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv_domain` or `cnv` or `subconv` not found\n\nConversation not found (label: `no-conversation`)" + } + }, + "summary": "Get information about an MLS subconversation" + } + }, + "/conversations/{cnv_domain}/{cnv}/subconversations/{subconv}/groupinfo": { + "get": { + "description": " [internal route ID: \"get-subconversation-group-info\"]\n\nCalls federation service galley on query-group-info", + "parameters": [ + { + "in": "path", + "name": "cnv_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "in": "path", + "name": "subconv", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "message/mls": { + "schema": { + "$ref": "#/components/schemas/GroupInfoData" + } + } + }, + "description": "The group information" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "mls-not-enabled", + "message": "MLS is not configured on this backend. See docs.wire.com for instructions on how to enable it" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "mls-not-enabled" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "MLS is not configured on this backend. See docs.wire.com for instructions on how to enable it (label: `mls-not-enabled`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "mls-missing-group-info", + "message": "The conversation has no group information" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "mls-missing-group-info", + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv_domain` or `cnv` or `subconv` not found\n\nThe conversation has no group information (label: `mls-missing-group-info`)\n\nConversation not found (label: `no-conversation`)" + } + }, + "summary": "Get MLS group information of subconversation" + } + }, + "/conversations/{cnv_domain}/{cnv}/subconversations/{subconv}/self": { + "delete": { + "description": " [internal route ID: \"leave-subconversation\"]\n\nCalls federation service galley on leave-sub-conversation
Calls federation service galley on on-mls-message-sent", + "parameters": [ + { + "in": "path", + "name": "cnv_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "in": "path", + "name": "subconv", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "mls-not-enabled", + "message": "MLS is not configured on this backend. See docs.wire.com for instructions on how to enable it" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "mls-not-enabled", + "mls-protocol-error" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "MLS is not configured on this backend. See docs.wire.com for instructions on how to enable it (label: `mls-not-enabled`)\n\nMLS protocol error (label: `mls-protocol-error`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "access-denied", + "message": "Conversation access denied" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Conversation access denied (label: `access-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv_domain` or `cnv` or `subconv` not found\n\nConversation not found (label: `no-conversation`)" + }, + "409": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 409, + "label": "mls-stale-message", + "message": "The conversation epoch in a message is too old" + }, + "properties": { + "code": { + "enum": [ + 409 + ], + "type": "integer" + }, + "label": { + "enum": [ + "mls-stale-message" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The conversation epoch in a message is too old (label: `mls-stale-message`)" + } + }, + "summary": "Leave an MLS subconversation" + } + }, + "/conversations/{cnv_domain}/{cnv}/typing": { + "post": { + "description": " [internal route ID: \"member-typing-qualified\"]\n\nCalls federation service galley on on-typing-indicator-updated
Calls federation service galley on update-typing-indicator", + "parameters": [ + { + "in": "path", + "name": "cnv_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Conversation ID", + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/TypingData" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Notification sent" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv_domain` or `cnv` not found\n\nConversation not found (label: `no-conversation`)" + } + }, + "summary": "Sending typing notifications" + } + }, + "/conversations/{cnv}": { + "put": { + "deprecated": true, + "description": " [internal route ID: \"update-conversation-name-deprecated\"]\n\nUse `/conversations/:domain/:conv/name` instead.Calls federation service brig on get-users-by-ids
Calls federation service galley on on-mls-message-sent
Calls federation service galley on on-conversation-updated", + "parameters": [ + { + "description": "Conversation ID", + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ConversationRename" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Event" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Event" + } + } + }, + "description": "Name updated" + }, + "204": { + "description": "Name unchanged" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-op", + "message": "Invalid operation" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-op", + "action-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid operation (label: `invalid-op`)\n\nInsufficient authorization (missing modify_conversation_name) (label: `action-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv` not found\n\nConversation not found (label: `no-conversation`)" + } + }, + "summary": "Update conversation name (deprecated)" + } + }, + "/conversations/{cnv}/code": { + "delete": { + "description": " [internal route ID: \"remove-code-unqualified\"]\n\n", + "parameters": [ + { + "description": "Conversation ID", + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Event" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Event" + } + } + }, + "description": "Conversation code deleted." + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "access-denied", + "message": "Conversation access denied" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Conversation access denied (label: `access-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv` not found\n\nConversation not found (label: `no-conversation`)" + } + }, + "summary": "Delete conversation code" + }, + "get": { + "description": " [internal route ID: \"get-code\"]\n\n", + "parameters": [ + { + "description": "Conversation ID", + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConversationCodeInfo" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ConversationCodeInfo" + } + } + }, + "description": "Conversation Code" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "access-denied", + "message": "Conversation access denied" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Conversation access denied (label: `access-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation", + "no-conversation-code" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv` not found\n\nConversation not found (label: `no-conversation`)\n\nConversation code not found (label: `no-conversation-code`)" + }, + "409": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 409, + "label": "guest-links-disabled", + "message": "The guest link feature is disabled and all guest links have been revoked" + }, + "properties": { + "code": { + "enum": [ + 409 + ], + "type": "integer" + }, + "label": { + "enum": [ + "guest-links-disabled" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The guest link feature is disabled and all guest links have been revoked (label: `guest-links-disabled`)" + } + }, + "summary": "Get existing conversation code" + }, + "post": { + "description": " [internal route ID: \"create-conversation-code-unqualified\"]\n\n\nOAuth scope: `write:conversations_code`", + "parameters": [ + { + "description": "Conversation ID", + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/CreateConversationCodeRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConversationCodeInfo" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ConversationCodeInfo" + } + } + }, + "description": "Conversation code already exists." + }, + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Event" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Event" + } + } + }, + "description": "Conversation code created." + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "access-denied", + "message": "Conversation access denied" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Conversation access denied (label: `access-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv` not found\n\nConversation not found (label: `no-conversation`)" + }, + "409": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 409, + "label": "create-conv-code-conflict", + "message": "Conversation code already exists with a different password setting than the requested one." + }, + "properties": { + "code": { + "enum": [ + 409 + ], + "type": "integer" + }, + "label": { + "enum": [ + "create-conv-code-conflict", + "guest-links-disabled" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Conversation code already exists with a different password setting than the requested one. (label: `create-conv-code-conflict`)\n\nThe guest link feature is disabled and all guest links have been revoked (label: `guest-links-disabled`)" + } + }, + "summary": "Create or recreate a conversation code" + } + }, + "/conversations/{cnv}/features/conversationGuestLinks": { + "get": { + "description": " [internal route ID: \"get-conversation-guest-links-status\"]\n\n", + "parameters": [ + { + "description": "Conversation ID", + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/GuestLinksConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "access-denied", + "message": "Conversation access denied" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Conversation access denied (label: `access-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv` not found\n\nConversation not found (label: `no-conversation`)" + } + }, + "summary": "Get the status of the guest links feature for a conversation that potentially has been created by someone from another team." + } + }, + "/conversations/{cnv}/members/{usr}": { + "put": { + "deprecated": true, + "description": " [internal route ID: \"update-other-member-unqualified\"]\n\nUse `PUT /conversations/:cnv_domain/:cnv/members/:usr_domain/:usr` insteadCalls federation service brig on get-users-by-ids
Calls federation service galley on on-mls-message-sent
Calls federation service galley on on-conversation-updated", + "parameters": [ + { + "description": "Conversation ID", + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "description": "Target User ID", + "in": "path", + "name": "usr", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/OtherMemberUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Membership updated" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-op", + "message": "Invalid operation" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-op", + "action-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid operation (label: `invalid-op`)\n\nInvalid target (label: `invalid-op`)\n\nInsufficient authorization (missing modify_other_conversation_member) (label: `action-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation-member", + "message": "Conversation member not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation-member", + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv` or `usr` not found\n\nConversation member not found (label: `no-conversation-member`)\n\nConversation not found (label: `no-conversation`)" + } + }, + "summary": "Update membership of the specified user (deprecated)" + } + }, + "/conversations/{cnv}/message-timer": { + "put": { + "deprecated": true, + "description": " [internal route ID: \"update-conversation-message-timer-unqualified\"]\n\nUse `/conversations/:domain/:cnv/message-timer` instead.Calls federation service brig on get-users-by-ids
Calls federation service galley on on-mls-message-sent
Calls federation service galley on on-conversation-updated", + "parameters": [ + { + "description": "Conversation ID", + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ConversationMessageTimerUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Event" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Event" + } + } + }, + "description": "Message timer updated" + }, + "204": { + "description": "Message timer unchanged" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-op", + "message": "Invalid operation" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-op", + "access-denied", + "action-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid operation (label: `invalid-op`)\n\nConversation access denied (label: `access-denied`)\n\nInsufficient authorization (missing modify_conversation_message_timer) (label: `action-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv` not found\n\nConversation not found (label: `no-conversation`)" + } + }, + "summary": "Update the message timer for a conversation (deprecated)" + } + }, + "/conversations/{cnv}/name": { + "put": { + "deprecated": true, + "description": " [internal route ID: \"update-conversation-name-unqualified\"]\n\nUse `/conversations/:domain/:conv/name` instead.Calls federation service brig on get-users-by-ids
Calls federation service galley on on-mls-message-sent
Calls federation service galley on on-conversation-updated", + "parameters": [ + { + "description": "Conversation ID", + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ConversationRename" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Event" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Event" + } + } + }, + "description": "Name updated" + }, + "204": { + "description": "Name unchanged" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-op", + "message": "Invalid operation" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-op", + "action-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid operation (label: `invalid-op`)\n\nInsufficient authorization (missing modify_conversation_name) (label: `action-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv` not found\n\nConversation not found (label: `no-conversation`)" + } + }, + "summary": "Update conversation name (deprecated)" + } + }, + "/conversations/{cnv}/otr/messages": { + "post": { + "description": " [internal route ID: \"post-otr-message-unqualified\"]\n\nThis endpoint ensures that the list of clients is correct and only sends the message if the list is correct.\nTo override this, the endpoint accepts two query params:\n- `ignore_missing`: Can be 'true' 'false' or a comma separated list of user IDs.\n - When 'true' all missing clients are ignored.\n - When 'false' all missing clients are reported.\n - When comma separated list of user-ids, only clients for listed users are ignored.\n- `report_missing`: Can be 'true' 'false' or a comma separated list of user IDs.\n - When 'true' all missing clients are reported.\n - When 'false' all missing clients are ignored.\n - When comma separated list of user-ids, only clients for listed users are reported.\n\nApart from these, the request body also accepts `report_missing` which can only be a list of user ids and behaves the same way as the query parameter.\n\nAll three of these should be considered mutually exclusive. The server however does not error if more than one is specified, it reads them in this order of precedence:\n- `report_missing` in the request body has highest precedence.\n- `ignore_missing` in the query param is the next.\n- `report_missing` in the query param has the lowest precedence.\n\nThis endpoint can lead to OtrMessageAdd event being sent to the recipients.\n\n**NOTE:** The protobuf definitions of the request body can be found at https://github.com/wireapp/generic-message-proto/blob/master/proto/otr.proto.Calls federation service brig on get-user-clients
Calls federation service galley on on-message-sent", + "parameters": [ + { + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "in": "query", + "name": "ignore_missing", + "required": false, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "report_missing", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/new-otr-message" + } + }, + "application/x-protobuf": { + "schema": { + "$ref": "#/components/schemas/new-otr-message" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClientMismatch" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ClientMismatch" + } + } + }, + "description": "Message sent" + }, + "403": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 403, + "label": "unknown-client", + "message": "Unknown Client" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "unknown-client", + "missing-legalhold-consent-old-clients", + "missing-legalhold-consent" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "unknown-client", + "message": "Unknown Client" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "unknown-client", + "missing-legalhold-consent-old-clients", + "missing-legalhold-consent" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Unknown Client (label: `unknown-client`)\n\nFailed to connect to a user or to invite a user to a group because somebody is under legalhold and somebody else has old clients that do not support legalhold's UI requirements (label: `missing-legalhold-consent-old-clients`)\n\nFailed to connect to a user or to invite a user to a group because somebody is under legalhold and somebody else has not granted consent (label: `missing-legalhold-consent`)" + }, + "404": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv` or Conversation not found (label: `no-conversation`)" + }, + "412": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClientMismatch" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ClientMismatch" + } + } + }, + "description": "Missing clients" + } + }, + "summary": "Post an encrypted message to a conversation (accepts JSON or Protobuf)" + } + }, + "/conversations/{cnv}/receipt-mode": { + "put": { + "deprecated": true, + "description": " [internal route ID: \"update-conversation-receipt-mode-unqualified\"]\n\nUse `PUT /conversations/:domain/:cnv/receipt-mode` instead.Calls federation service brig on get-users-by-ids
Calls federation service galley on update-conversation
Calls federation service galley on on-mls-message-sent
Calls federation service galley on on-conversation-updated", + "parameters": [ + { + "description": "Conversation ID", + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ConversationReceiptModeUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Event" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Event" + } + } + }, + "description": "Receipt mode updated" + }, + "204": { + "description": "Receipt mode unchanged" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-op", + "message": "Invalid operation" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-op", + "access-denied", + "action-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid operation (label: `invalid-op`)\n\nConversation access denied (label: `access-denied`)\n\nInsufficient authorization (missing modify_conversation_receipt_mode) (label: `action-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv` not found\n\nConversation not found (label: `no-conversation`)" + } + }, + "summary": "Update receipt mode for a conversation (deprecated)" + } + }, + "/conversations/{cnv}/roles": { + "get": { + "description": " [internal route ID: \"get-conversation-roles\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ConversationRolesList" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "access-denied", + "message": "Conversation access denied" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Conversation access denied (label: `access-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv` not found\n\nConversation not found (label: `no-conversation`)" + } + }, + "summary": "Get existing roles available for the given conversation" + } + }, + "/conversations/{cnv}/self": { + "get": { + "deprecated": true, + "description": " [internal route ID: \"get-conversation-self-unqualified\"]\n\n", + "parameters": [ + { + "description": "Conversation ID", + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Member" + } + } + }, + "description": "" + } + }, + "summary": "Get self membership properties (deprecated)" + }, + "put": { + "deprecated": true, + "description": " [internal route ID: \"update-conversation-self-unqualified\"]\n\nUse `/conversations/:domain/:conv/self` instead.", + "parameters": [ + { + "description": "Conversation ID", + "in": "path", + "name": "cnv", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/MemberUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Update successful" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`cnv` not found\n\nConversation not found (label: `no-conversation`)" + } + }, + "summary": "Update self membership properties (deprecated)" + } + }, + "/cookies": { + "get": { + "description": " [internal route ID: \"list-cookies\"]\n\n", + "parameters": [ + { + "description": "Filter by label (comma-separated list)", + "in": "query", + "name": "labels", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CookieList" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/CookieList" + } + } + }, + "description": "List of cookies" + } + }, + "summary": "Retrieve the list of cookies currently stored for the user" + } + }, + "/cookies/remove": { + "post": { + "description": " [internal route ID: \"remove-cookies\"]\n\n", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/RemoveCookies" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Cookies revoked" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-credentials", + "message": "Authentication failed" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-credentials" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Authentication failed (label: `invalid-credentials`)" + } + }, + "summary": "Revoke stored cookies" + } + }, + "/custom-backend/by-domain/{domain}": { + "get": { + "description": " [internal route ID: \"get-custom-backend-by-domain\"]\n\n", + "parameters": [ + { + "description": "URL-encoded email domain", + "in": "path", + "name": "domain", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/CustomBackend" + } + } + }, + "description": "" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "custom-backend-not-found", + "message": "Custom backend not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "custom-backend-not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`domain` not found\n\nCustom backend not found (label: `custom-backend-not-found`)" + } + }, + "summary": "Shows information about custom backends related to a given email domain" + } + }, + "/delete": { + "post": { + "description": " [internal route ID: \"verify-delete\"]\n\n", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/VerifyDeleteUser" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Deletion is initiated." + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-code", + "message": "Invalid verification code" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-code" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid verification code (label: `invalid-code`)" + } + }, + "summary": "Verify account deletion with a code." + } + }, + "/feature-configs": { + "get": { + "description": " [internal route ID: \"get-all-feature-configs-for-user\"]\n\nGets feature configs for a user. If the user is a member of a team and has the required permissions, this will return the team's feature configs.If the user is not a member of a team, this will return the personal feature configs (the server defaults).\nOAuth scope: `read:feature_configs`", + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/AllFeatureConfigs" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "operation-denied", + "message": "Insufficient permissions" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "operation-denied", + "no-team-member" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Insufficient permissions (label: `operation-denied`)\n\nRequesting user is not a team member (label: `no-team-member`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Team not found (label: `no-team`)" + } + }, + "summary": "Gets feature configs for a user" + } + }, + "/identity-providers": { + "get": { + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/IdPList" + } + } + }, + "description": "" + } + } + }, + "post": { + "parameters": [ + { + "in": "query", + "name": "replaces", + "required": false, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "in": "query", + "name": "api_version", + "required": false, + "schema": { + "default": "v2", + "enum": [ + "v1", + "v2" + ], + "type": "string" + } + }, + { + "in": "query", + "name": "handle", + "required": false, + "schema": { + "maxLength": 1, + "minLength": 32, + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/IdPMetadataInfo" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/IdPMetadataInfo" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/IdPConfig_WireIdP" + } + } + }, + "description": "" + } + } + } + }, + "/identity-providers/{id}": { + "delete": { + "parameters": [ + { + "in": "path", + "name": "id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "in": "query", + "name": "purge", + "required": false, + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "204": { + "description": "" + } + } + }, + "get": { + "parameters": [ + { + "in": "path", + "name": "id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/IdPConfig_WireIdP" + } + } + }, + "description": "" + } + } + }, + "put": { + "parameters": [ + { + "in": "path", + "name": "id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "in": "query", + "name": "handle", + "required": false, + "schema": { + "maxLength": 1, + "minLength": 32, + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/IdPMetadataInfo" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/IdPMetadataInfo" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/IdPConfig_WireIdP" + } + } + }, + "description": "" + } + } + } + }, + "/identity-providers/{id}/raw": { + "get": { + "parameters": [ + { + "in": "path", + "name": "id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/xml": { + "schema": { + "type": "string" + } + } + }, + "description": "" + } + } + } + }, + "/list-connections": { + "post": { + "description": " [internal route ID: \"list-connections\"]\n\nThe IDs returned by this endpoint are paginated. To get the first page, make a call with the `paging_state` field set to `null` (or omitted). Whenever the `has_more` field of the response is set to `true`, more results are available, and they can be obtained by calling the endpoint again, but this time passing the value of `paging_state` returned by the previous call. One can continue in this fashion until all results are returned, which is indicated by `has_more` being `false`. Note that `paging_state` should be considered an opaque token. It should not be inspected, or stored, or reused across multiple unrelated invocations of the endpoint.", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/GetPaginated_Connections" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Connections_Page" + } + } + }, + "description": "" + } + }, + "summary": "List the connections to other users, including remote users" + } + }, + "/list-users": { + "post": { + "description": " [internal route ID: \"list-users-by-ids-or-handles\"]\n\nThe 'qualified_ids' and 'qualified_handles' parameters are mutually exclusive.Calls federation service brig on get-users-by-ids", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ListUsersQuery" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ListUsersById" + } + } + }, + "description": "" + } + }, + "summary": "List users" + } + }, + "/login": { + "post": { + "description": " [internal route ID: \"login\"]\n\nLogins are throttled at the server's discretion", + "parameters": [ + { + "description": "Request a persistent cookie instead of a session cookie", + "in": "query", + "name": "persist", + "required": false, + "schema": { + "type": "boolean" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Login" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccessToken" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/AccessToken" + } + } + }, + "description": "OK", + "headers": { + "Set-Cookie": { + "schema": { + "type": "string" + } + } + } + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "code-authentication-required", + "message": "Code authentication is required" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "code-authentication-required", + "code-authentication-failed", + "pending-activation", + "suspended", + "invalid-credentials" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Code authentication is required (label: `code-authentication-required`)\n\nCode authentication failed (label: `code-authentication-failed`)\n\nAccount pending activation (label: `pending-activation`)\n\nAccount suspended (label: `suspended`)\n\nAuthentication failed (label: `invalid-credentials`)" + } + }, + "summary": "Authenticate a user to obtain a cookie and first access token" + } + }, + "/login/send": { + "post": { + "description": " [internal route ID: \"send-login-code\"]\n\nThis operation generates and sends a login code via sms for phone login. A login code can be used only once and times out after 10 minutes. Only one login code may be pending at a time. For 2nd factor authentication login with email and password, use the `/verification-code/send` endpoint.", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/SendLoginCode" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginCodeTimeout" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/LoginCodeTimeout" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-phone", + "message": "Invalid mobile phone number" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-phone" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid mobile phone number (label: `invalid-phone`) or `body`" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "password-exists", + "message": "The operation is not permitted because the user has a password set" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "password-exists" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The operation is not permitted because the user has a password set (label: `password-exists`)" + } + }, + "summary": "Send a login code to a verified phone number" + } + }, + "/mls/commit-bundles": { + "post": { + "description": " [internal route ID: \"mls-commit-bundle\"]\n\n\n\n**Note**: this endpoint can execute proposals, and therefore return all possible errors associated with adding or removing members to a conversation, in addition to the ones listed below. See the documentation of [POST /conversations/{cnv}/members/v2](#/default/post_conversations__cnv__members_v2) and [POST /conversations/{cnv_domain}/{cnv}/members/{usr_domain}/{usr}](#/default/delete_conversations__cnv_domain___cnv__members__usr_domain___usr_) for more details on the possible error responses of each type of proposal.
Calls federation service brig on api-version
Calls federation service brig on get-users-by-ids
Calls federation service brig on get-mls-clients
Calls federation service galley on on-conversation-updated
Calls federation service galley on send-mls-commit-bundle
Calls federation service galley on mls-welcome
Calls federation service galley on on-mls-message-sent", + "requestBody": { + "content": { + "message/mls": { + "schema": { + "$ref": "#/components/schemas/CommitBundle" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MLSMessageSendingStatus" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/MLSMessageSendingStatus" + } + } + }, + "description": "Commit accepted and forwarded" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "mls-welcome-mismatch", + "message": "The list of targets of a welcome message does not match the list of new clients in a group" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "mls-welcome-mismatch", + "mls-self-removal-not-allowed", + "mls-protocol-error", + "mls-not-enabled", + "mls-invalid-leaf-node-index", + "mls-group-conversation-mismatch", + "mls-commit-missing-references", + "mls-client-sender-user-mismatch" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nThe list of targets of a welcome message does not match the list of new clients in a group (label: `mls-welcome-mismatch`)\n\nSelf removal from group is not allowed (label: `mls-self-removal-not-allowed`)\n\nMLS protocol error (label: `mls-protocol-error`)\n\nMLS is not configured on this backend. See docs.wire.com for instructions on how to enable it (label: `mls-not-enabled`)\n\nA referenced leaf node index points to a blank or non-existing node (label: `mls-invalid-leaf-node-index`)\n\nConversation ID resolved from Group ID does not match submitted Conversation ID (label: `mls-group-conversation-mismatch`)\n\nThe commit is not referencing all pending proposals (label: `mls-commit-missing-references`)\n\nUser ID resolved from Client ID does not match message's sender user ID (label: `mls-client-sender-user-mismatch`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "mls-subconv-join-parent-missing", + "message": "MLS client cannot join the subconversation because it is not member of the parent conversation" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "mls-subconv-join-parent-missing", + "missing-legalhold-consent", + "legalhold-not-enabled", + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "MLS client cannot join the subconversation because it is not member of the parent conversation (label: `mls-subconv-join-parent-missing`)\n\nFailed to connect to a user or to invite a user to a group because somebody is under legalhold and somebody else has not granted consent (label: `missing-legalhold-consent`)\n\nlegal hold is not enabled for this team (label: `legalhold-not-enabled`)\n\nConversation access denied (label: `access-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "mls-proposal-not-found", + "message": "A proposal referenced in a commit message could not be found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "mls-proposal-not-found", + "no-conversation", + "no-conversation-member" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "A proposal referenced in a commit message could not be found (label: `mls-proposal-not-found`)\n\nConversation not found (label: `no-conversation`)\n\nConversation member not found (label: `no-conversation-member`)" + }, + "409": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "properties": { + "non_federating_backends": { + "items": { + "$ref": "#/components/schemas/Domain" + }, + "type": "array" + } + }, + "required": [ + "non_federating_backends" + ], + "type": "object" + } + } + }, + "description": "Adding members to the conversation is not possible because the backends involved do not form a fully connected graph\n\nThe conversation epoch in a message is too old (label: `mls-stale-message`)\n\nA proposal of type Add or Remove does not apply to the full list of clients for a user (label: `mls-client-mismatch`)" + }, + "422": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 422, + "label": "mls-unsupported-proposal", + "message": "Unsupported proposal type" + }, + "properties": { + "code": { + "enum": [ + 422 + ], + "type": "integer" + }, + "label": { + "enum": [ + "mls-unsupported-proposal", + "mls-unsupported-message" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Unsupported proposal type (label: `mls-unsupported-proposal`)\n\nAttempted to send a message with an unsupported combination of content type and wire format (label: `mls-unsupported-message`)" + }, + "533": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "properties": { + "unreachable_backends": { + "items": { + "$ref": "#/components/schemas/Domain" + }, + "type": "array" + } + }, + "required": [ + "unreachable_backends" + ], + "type": "object" + } + } + }, + "description": "Some domains are unreachable" + } + }, + "summary": "Post a MLS CommitBundle" + } + }, + "/mls/key-packages/claim/{user_domain}/{user}": { + "post": { + "description": " [internal route ID: \"mls-key-packages-claim\"]\n\nOnly key packages for the specified ciphersuite are claimed. For backwards compatibility, the `ciphersuite` parameter is optional, defaulting to ciphersuite 0x0001 when omitted.", + "parameters": [ + { + "in": "path", + "name": "user_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "User Id", + "in": "path", + "name": "user", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "description": "Ciphersuite in hex format (e.g. 0xf031) - default is 0x0001", + "in": "query", + "name": "ciphersuite", + "required": false, + "schema": { + "type": "number" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KeyPackageBundle" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/KeyPackageBundle" + } + } + }, + "description": "Claimed key packages" + } + }, + "summary": "Claim one key package for each client of the given user" + } + }, + "/mls/key-packages/self/{client}": { + "delete": { + "description": " [internal route ID: \"mls-key-packages-delete\"]\n\n", + "parameters": [ + { + "description": "ClientId", + "in": "path", + "name": "client", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Ciphersuite in hex format (e.g. 0xf031) - default is 0x0001", + "in": "query", + "name": "ciphersuite", + "required": false, + "schema": { + "type": "number" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/DeleteKeyPackages" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "OK" + } + }, + "summary": "Delete all key packages for a given ciphersuite and client" + }, + "post": { + "description": " [internal route ID: \"mls-key-packages-upload\"]\n\nThe request body should be a json object containing a list of base64-encoded key packages.", + "parameters": [ + { + "description": "ClientId", + "in": "path", + "name": "client", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/KeyPackageUpload" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Key packages uploaded" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "mls-protocol-error", + "message": "MLS protocol error" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "mls-protocol-error" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nMLS protocol error (label: `mls-protocol-error`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "mls-identity-mismatch", + "message": "Key package credential does not match qualified client ID" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "mls-identity-mismatch" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Key package credential does not match qualified client ID (label: `mls-identity-mismatch`)" + } + }, + "summary": "Upload a fresh batch of key packages" + }, + "put": { + "description": " [internal route ID: \"mls-key-packages-replace\"]\n\nThe request body should be a json object containing a list of base64-encoded key packages. Use this sparingly.", + "parameters": [ + { + "description": "ClientId", + "in": "path", + "name": "client", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Comma-separated list of ciphersuites in hex format (e.g. 0xf031) - default is 0x0001", + "in": "query", + "name": "ciphersuites", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/KeyPackageUpload" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Key packages replaced" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "mls-protocol-error", + "message": "MLS protocol error" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "mls-protocol-error" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body` or `ciphersuites`\n\nMLS protocol error (label: `mls-protocol-error`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "mls-identity-mismatch", + "message": "Key package credential does not match qualified client ID" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "mls-identity-mismatch" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Key package credential does not match qualified client ID (label: `mls-identity-mismatch`)" + } + }, + "summary": "Upload a fresh batch of key packages and replace the old ones" + } + }, + "/mls/key-packages/self/{client}/count": { + "get": { + "description": " [internal route ID: \"mls-key-packages-count\"]\n\n", + "parameters": [ + { + "description": "ClientId", + "in": "path", + "name": "client", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Ciphersuite in hex format (e.g. 0xf031) - default is 0x0001", + "in": "query", + "name": "ciphersuite", + "required": false, + "schema": { + "type": "number" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OwnKeyPackages" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/OwnKeyPackages" + } + } + }, + "description": "Number of key packages" + } + }, + "summary": "Return the number of unclaimed key packages for a given ciphersuite and client" + } + }, + "/mls/messages": { + "post": { + "description": " [internal route ID: \"mls-message\"]\n\n\n\n**Note**: this endpoint can execute proposals, and therefore return all possible errors associated with adding or removing members to a conversation, in addition to the ones listed below. See the documentation of [POST /conversations/{cnv}/members/v2](#/default/post_conversations__cnv__members_v2) and [POST /conversations/{cnv_domain}/{cnv}/members/{usr_domain}/{usr}](#/default/delete_conversations__cnv_domain___cnv__members__usr_domain___usr_) for more details on the possible error responses of each type of proposal.
Calls federation service brig on get-mls-clients
Calls federation service galley on on-conversation-updated
Calls federation service galley on send-mls-message
Calls federation service galley on on-mls-message-sent", + "requestBody": { + "content": { + "message/mls": { + "schema": { + "$ref": "#/components/schemas/MLSMessage" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MLSMessageSendingStatus" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/MLSMessageSendingStatus" + } + } + }, + "description": "Message sent" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "mls-self-removal-not-allowed", + "message": "Self removal from group is not allowed" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "mls-self-removal-not-allowed", + "mls-protocol-error", + "mls-not-enabled", + "mls-invalid-leaf-node-index", + "mls-group-conversation-mismatch", + "mls-commit-missing-references", + "mls-client-sender-user-mismatch" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nSelf removal from group is not allowed (label: `mls-self-removal-not-allowed`)\n\nMLS protocol error (label: `mls-protocol-error`)\n\nMLS is not configured on this backend. See docs.wire.com for instructions on how to enable it (label: `mls-not-enabled`)\n\nA referenced leaf node index points to a blank or non-existing node (label: `mls-invalid-leaf-node-index`)\n\nConversation ID resolved from Group ID does not match submitted Conversation ID (label: `mls-group-conversation-mismatch`)\n\nThe commit is not referencing all pending proposals (label: `mls-commit-missing-references`)\n\nUser ID resolved from Client ID does not match message's sender user ID (label: `mls-client-sender-user-mismatch`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "mls-subconv-join-parent-missing", + "message": "MLS client cannot join the subconversation because it is not member of the parent conversation" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "mls-subconv-join-parent-missing", + "missing-legalhold-consent", + "legalhold-not-enabled", + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "MLS client cannot join the subconversation because it is not member of the parent conversation (label: `mls-subconv-join-parent-missing`)\n\nFailed to connect to a user or to invite a user to a group because somebody is under legalhold and somebody else has not granted consent (label: `missing-legalhold-consent`)\n\nlegal hold is not enabled for this team (label: `legalhold-not-enabled`)\n\nConversation access denied (label: `access-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "mls-proposal-not-found", + "message": "A proposal referenced in a commit message could not be found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "mls-proposal-not-found", + "no-conversation", + "no-conversation-member" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "A proposal referenced in a commit message could not be found (label: `mls-proposal-not-found`)\n\nConversation not found (label: `no-conversation`)\n\nConversation member not found (label: `no-conversation-member`)" + }, + "409": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "properties": { + "non_federating_backends": { + "items": { + "$ref": "#/components/schemas/Domain" + }, + "type": "array" + } + }, + "required": [ + "non_federating_backends" + ], + "type": "object" + } + } + }, + "description": "Adding members to the conversation is not possible because the backends involved do not form a fully connected graph\n\nThe conversation epoch in a message is too old (label: `mls-stale-message`)\n\nA proposal of type Add or Remove does not apply to the full list of clients for a user (label: `mls-client-mismatch`)" + }, + "422": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 422, + "label": "mls-unsupported-proposal", + "message": "Unsupported proposal type" + }, + "properties": { + "code": { + "enum": [ + 422 + ], + "type": "integer" + }, + "label": { + "enum": [ + "mls-unsupported-proposal", + "mls-unsupported-message" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Unsupported proposal type (label: `mls-unsupported-proposal`)\n\nAttempted to send a message with an unsupported combination of content type and wire format (label: `mls-unsupported-message`)" + }, + "533": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "properties": { + "unreachable_backends": { + "items": { + "$ref": "#/components/schemas/Domain" + }, + "type": "array" + } + }, + "required": [ + "unreachable_backends" + ], + "type": "object" + } + } + }, + "description": "Some domains are unreachable" + } + }, + "summary": "Post an MLS message" + } + }, + "/mls/public-keys": { + "get": { + "description": " [internal route ID: \"mls-public-keys\"]\n\n", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MLSKeysByPurpose" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/MLSKeysByPurpose" + } + } + }, + "description": "Public keys" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "mls-not-enabled", + "message": "MLS is not configured on this backend. See docs.wire.com for instructions on how to enable it" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "mls-not-enabled" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "MLS is not configured on this backend. See docs.wire.com for instructions on how to enable it (label: `mls-not-enabled`)" + } + }, + "summary": "Get public keys used by the backend to sign external proposals" + } + }, + "/notifications": { + "get": { + "description": " [internal route ID: \"get-notifications\"]\n\n", + "parameters": [ + { + "description": "Only return notifications more recent than this", + "in": "query", + "name": "since", + "required": false, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "description": "Only return notifications targeted at the given client", + "in": "query", + "name": "client", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Maximum number of notifications to return", + "in": "query", + "name": "size", + "required": false, + "schema": { + "format": "int32", + "maximum": 10000, + "minimum": 100, + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QueuedNotificationList" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/QueuedNotificationList" + } + } + }, + "description": "Notification list" + }, + "404": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Some notifications not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Some notifications not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Some notifications not found (label: `not-found`)" + } + }, + "summary": "Fetch notifications" + } + }, + "/notifications/last": { + "get": { + "description": " [internal route ID: \"get-last-notification\"]\n\n", + "parameters": [ + { + "description": "Only return notifications targeted at the given client", + "in": "query", + "name": "client", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QueuedNotification" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/QueuedNotification" + } + } + }, + "description": "Notification found" + }, + "404": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Some notifications not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Some notifications not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Some notifications not found (label: `not-found`)" + } + }, + "summary": "Fetch the last notification" + } + }, + "/notifications/{id}": { + "get": { + "description": " [internal route ID: \"get-notification-by-id\"]\n\n", + "parameters": [ + { + "description": "Notification ID", + "in": "path", + "name": "id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "description": "Only return notifications targeted at the given client", + "in": "query", + "name": "client", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QueuedNotification" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/QueuedNotification" + } + } + }, + "description": "Notification found" + }, + "404": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Some notifications not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Some notifications not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`id` or Some notifications not found (label: `not-found`)" + } + }, + "summary": "Fetch a notification by ID" + } + }, + "/oauth/applications": { + "get": { + "description": " [internal route ID: \"get-oauth-applications\"]\n\nGet all OAuth applications with active account access for a user.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/OAuthApplication" + }, + "type": "array" + } + }, + "application/json;charset=utf-8": { + "schema": { + "items": { + "$ref": "#/components/schemas/OAuthApplication" + }, + "type": "array" + } + } + }, + "description": "OAuth applications found" + } + }, + "summary": "Get OAuth applications with account access" + } + }, + "/oauth/applications/{OAuthClientId}": { + "delete": { + "description": " [internal route ID: \"revoke-oauth-account-access\"]\n\n", + "parameters": [ + { + "description": "The ID of the OAuth client", + "in": "path", + "name": "OAuthClientId", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "OAuth application access revoked" + } + }, + "summary": "Revoke account access from an OAuth application" + } + }, + "/oauth/authorization/codes": { + "post": { + "description": " [internal route ID: \"create-oauth-auth-code\"]\n\nCurrently only supports the 'code' response type, which corresponds to the authorization code flow.", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/CreateOAuthAuthorizationCodeRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Created", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 400, + "label": "redirect-url-miss-match", + "message": "The redirect URL does not match the one registered with the client" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "redirect-url-miss-match" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "redirect-url-miss-match", + "message": "The redirect URL does not match the one registered with the client" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "redirect-url-miss-match" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Bad Request\n\nThe redirect URL does not match the one registered with the client (label: `redirect-url-miss-match`) or `body`", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + } + }, + "403": { + "description": "Forbidden", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + } + }, + "404": { + "description": "Not Found", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + } + } + }, + "summary": "Create an OAuth authorization code" + } + }, + "/oauth/clients/{OAuthClientId}": { + "get": { + "description": " [internal route ID: \"get-oauth-client\"]\n\n", + "parameters": [ + { + "description": "The ID of the OAuth client", + "in": "path", + "name": "OAuthClientId", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuthClient" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/OAuthClient" + } + } + }, + "description": "OAuth client found" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "forbidden", + "message": "OAuth is disabled" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "forbidden" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "OAuth is disabled (label: `forbidden`)" + }, + "404": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "OAuth client not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "OAuth client not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`OAuthClientId` or OAuth client not found (label: `not-found`)\n\nOAuth client not found (label: `not-found`)" + } + }, + "summary": "Get OAuth client information" + } + }, + "/oauth/revoke": { + "post": { + "description": " [internal route ID: \"revoke-oauth-refresh-token\"]\n\nRevoke an access token.", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/OAuthRevokeRefreshTokenRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": [], + "items": {}, + "maxItems": 0, + "type": "array" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "forbidden", + "message": "Invalid refresh token" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "forbidden" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid refresh token (label: `forbidden`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "OAuth client not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "OAuth client not found (label: `not-found`)" + }, + "500": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 500, + "label": "jwt-error", + "message": "Internal error while handling JWT token" + }, + "properties": { + "code": { + "enum": [ + 500 + ], + "type": "integer" + }, + "label": { + "enum": [ + "jwt-error" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Internal error while handling JWT token (label: `jwt-error`)" + } + }, + "summary": "Revoke an OAuth refresh token" + } + }, + "/oauth/token": { + "post": { + "description": " [internal route ID: \"create-oauth-access-token\"]\n\nObtain a new access token from an authorization code or a refresh token.", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Either_OAuthAccessTokenRequest_OAuthRefreshAccessTokenRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/OAuthAccessTokenResponse" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid_grant", + "message": "Invalid grant" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid_grant", + "forbidden" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid grant (label: `invalid_grant`)\n\nInvalid client credentials (label: `forbidden`)\n\nInvalid grant type (label: `forbidden`)\n\nInvalid refresh token (label: `forbidden`)\n\nOAuth is disabled (label: `forbidden`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "OAuth client not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "OAuth client not found (label: `not-found`)\n\nOAuth authorization code not found (label: `not-found`)" + }, + "500": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 500, + "label": "jwt-error", + "message": "Internal error while handling JWT token" + }, + "properties": { + "code": { + "enum": [ + 500 + ], + "type": "integer" + }, + "label": { + "enum": [ + "jwt-error" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Internal error while handling JWT token (label: `jwt-error`)" + } + }, + "summary": "Create an OAuth access token" + } + }, + "/onboarding/v3": { + "post": { + "deprecated": true, + "description": " [internal route ID: \"onboarding\"]\n\nDEPRECATED: the feature has been turned off, the end-point does nothing and always returns '{\"results\":[],\"auto-connects\":[]}'.", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Body" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/DeprecatedMatchingResult" + } + } + }, + "description": "" + } + }, + "summary": "Upload contacts and invoke matching." + } + }, + "/password-reset": { + "post": { + "description": " [internal route ID: \"post-password-reset\"]\n\n", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/NewPasswordReset" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Password reset code created and sent by email." + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-key", + "message": "Invalid email or mobile number for password reset." + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-key" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nInvalid email or mobile number for password reset. (label: `invalid-key`)" + }, + "409": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 409, + "label": "code-exists", + "message": "A password reset is already in progress." + }, + "properties": { + "code": { + "enum": [ + 409 + ], + "type": "integer" + }, + "label": { + "enum": [ + "code-exists" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "A password reset is already in progress. (label: `code-exists`)" + } + }, + "summary": "Initiate a password reset." + } + }, + "/password-reset/complete": { + "post": { + "description": " [internal route ID: \"post-password-reset-complete\"]\n\n", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/CompletePasswordReset" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Password reset successful." + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-code", + "message": "Invalid password reset code." + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-code" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nInvalid password reset code. (label: `invalid-code`)" + } + }, + "summary": "Complete a password reset." + } + }, + "/password-reset/{key}": { + "post": { + "deprecated": true, + "description": " [internal route ID: \"post-password-reset-key-deprecated\"]\n\nDEPRECATED: Use 'POST /password-reset/complete'.", + "parameters": [ + { + "description": "An opaque key for a pending password reset.", + "in": "path", + "name": "key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/PasswordReset" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Password reset successful." + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-code", + "message": "Invalid password reset code." + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-code", + "invalid-key" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nInvalid password reset code. (label: `invalid-code`)\n\nInvalid email or mobile number for password reset. (label: `invalid-key`)" + }, + "409": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 409, + "label": "password-must-differ", + "message": "For password reset, new and old password must be different." + }, + "properties": { + "code": { + "enum": [ + 409 + ], + "type": "integer" + }, + "label": { + "enum": [ + "password-must-differ", + "code-exists" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "For password reset, new and old password must be different. (label: `password-must-differ`)\n\nA password reset is already in progress. (label: `code-exists`)" + } + }, + "summary": "Complete a password reset." + } + }, + "/properties": { + "delete": { + "description": " [internal route ID: \"clear-properties\"]\n\n", + "responses": { + "200": { + "description": "Properties cleared" + } + }, + "summary": "Clear all properties" + }, + "get": { + "description": " [internal route ID: \"list-property-keys\"]\n\n", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/ASCII" + }, + "type": "array" + } + }, + "application/json;charset=utf-8": { + "schema": { + "items": { + "$ref": "#/components/schemas/ASCII" + }, + "type": "array" + } + } + }, + "description": "List of property keys" + } + }, + "summary": "List all property keys" + } + }, + "/properties-values": { + "get": { + "description": " [internal route ID: \"list-properties\"]\n\n", + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/PropertyKeysAndValues" + } + } + }, + "description": "" + } + }, + "summary": "List all properties with key and value" + } + }, + "/properties/{key}": { + "delete": { + "description": " [internal route ID: \"delete-property\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "key", + "required": true, + "schema": { + "format": "printable", + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Property deleted" + } + }, + "summary": "Delete a property" + }, + "get": { + "description": " [internal route ID: \"get-property\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "key", + "required": true, + "schema": { + "format": "printable", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PropertyValue" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/PropertyValue" + } + } + }, + "description": "The property value" + }, + "404": { + "description": "`key` or Property not found(**Note**: This error has an empty body for legacy reasons)" + } + }, + "summary": "Get a property value" + }, + "put": { + "description": " [internal route ID: \"set-property\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "key", + "required": true, + "schema": { + "format": "printable", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/PropertyValue" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Property set" + } + }, + "summary": "Set a user property" + } + }, + "/provider": { + "delete": { + "description": " [internal route ID: \"provider-delete\"]\n\n", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/DeleteProvider" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-credentials", + "message": "Authentication failed" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-credentials", + "invalid-provider", + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Authentication failed (label: `invalid-credentials`)\n\nThe provider does not exist. (label: `invalid-provider`)\n\nAccess denied. (label: `access-denied`)" + } + }, + "summary": "Delete a provider" + }, + "get": { + "description": " [internal route ID: \"provider-get-account\"]\n\n", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Provider" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Provider" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "access-denied", + "message": "Access denied." + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Access denied. (label: `access-denied`)" + }, + "404": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Provider not found." + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Provider not found." + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Provider not found. (label: `not-found`)\n\nProvider not found. (label: `not-found`)" + } + }, + "summary": "Get account" + }, + "put": { + "description": " [internal route ID: \"provider-update\"]\n\n", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/UpdateProvider" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-provider", + "message": "The provider does not exist." + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-provider", + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The provider does not exist. (label: `invalid-provider`)\n\nAccess denied. (label: `access-denied`)" + } + }, + "summary": "Update a provider" + } + }, + "/provider/activate": { + "get": { + "description": " [internal route ID: \"provider-activate\"]\n\n", + "parameters": [ + { + "in": "query", + "name": "key", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "code", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProviderActivationResponse" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ProviderActivationResponse" + } + } + }, + "description": "" + }, + "204": { + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-code", + "message": "Invalid verification code" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-code", + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid verification code (label: `invalid-code`)\n\nAccess denied. (label: `access-denied`)" + } + }, + "summary": "Activate a provider" + } + }, + "/provider/assets": { + "post": { + "requestBody": { + "content": { + "multipart/mixed": { + "schema": { + "$ref": "#/components/schemas/AssetSource" + } + } + }, + "description": "A body with content type `multipart/mixed body`. The first section's content type should be `application/json`. The second section's content type should be always be `application/octet-stream`. Other content types will be ignored by the server." + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Asset" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Asset" + } + } + }, + "description": "Asset posted", + "headers": { + "Location": { + "description": "Asset location", + "schema": { + "format": "url", + "type": "string" + } + } + } + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-length", + "message": "Invalid content length" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-length" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nInvalid content length (label: `invalid-length`)" + }, + "413": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 413, + "label": "client-error", + "message": "Asset too large" + }, + "properties": { + "code": { + "enum": [ + 413 + ], + "type": "integer" + }, + "label": { + "enum": [ + "client-error" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Asset too large (label: `client-error`)" + } + }, + "summary": "Upload an asset" + } + }, + "/provider/assets/{key}": { + "delete": { + "parameters": [ + { + "in": "path", + "name": "key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Asset deleted" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "unauthorised", + "message": "Unauthorised operation" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "unauthorised" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Unauthorised operation (label: `unauthorised`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Asset not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`key` not found\n\nAsset not found (label: `not-found`)" + } + }, + "summary": "Delete an asset" + }, + "get": { + "parameters": [ + { + "in": "path", + "name": "key", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "header", + "name": "Asset-Token", + "required": false, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "asset_token", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "302": { + "description": "Asset found", + "headers": { + "Location": { + "description": "Asset location", + "schema": { + "format": "url", + "type": "string" + } + } + } + }, + "404": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Asset not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Asset not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`key` or Asset not found (label: `not-found`)" + } + }, + "summary": "Download an asset" + } + }, + "/provider/email": { + "put": { + "description": " [internal route ID: \"provider-update-email\"]\n\n", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/EmailUpdate" + } + } + }, + "required": true + }, + "responses": { + "202": { + "description": "" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-email", + "message": "Invalid e-mail address." + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-email" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nInvalid e-mail address. (label: `invalid-email`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-provider", + "message": "The provider does not exist." + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-provider", + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The provider does not exist. (label: `invalid-provider`)\n\nAccess denied. (label: `access-denied`)" + }, + "429": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 429, + "label": "too-many-requests", + "message": "Too many request to generate a verification code." + }, + "properties": { + "code": { + "enum": [ + 429 + ], + "type": "integer" + }, + "label": { + "enum": [ + "too-many-requests" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Too many request to generate a verification code. (label: `too-many-requests`)" + } + }, + "summary": "Update a provider email" + } + }, + "/provider/login": { + "post": { + "description": " [internal route ID: \"provider-login\"]\n\n", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ProviderLogin" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "headers": { + "Set-Cookie": { + "schema": { + "type": "string" + } + } + } + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-credentials", + "message": "Authentication failed" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-credentials", + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Authentication failed (label: `invalid-credentials`)\n\nAccess denied. (label: `access-denied`)" + } + }, + "summary": "Login as a provider" + } + }, + "/provider/password": { + "put": { + "description": " [internal route ID: \"provider-update-password\"]\n\n", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/PasswordChange" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-credentials", + "message": "Authentication failed" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-credentials", + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Authentication failed (label: `invalid-credentials`)\n\nAccess denied. (label: `access-denied`)" + }, + "409": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 409, + "label": "password-must-differ", + "message": "For password reset, new and old password must be different." + }, + "properties": { + "code": { + "enum": [ + 409 + ], + "type": "integer" + }, + "label": { + "enum": [ + "password-must-differ" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "For password reset, new and old password must be different. (label: `password-must-differ`)" + } + }, + "summary": "Update a provider password" + } + }, + "/provider/password-reset": { + "post": { + "description": " [internal route ID: \"provider-password-reset\"]\n\n", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/PasswordReset" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-code", + "message": "Invalid password reset code." + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-code", + "invalid-key" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nInvalid password reset code. (label: `invalid-code`)\n\nInvalid email or mobile number for password reset. (label: `invalid-key`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-credentials", + "message": "Authentication failed" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-credentials", + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Authentication failed (label: `invalid-credentials`)\n\nAccess denied. (label: `access-denied`)" + }, + "409": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 409, + "label": "password-must-differ", + "message": "For password reset, new and old password must be different." + }, + "properties": { + "code": { + "enum": [ + 409 + ], + "type": "integer" + }, + "label": { + "enum": [ + "password-must-differ", + "code-exists" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "For password reset, new and old password must be different. (label: `password-must-differ`)\n\nA password reset is already in progress. (label: `code-exists`)\n\nA password reset is already in progress. (label: `code-exists`)" + }, + "429": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 429, + "label": "too-many-requests", + "message": "Too many request to generate a verification code." + }, + "properties": { + "code": { + "enum": [ + 429 + ], + "type": "integer" + }, + "label": { + "enum": [ + "too-many-requests" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Too many request to generate a verification code. (label: `too-many-requests`)" + } + }, + "summary": "Begin a password reset" + } + }, + "/provider/password-reset/complete": { + "post": { + "description": " [internal route ID: \"provider-password-reset-complete\"]\n\n", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/CompletePasswordReset" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-code", + "message": "Invalid password reset code." + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-code" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nInvalid password reset code. (label: `invalid-code`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-credentials", + "message": "Authentication failed" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-credentials", + "invalid-code", + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Authentication failed (label: `invalid-credentials`)\n\nInvalid verification code (label: `invalid-code`)\n\nAccess denied. (label: `access-denied`)" + }, + "409": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 409, + "label": "password-must-differ", + "message": "For password reset, new and old password must be different." + }, + "properties": { + "code": { + "enum": [ + 409 + ], + "type": "integer" + }, + "label": { + "enum": [ + "password-must-differ" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "For password reset, new and old password must be different. (label: `password-must-differ`)" + } + }, + "summary": "Complete a password reset" + } + }, + "/provider/register": { + "post": { + "description": " [internal route ID: \"provider-register\"]\n\n", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/NewProvider" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewProviderResponse" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/NewProviderResponse" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-email", + "message": "Invalid e-mail address." + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-email" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nInvalid e-mail address. (label: `invalid-email`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "access-denied", + "message": "Access denied." + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Access denied. (label: `access-denied`)" + }, + "429": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 429, + "label": "too-many-requests", + "message": "Too many request to generate a verification code." + }, + "properties": { + "code": { + "enum": [ + 429 + ], + "type": "integer" + }, + "label": { + "enum": [ + "too-many-requests" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Too many request to generate a verification code. (label: `too-many-requests`)" + } + }, + "summary": "Register a new provider" + } + }, + "/provider/services": { + "get": { + "description": " [internal route ID: \"get-provider-services\"]\n\n", + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "items": { + "$ref": "#/components/schemas/Service" + }, + "type": "array" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "access-denied", + "message": "Access denied." + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Access denied. (label: `access-denied`)" + } + }, + "summary": "List provider services" + }, + "post": { + "description": " [internal route ID: \"post-provider-services\"]\n\n", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/NewService" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewServiceResponse" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/NewServiceResponse" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-service-key", + "message": "Invalid service key." + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-service-key" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nInvalid service key. (label: `invalid-service-key`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "access-denied", + "message": "Access denied." + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Access denied. (label: `access-denied`)" + } + }, + "summary": "Create a new service" + } + }, + "/provider/services/{service-id}": { + "delete": { + "description": " [internal route ID: \"delete-provider-services-by-service-id\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "service-id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/DeleteService" + } + } + }, + "required": true + }, + "responses": { + "202": { + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-credentials", + "message": "Authentication failed" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-credentials", + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Authentication failed (label: `invalid-credentials`)\n\nAccess denied. (label: `access-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Service not found." + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`service-id` not found\n\nService not found. (label: `not-found`)" + } + }, + "summary": "Delete service" + }, + "get": { + "description": " [internal route ID: \"get-provider-services-by-service-id\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "service-id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Service" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "access-denied", + "message": "Access denied." + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Access denied. (label: `access-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Service not found." + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`service-id` not found\n\nService not found. (label: `not-found`)" + } + }, + "summary": "Get provider service by service id" + }, + "put": { + "description": " [internal route ID: \"put-provider-services-by-service-id\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "service-id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/UpdateService" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Provider service updated" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "access-denied", + "message": "Access denied." + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Access denied. (label: `access-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Provider not found." + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`service-id` not found\n\nProvider not found. (label: `not-found`)\n\nService not found. (label: `not-found`)" + } + }, + "summary": "Update provider service" + } + }, + "/provider/services/{service-id}/connection": { + "put": { + "description": " [internal route ID: \"put-provider-services-connection-by-service-id\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "service-id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/UpdateServiceConn" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Provider service connection updated" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-service-key", + "message": "Invalid service key." + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-service-key" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nInvalid service key. (label: `invalid-service-key`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-credentials", + "message": "Authentication failed" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-credentials", + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Authentication failed (label: `invalid-credentials`)\n\nAccess denied. (label: `access-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Service not found." + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`service-id` not found\n\nService not found. (label: `not-found`)" + } + }, + "summary": "Update provider service connection" + } + }, + "/providers/{pid}": { + "get": { + "description": " [internal route ID: \"provider-get-profile\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "pid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Provider" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Provider" + } + } + }, + "description": "" + }, + "404": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Provider not found." + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Provider not found." + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`pid` or Provider not found. (label: `not-found`)" + } + }, + "summary": "Get profile" + } + }, + "/providers/{provider-id}/services": { + "get": { + "description": " [internal route ID: \"get-provider-services-by-provider-id\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "provider-id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "items": { + "$ref": "#/components/schemas/ServiceProfile" + }, + "type": "array" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "access-denied", + "message": "Access denied." + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Access denied. (label: `access-denied`)" + } + }, + "summary": "Get provider services by provider id" + } + }, + "/providers/{provider-id}/services/{service-id}": { + "get": { + "description": " [internal route ID: \"get-provider-services-by-provider-id-and-service-id\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "provider-id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "in": "path", + "name": "service-id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ServiceProfile" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "access-denied", + "message": "Access denied." + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Access denied. (label: `access-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Service not found." + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`provider-id` or `service-id` not found\n\nService not found. (label: `not-found`)" + } + }, + "summary": "Get provider service by provider id and service id" + } + }, + "/proxy/giphy/v1/gifs": {}, + "/proxy/googlemaps/api/staticmap": {}, + "/proxy/googlemaps/maps/api/geocode": {}, + "/proxy/youtube/v3": {}, + "/push/tokens": { + "get": { + "description": " [internal route ID: \"get-push-tokens\"]\n\n", + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/PushTokenList" + } + } + }, + "description": "" + } + }, + "summary": "List the user's registered push tokens" + }, + "post": { + "description": " [internal route ID: \"register-push-token\"]\n\n", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/PushToken" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PushToken" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/PushToken" + } + } + }, + "description": "Push token registered", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + } + }, + "404": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 404, + "label": "app-not-found", + "message": "App does not exist" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "app-not-found", + "invalid-token" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "app-not-found", + "message": "App does not exist" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "app-not-found", + "invalid-token" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "App does not exist (label: `app-not-found`)\n\nInvalid push token (label: `invalid-token`)" + }, + "413": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 413, + "label": "sns-thread-budget-reached", + "message": "Too many concurrent calls to SNS; is SNS down?" + }, + "properties": { + "code": { + "enum": [ + 413 + ], + "type": "integer" + }, + "label": { + "enum": [ + "sns-thread-budget-reached", + "token-too-long", + "metadata-too-long" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 413, + "label": "sns-thread-budget-reached", + "message": "Too many concurrent calls to SNS; is SNS down?" + }, + "properties": { + "code": { + "enum": [ + 413 + ], + "type": "integer" + }, + "label": { + "enum": [ + "sns-thread-budget-reached", + "token-too-long", + "metadata-too-long" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Too many concurrent calls to SNS; is SNS down? (label: `sns-thread-budget-reached`)\n\nPush token length must be < 8192 for GCM or 400 for APNS (label: `token-too-long`)\n\nTried to add token to endpoint resulting in metadata length > 2048 (label: `metadata-too-long`)" + } + }, + "summary": "Register a native push token" + } + }, + "/push/tokens/{pid}": { + "delete": { + "description": " [internal route ID: \"delete-push-token\"]\n\n", + "parameters": [ + { + "description": "The push token to delete", + "in": "path", + "name": "pid", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Push token unregistered" + }, + "404": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Push token not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Push token not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`pid` or Push token not found (label: `not-found`)" + } + }, + "summary": "Unregister a native push token" + } + }, + "/register": { + "post": { + "description": " [internal route ID: \"register\"]\n\nIf the environment where the registration takes place is private and a registered email address or phone number is not whitelisted, a 403 error is returned.", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/NewUser" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + }, + "description": "User created and pending activation", + "headers": { + "Location": { + "description": "UserId", + "schema": { + "format": "uuid", + "type": "string" + } + }, + "Set-Cookie": { + "description": "Cookie", + "schema": { + "type": "string" + } + } + } + }, + "400": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 400, + "label": "invalid-invitation-code", + "message": "Invalid invitation code." + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-invitation-code", + "invalid-email", + "invalid-phone" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-invitation-code", + "message": "Invalid invitation code." + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-invitation-code", + "invalid-email", + "invalid-phone" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid invitation code. (label: `invalid-invitation-code`)\n\nInvalid e-mail address. (label: `invalid-email`)\n\nInvalid mobile phone number (label: `invalid-phone`) or `body`" + }, + "403": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 403, + "label": "unauthorized", + "message": "Unauthorized e-mail address or phone number." + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "unauthorized", + "missing-identity", + "blacklisted-phone", + "blacklisted-email", + "too-many-team-members", + "user-creation-restricted" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "unauthorized", + "message": "Unauthorized e-mail address or phone number." + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "unauthorized", + "missing-identity", + "blacklisted-phone", + "blacklisted-email", + "too-many-team-members", + "user-creation-restricted" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Unauthorized e-mail address or phone number. (label: `unauthorized`)\n\nUsing an invitation code requires registering the given email and/or phone. (label: `missing-identity`)\n\nThe given phone number has been blacklisted due to suspected abuse or a complaint (label: `blacklisted-phone`)\n\nThe given e-mail address has been blacklisted due to a permanent bounce or a complaint. (label: `blacklisted-email`)\n\nToo many members in this team. (label: `too-many-team-members`)\n\nThis instance does not allow creation of personal users or teams. (label: `user-creation-restricted`)" + }, + "404": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 404, + "label": "invalid-code", + "message": "User does not exist" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-code" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "invalid-code", + "message": "User does not exist" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-code" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "User does not exist (label: `invalid-code`)\n\nInvalid activation code (label: `invalid-code`)" + }, + "409": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 409, + "label": "key-exists", + "message": "The given e-mail address or phone number is in use." + }, + "properties": { + "code": { + "enum": [ + 409 + ], + "type": "integer" + }, + "label": { + "enum": [ + "key-exists" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 409, + "label": "key-exists", + "message": "The given e-mail address or phone number is in use." + }, + "properties": { + "code": { + "enum": [ + 409 + ], + "type": "integer" + }, + "label": { + "enum": [ + "key-exists" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The given e-mail address or phone number is in use. (label: `key-exists`)" + } + }, + "summary": "Register a new user." + } + }, + "/scim/auth-tokens": { + "delete": { + "parameters": [ + { + "in": "query", + "name": "id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "code-authentication-required", + "message": "Code authentication is required" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "code-authentication-required", + "code-authentication-failed", + "password-authentication-failed" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Code authentication is required (label: `code-authentication-required`)\n\nCode authentication failed (label: `code-authentication-failed`)\n\nPassword authentication failed. (label: `password-authentication-failed`)" + } + } + }, + "get": { + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ScimTokenList" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "code-authentication-required", + "message": "Code authentication is required" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "code-authentication-required", + "code-authentication-failed", + "password-authentication-failed" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Code authentication is required (label: `code-authentication-required`)\n\nCode authentication failed (label: `code-authentication-failed`)\n\nPassword authentication failed. (label: `password-authentication-failed`)" + } + } + }, + "post": { + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/CreateScimToken" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/CreateScimTokenResponse" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "code-authentication-required", + "message": "Code authentication is required" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "code-authentication-required", + "code-authentication-failed", + "password-authentication-failed" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Code authentication is required (label: `code-authentication-required`)\n\nCode authentication failed (label: `code-authentication-failed`)\n\nPassword authentication failed. (label: `password-authentication-failed`)" + } + } + } + }, + "/search/contacts": { + "get": { + "description": " [internal route ID: \"search-contacts\"]\n\nCalls federation service brig on search-users
Calls federation service brig on get-users-by-ids", + "parameters": [ + { + "description": "Search query", + "in": "query", + "name": "q", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Searched domain. Note: This is optional only for backwards compatibility, future versions will mandate this.", + "in": "query", + "name": "domain", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Number of results to return (min: 1, max: 500, default 15)", + "in": "query", + "name": "size", + "required": false, + "schema": { + "format": "int32", + "maximum": 500, + "minimum": 1, + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/SearchResult" + } + } + }, + "description": "" + } + }, + "summary": "Search for users" + } + }, + "/self": { + "delete": { + "description": " [internal route ID: \"delete-self\"]\n\nif the account has a verified identity, a verification code is sent and needs to be confirmed to authorise the deletion. if the account has no verified identity but a password, it must be provided. if password is correct, or if neither a verified identity nor a password exists, account deletion is scheduled immediately.", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/DeleteUser" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Deletion is initiated." + }, + "202": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeletionCodeTimeout" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/DeletionCodeTimeout" + } + } + }, + "description": "Deletion is pending verification with a code." + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-user", + "message": "Invalid user" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-user" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nInvalid user (label: `invalid-user`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-self-delete-for-team-owner", + "message": "Team owners are not allowed to delete themselves; ask a fellow owner" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-self-delete-for-team-owner", + "pending-delete", + "missing-auth", + "invalid-credentials", + "invalid-code" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Team owners are not allowed to delete themselves; ask a fellow owner (label: `no-self-delete-for-team-owner`)\n\nA verification code for account deletion is still pending (label: `pending-delete`)\n\nRe-authentication via password required (label: `missing-auth`)\n\nAuthentication failed (label: `invalid-credentials`)\n\nInvalid verification code (label: `invalid-code`)" + } + }, + "summary": "Initiate account deletion." + }, + "get": { + "description": " [internal route ID: \"get-self\"]\n\n\nOAuth scope: `read:self`", + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + }, + "description": "" + } + }, + "summary": "Get your own profile" + }, + "put": { + "description": " [internal route ID: \"put-self\"]\n\n", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/UserUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "User updated" + }, + "403": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 403, + "label": "managed-by-scim", + "message": "Updating name is not allowed, because it is managed by SCIM" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "managed-by-scim" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "managed-by-scim", + "message": "Updating name is not allowed, because it is managed by SCIM" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "managed-by-scim" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Updating name is not allowed, because it is managed by SCIM (label: `managed-by-scim`)" + }, + "404": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "User not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "User not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "User not found (label: `not-found`)" + } + }, + "summary": "Update your profile." + } + }, + "/self/email": { + "delete": { + "description": " [internal route ID: \"remove-email\"]\n\nYour email address can only be removed if you also have a phone number.", + "responses": { + "200": { + "description": "Identity Removed" + }, + "403": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 403, + "label": "last-identity", + "message": "The last user identity (email or phone number) cannot be removed." + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "last-identity", + "no-password", + "no-identity" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "last-identity", + "message": "The last user identity (email or phone number) cannot be removed." + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "last-identity", + "no-password", + "no-identity" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The last user identity (email or phone number) cannot be removed. (label: `last-identity`)\n\nThe user has no password. (label: `no-password`)\n\nThe user has no verified identity (email or phone number) (label: `no-identity`)" + } + }, + "summary": "Remove your email address." + } + }, + "/self/handle": { + "put": { + "description": " [internal route ID: \"change-handle\"]\n\n", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/HandleUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Handle Changed" + }, + "400": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 400, + "label": "invalid-handle", + "message": "The given handle is invalid" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-handle" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-handle", + "message": "The given handle is invalid" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-handle" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The given handle is invalid (label: `invalid-handle`) or `body`" + }, + "403": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 403, + "label": "no-identity", + "message": "The user has no verified identity (email or phone number)" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-identity", + "managed-by-scim" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-identity", + "message": "The user has no verified identity (email or phone number)" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-identity", + "managed-by-scim" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The user has no verified identity (email or phone number) (label: `no-identity`)\n\nUpdating handle is not allowed, because it is managed by SCIM (label: `managed-by-scim`)" + }, + "409": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 409, + "label": "handle-exists", + "message": "The given handle is already taken" + }, + "properties": { + "code": { + "enum": [ + 409 + ], + "type": "integer" + }, + "label": { + "enum": [ + "handle-exists" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 409, + "label": "handle-exists", + "message": "The given handle is already taken" + }, + "properties": { + "code": { + "enum": [ + 409 + ], + "type": "integer" + }, + "label": { + "enum": [ + "handle-exists" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The given handle is already taken (label: `handle-exists`)" + } + }, + "summary": "Change your handle." + } + }, + "/self/locale": { + "put": { + "description": " [internal route ID: \"change-locale\"]\n\n", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/LocaleUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Local Changed" + } + }, + "summary": "Change your locale." + } + }, + "/self/password": { + "head": { + "description": " [internal route ID: \"check-password-exists\"]\n\n", + "responses": { + "200": { + "description": "Password is set" + }, + "404": { + "description": "Password is not set" + } + }, + "summary": "Check that your password is set." + }, + "put": { + "description": " [internal route ID: \"change-password\"]\n\n", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/PasswordChange" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Password Changed" + }, + "403": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 403, + "label": "invalid-credentials", + "message": "Authentication failed" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-credentials", + "no-identity" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-credentials", + "message": "Authentication failed" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-credentials", + "no-identity" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Authentication failed (label: `invalid-credentials`)\n\nThe user has no verified identity (email or phone number) (label: `no-identity`)" + }, + "409": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 409, + "label": "password-must-differ", + "message": "For password change, new and old password must be different." + }, + "properties": { + "code": { + "enum": [ + 409 + ], + "type": "integer" + }, + "label": { + "enum": [ + "password-must-differ" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 409, + "label": "password-must-differ", + "message": "For password change, new and old password must be different." + }, + "properties": { + "code": { + "enum": [ + 409 + ], + "type": "integer" + }, + "label": { + "enum": [ + "password-must-differ" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "For password change, new and old password must be different. (label: `password-must-differ`)" + } + }, + "summary": "Change your password." + } + }, + "/self/phone": { + "delete": { + "description": " [internal route ID: \"remove-phone\"]\n\nYour phone number can only be removed if you also have an email address and a password.", + "responses": { + "200": { + "description": "Identity Removed" + }, + "403": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 403, + "label": "last-identity", + "message": "The last user identity (email or phone number) cannot be removed." + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "last-identity", + "no-password", + "no-identity" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "last-identity", + "message": "The last user identity (email or phone number) cannot be removed." + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "last-identity", + "no-password", + "no-identity" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The last user identity (email or phone number) cannot be removed. (label: `last-identity`)\n\nThe user has no password. (label: `no-password`)\n\nThe user has no verified identity (email or phone number) (label: `no-identity`)" + } + }, + "summary": "Remove your phone number." + }, + "put": { + "description": " [internal route ID: \"change-phone\"]\n\n", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/PhoneUpdate" + } + } + }, + "required": true + }, + "responses": { + "202": { + "description": "Phone updated" + }, + "400": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 400, + "label": "invalid-phone", + "message": "Invalid mobile phone number" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-phone" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-phone", + "message": "Invalid mobile phone number" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-phone" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid mobile phone number (label: `invalid-phone`) or `body`" + }, + "403": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 403, + "label": "blacklisted-phone", + "message": "The given phone number has been blacklisted due to suspected abuse or a complaint" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "blacklisted-phone" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "blacklisted-phone", + "message": "The given phone number has been blacklisted due to suspected abuse or a complaint" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "blacklisted-phone" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The given phone number has been blacklisted due to suspected abuse or a complaint (label: `blacklisted-phone`)" + }, + "409": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 409, + "label": "key-exists", + "message": "The given e-mail address or phone number is in use." + }, + "properties": { + "code": { + "enum": [ + 409 + ], + "type": "integer" + }, + "label": { + "enum": [ + "key-exists" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 409, + "label": "key-exists", + "message": "The given e-mail address or phone number is in use." + }, + "properties": { + "code": { + "enum": [ + 409 + ], + "type": "integer" + }, + "label": { + "enum": [ + "key-exists" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The given e-mail address or phone number is in use. (label: `key-exists`)" + } + }, + "summary": "Change your phone number." + } + }, + "/self/supported-protocols": { + "put": { + "description": " [internal route ID: \"change-supported-protocols\"]\n\n", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/SupportedProtocolUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Supported protocols changed" + } + }, + "summary": "Change your supported protocols" + } + }, + "/services": { + "get": { + "description": " [internal route ID: \"get-services\"]\n\n", + "parameters": [ + { + "in": "query", + "name": "tags", + "required": false, + "schema": { + "enum": [ + "audio", + "books", + "business", + "design", + "education", + "entertainment", + "finance", + "fitness", + "food-drink", + "games", + "graphics", + "health", + "integration", + "lifestyle", + "media", + "medical", + "movies", + "music", + "news", + "photography", + "poll", + "productivity", + "quiz", + "rating", + "shopping", + "social", + "sports", + "travel", + "tutorial", + "video", + "weather" + ], + "type": "string" + } + }, + { + "in": "query", + "name": "start", + "required": false, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "size", + "required": false, + "schema": { + "format": "int32", + "maximum": 100, + "minimum": 10, + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ServiceProfile" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "access-denied", + "message": "Access denied." + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Access denied. (label: `access-denied`)" + } + }, + "summary": "List services" + } + }, + "/sso/finalize-login": { + "post": { + "deprecated": true, + "description": "DEPRECATED! use /sso/metadata/:tid instead! Details: https://docs.wire.com/understand/single-sign-on/trouble-shooting.html#can-i-use-the-same-sso-login-code-for-multiple-teams", + "responses": { + "200": { + "content": { + "text/plain;charset=utf-8": { + "schema": { + "type": "string" + } + } + }, + "description": "" + } + } + } + }, + "/sso/finalize-login/{team}": { + "post": { + "parameters": [ + { + "in": "path", + "name": "team", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "text/plain;charset=utf-8": { + "schema": { + "type": "string" + } + } + }, + "description": "" + } + } + } + }, + "/sso/initiate-login/{idp}": { + "get": { + "parameters": [ + { + "in": "query", + "name": "success_redirect", + "required": false, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "error_redirect", + "required": false, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "idp", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "text/html": { + "schema": { + "$ref": "#/components/schemas/FormRedirect" + } + } + }, + "description": "" + } + } + }, + "head": { + "parameters": [ + { + "in": "query", + "name": "success_redirect", + "required": false, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "error_redirect", + "required": false, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "idp", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "text/plain;charset=utf-8": {} + }, + "description": "" + } + } + } + }, + "/sso/metadata": { + "get": { + "deprecated": true, + "description": "DEPRECATED! use /sso/metadata/:tid instead! Details: https://docs.wire.com/understand/single-sign-on/trouble-shooting.html#can-i-use-the-same-sso-login-code-for-multiple-teams", + "responses": { + "200": { + "content": { + "application/xml": { + "schema": { + "type": "string" + } + } + }, + "description": "" + } + } + } + }, + "/sso/metadata/{team}": { + "get": { + "parameters": [ + { + "in": "path", + "name": "team", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/xml": { + "schema": { + "type": "string" + } + } + }, + "description": "" + } + } + } + }, + "/sso/settings": { + "get": { + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/SsoSettings" + } + } + }, + "description": "" + } + } + } + }, + "/system/settings": { + "get": { + "description": " [internal route ID: \"get-system-settings\"]\n\n", + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/SystemSettings" + } + } + }, + "description": "" + } + }, + "summary": "Returns a curated set of system configuration settings for authorized users." + } + }, + "/system/settings/unauthorized": { + "get": { + "description": " [internal route ID: \"get-system-settings-unauthorized\"]\n\n", + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/SystemSettingsPublic" + } + } + }, + "description": "" + } + }, + "summary": "Returns a curated set of system configuration settings." + } + }, + "/teams/invitations/by-email": { + "head": { + "description": " [internal route ID: \"head-team-invitations\"]\n\n", + "parameters": [ + { + "description": "Email address", + "in": "query", + "name": "email", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Pending invitation exists." + }, + "404": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "No pending invitations exists." + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "No pending invitations exists." + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "No pending invitations exists. (label: `not-found`)" + }, + "409": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 409, + "label": "conflicting-invitations", + "message": "Multiple conflicting invitations to different teams exists." + }, + "properties": { + "code": { + "enum": [ + 409 + ], + "type": "integer" + }, + "label": { + "enum": [ + "conflicting-invitations" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 409, + "label": "conflicting-invitations", + "message": "Multiple conflicting invitations to different teams exists." + }, + "properties": { + "code": { + "enum": [ + 409 + ], + "type": "integer" + }, + "label": { + "enum": [ + "conflicting-invitations" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Multiple conflicting invitations to different teams exists. (label: `conflicting-invitations`)" + } + }, + "summary": "Check if there is an invitation pending given an email address." + } + }, + "/teams/invitations/info": { + "get": { + "description": " [internal route ID: \"get-team-invitation-info\"]\n\n", + "parameters": [ + { + "description": "Invitation code", + "in": "query", + "name": "code", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Invitation" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Invitation" + } + } + }, + "description": "Invitation info" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-invitation-code", + "message": "Invalid invitation code." + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-invitation-code" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `code`\n\nInvalid invitation code. (label: `invalid-invitation-code`)" + } + }, + "summary": "Get invitation info given a code." + } + }, + "/teams/notifications": { + "get": { + "description": " [internal route ID: \"get-team-notifications\"]\n\nThis is a work-around for scalability issues with gundeck user event fan-out. It does not track all team-wide events, but only `member-join`.\nNote that `/teams/notifications` behaves differently from `/notifications`:\n- If there is a gap between the notification id requested with `since` and the available data, team queues respond with 200 and the data that could be found. They do NOT respond with status 404, but valid data in the body.\n- The notification with the id given via `since` is included in the response if it exists. You should remove this and only use it to decide whether there was a gap between your last request and this one.\n- If the notification id does *not* exist, you get the more recent events from the queue (instead of all of them). This can be done because a notification id is a UUIDv1, which is essentially a time stamp.\n- There is no corresponding `/last` end-point to get only the most recent event. That end-point was only useful to avoid having to pull the entire queue. In team queues, if you have never requested the queue before and have no prior notification id, just pull with timestamp 'now'.", + "parameters": [ + { + "description": "Notification id to start with in the response (UUIDv1)", + "in": "query", + "name": "since", + "required": false, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "description": "Maximum number of events to return (1..10000; default: 1000)", + "in": "query", + "name": "size", + "required": false, + "schema": { + "format": "int32", + "maximum": 10000, + "minimum": 1, + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/QueuedNotificationList" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-notification-id", + "message": "Could not parse notification id (must be UUIDv1)." + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-notification-id" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `size` or `since`\n\nCould not parse notification id (must be UUIDv1). (label: `invalid-notification-id`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Team not found (label: `no-team`)" + } + }, + "summary": "Read recently added team members from team queue" + } + }, + "/teams/{team-id}/services/whitelist": { + "post": { + "description": " [internal route ID: \"post-team-whitelist-by-team-id\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "team-id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/UpdateServiceWhitelist" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "UpdateServiceWhitelistRespChanged" + }, + "204": { + "description": "UpdateServiceWhitelistRespUnchanged" + } + }, + "summary": "Update service whitelist" + } + }, + "/teams/{team-id}/services/whitelisted": { + "get": { + "description": " [internal route ID: \"get-whitelisted-services-by-team-id\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "team-id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "in": "query", + "name": "prefix", + "required": false, + "schema": { + "maxLength": 1, + "minLength": 128, + "type": "string" + } + }, + { + "in": "query", + "name": "filter_disabled", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "in": "query", + "name": "size", + "required": false, + "schema": { + "format": "int32", + "maximum": 100, + "minimum": 10, + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ServiceProfile" + } + } + }, + "description": "" + } + }, + "summary": "Get whitelisted services by team id" + } + }, + "/teams/{tid}": { + "delete": { + "description": " [internal route ID: \"delete-team\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/TeamDeleteData" + } + } + }, + "required": true + }, + "responses": { + "202": { + "description": "Team is scheduled for removal" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "code-authentication-required", + "message": "Verification code required" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "code-authentication-required", + "code-authentication-failed", + "access-denied", + "operation-denied", + "no-team-member" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Verification code required (label: `code-authentication-required`)\n\nCode authentication failed (label: `code-authentication-failed`)\n\nThis operation requires reauthentication (label: `access-denied`)\n\nInsufficient permissions (label: `operation-denied`)\n\nRequesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (missing DeleteTeam) (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + }, + "503": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 503, + "label": "queue-full", + "message": "The delete queue is full; no further delete requests can be processed at the moment" + }, + "properties": { + "code": { + "enum": [ + 503 + ], + "type": "integer" + }, + "label": { + "enum": [ + "queue-full" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The delete queue is full; no further delete requests can be processed at the moment (label: `queue-full`)" + } + }, + "summary": "Delete a team" + }, + "get": { + "description": " [internal route ID: \"get-team\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Team" + } + } + }, + "description": "" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Get a team by ID" + }, + "put": { + "description": " [internal route ID: \"update-team\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/TeamUpdateData" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Team updated" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "operation-denied", + "message": "Insufficient permissions (missing SetTeamData)" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "operation-denied", + "no-team-member" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Insufficient permissions (missing SetTeamData) (label: `operation-denied`)\n\nRequesting user is not a team member (label: `no-team-member`)" + } + }, + "summary": "Update team properties" + } + }, + "/teams/{tid}/conversations": { + "get": { + "description": " [internal route ID: \"get-team-conversations\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/TeamConversationList" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + } + }, + "summary": "Get team conversations" + } + }, + "/teams/{tid}/conversations/roles": { + "get": { + "description": " [internal route ID: \"get-team-conversation-roles\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ConversationRolesList" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)" + } + }, + "summary": "Get existing roles available for the given team" + } + }, + "/teams/{tid}/conversations/{cid}": { + "delete": { + "description": " [internal route ID: \"delete-team-conversation\"]\n\nCalls federation service brig on get-users-by-ids
Calls federation service galley on on-mls-message-sent
Calls federation service galley on on-conversation-updated", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "in": "path", + "name": "cid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Conversation deleted" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "invalid-op", + "action-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInvalid operation (label: `invalid-op`)\n\nInsufficient authorization (missing delete_conversation) (label: `action-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` or `cid` not found\n\nConversation not found (label: `no-conversation`)" + } + }, + "summary": "Remove a team conversation" + }, + "get": { + "description": " [internal route ID: \"get-team-conversation\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "in": "path", + "name": "cid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/TeamConversation" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-conversation", + "message": "Conversation not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-conversation" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` or `cid` not found\n\nConversation not found (label: `no-conversation`)" + } + }, + "summary": "Get one team conversation" + } + }, + "/teams/{tid}/features": { + "get": { + "description": " [internal route ID: \"get-all-feature-configs-for-team\"]\n\nGets feature configs for a team. User must be a member of the team and have permission to view team features.", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/AllFeatureConfigs" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "operation-denied", + "message": "Insufficient permissions" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "operation-denied", + "no-team-member" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Insufficient permissions (label: `operation-denied`)\n\nRequesting user is not a team member (label: `no-team-member`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Gets feature configs for a team" + } + }, + "/teams/{tid}/features/appLock": { + "get": { + "description": " [internal route ID: (\"get\", AppLockConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/AppLockConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Get config for appLock" + }, + "put": { + "description": " [internal route ID: (\"put\", AppLockConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/AppLockConfig.WithStatusNoLock" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/AppLockConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Put config for appLock" + } + }, + "/teams/{tid}/features/classifiedDomains": { + "get": { + "description": " [internal route ID: (\"get\", ClassifiedDomainsConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ClassifiedDomainsConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Get config for classifiedDomains" + } + }, + "/teams/{tid}/features/conferenceCalling": { + "get": { + "description": " [internal route ID: (\"get\", ConferenceCallingConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ConferenceCallingConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Get config for conferenceCalling" + }, + "put": { + "description": " [internal route ID: (\"put\", ConferenceCallingConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ConferenceCallingConfig.WithStatusNoLock" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ConferenceCallingConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Put config for conferenceCalling" + } + }, + "/teams/{tid}/features/conversationGuestLinks": { + "get": { + "description": " [internal route ID: (\"get\", GuestLinksConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/GuestLinksConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Get config for conversationGuestLinks" + }, + "put": { + "description": " [internal route ID: (\"put\", GuestLinksConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/GuestLinksConfig.WithStatusNoLock" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/GuestLinksConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Put config for conversationGuestLinks" + } + }, + "/teams/{tid}/features/digitalSignatures": { + "get": { + "description": " [internal route ID: (\"get\", DigitalSignaturesConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/DigitalSignaturesConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Get config for digitalSignatures" + } + }, + "/teams/{tid}/features/enforceFileDownloadLocation": { + "get": { + "description": " [internal route ID: (\"get\", EnforceFileDownloadLocationConfig)]\n\n

Custom feature: only supported for some decidated on-prem systems.

", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/EnforceFileDownloadLocation.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Get config for enforceFileDownloadLocation" + }, + "put": { + "description": " [internal route ID: (\"put\", EnforceFileDownloadLocationConfig)]\n\n

Custom feature: only supported for some decidated on-prem systems.

", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/EnforceFileDownloadLocation.WithStatusNoLock" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/EnforceFileDownloadLocation.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Put config for enforceFileDownloadLocation" + } + }, + "/teams/{tid}/features/exposeInvitationURLsToTeamAdmin": { + "get": { + "description": " [internal route ID: (\"get\", ExposeInvitationURLsToTeamAdminConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ExposeInvitationURLsToTeamAdminConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Get config for exposeInvitationURLsToTeamAdmin" + }, + "put": { + "description": " [internal route ID: (\"put\", ExposeInvitationURLsToTeamAdminConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ExposeInvitationURLsToTeamAdminConfig.WithStatusNoLock" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ExposeInvitationURLsToTeamAdminConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Put config for exposeInvitationURLsToTeamAdmin" + } + }, + "/teams/{tid}/features/fileSharing": { + "get": { + "description": " [internal route ID: (\"get\", FileSharingConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/FileSharingConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Get config for fileSharing" + }, + "put": { + "description": " [internal route ID: (\"put\", FileSharingConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/FileSharingConfig.WithStatusNoLock" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/FileSharingConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Put config for fileSharing" + } + }, + "/teams/{tid}/features/legalhold": { + "get": { + "description": " [internal route ID: (\"get\", LegalholdConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/LegalholdConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Get config for legalhold" + }, + "put": { + "description": " [internal route ID: (\"put\", LegalholdConfig)]\n\nCalls federation service galley on on-mls-message-sent
Calls federation service galley on on-conversation-updated", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/LegalholdConfig.WithStatusNoLock" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/LegalholdConfig.WithStatus" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "legalhold-not-registered", + "message": "legal hold service has not been registered for this team" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "legalhold-not-registered" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nlegal hold service has not been registered for this team (label: `legalhold-not-registered`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "legalhold-disable-unimplemented", + "message": "legal hold cannot be disabled for whitelisted teams" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "legalhold-disable-unimplemented", + "legalhold-not-enabled", + "too-large-team-for-legalhold", + "code-authentication-required", + "code-authentication-failed", + "access-denied", + "action-denied", + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "legal hold cannot be disabled for whitelisted teams (label: `legalhold-disable-unimplemented`)\n\nlegal hold is not enabled for this team (label: `legalhold-not-enabled`)\n\nCannot enable legalhold on large teams (reason: for removing LH from team, we need to iterate over all members, which is only supported for teams with less than 2k members) (label: `too-large-team-for-legalhold`)\n\nVerification code required (label: `code-authentication-required`)\n\nCode authentication failed (label: `code-authentication-failed`)\n\nThis operation requires reauthentication (label: `access-denied`)\n\nInsufficient authorization (missing remove_conversation_member) (label: `action-denied`)\n\nRequesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + }, + "500": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 500, + "label": "legalhold-internal", + "message": "legal hold service: could not block connections when resolving policy conflicts." + }, + "properties": { + "code": { + "enum": [ + 500 + ], + "type": "integer" + }, + "label": { + "enum": [ + "legalhold-internal", + "legalhold-illegal-op" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "legal hold service: could not block connections when resolving policy conflicts. (label: `legalhold-internal`)\n\ninternal server error: inconsistent change of user's legalhold state (label: `legalhold-illegal-op`)" + } + }, + "summary": "Put config for legalhold" + } + }, + "/teams/{tid}/features/limitedEventFanout": { + "get": { + "description": " [internal route ID: (\"get\", LimitedEventFanoutConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/LimitedEventFanoutConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Get config for limitedEventFanout" + } + }, + "/teams/{tid}/features/mls": { + "get": { + "description": " [internal route ID: (\"get\", MLSConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/MLSConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Get config for mls" + }, + "put": { + "description": " [internal route ID: (\"put\", MLSConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/MLSConfig.WithStatusNoLock" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/MLSConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Put config for mls" + } + }, + "/teams/{tid}/features/mlsE2EId": { + "get": { + "description": " [internal route ID: (\"get\", MlsE2EIdConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/MlsE2EIdConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Get config for mlsE2EId" + }, + "put": { + "description": " [internal route ID: (\"put\", MlsE2EIdConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/MlsE2EIdConfig.WithStatusNoLock" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/MlsE2EIdConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Put config for mlsE2EId" + } + }, + "/teams/{tid}/features/mlsMigration": { + "get": { + "description": " [internal route ID: (\"get\", MlsMigrationConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/MlsMigration.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Get config for mlsMigration" + }, + "put": { + "description": " [internal route ID: (\"put\", MlsMigrationConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/MlsMigration.WithStatusNoLock" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/MlsMigration.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Put config for mlsMigration" + } + }, + "/teams/{tid}/features/outlookCalIntegration": { + "get": { + "description": " [internal route ID: (\"get\", OutlookCalIntegrationConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/OutlookCalIntegrationConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Get config for outlookCalIntegration" + }, + "put": { + "description": " [internal route ID: (\"put\", OutlookCalIntegrationConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/OutlookCalIntegrationConfig.WithStatusNoLock" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/OutlookCalIntegrationConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Put config for outlookCalIntegration" + } + }, + "/teams/{tid}/features/searchVisibility": { + "get": { + "description": " [internal route ID: (\"get\", SearchVisibilityAvailableConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/SearchVisibilityAvailableConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Get config for searchVisibility" + }, + "put": { + "description": " [internal route ID: (\"put\", SearchVisibilityAvailableConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/SearchVisibilityAvailableConfig.WithStatusNoLock" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/SearchVisibilityAvailableConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Put config for searchVisibility" + } + }, + "/teams/{tid}/features/searchVisibilityInbound": { + "get": { + "description": " [internal route ID: (\"get\", SearchVisibilityInboundConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/SearchVisibilityInboundConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Get config for searchVisibilityInbound" + }, + "put": { + "description": " [internal route ID: (\"put\", SearchVisibilityInboundConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/SearchVisibilityInboundConfig.WithStatusNoLock" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/SearchVisibilityInboundConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Put config for searchVisibilityInbound" + } + }, + "/teams/{tid}/features/selfDeletingMessages": { + "get": { + "description": " [internal route ID: (\"get\", SelfDeletingMessagesConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/SelfDeletingMessagesConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Get config for selfDeletingMessages" + }, + "put": { + "description": " [internal route ID: (\"put\", SelfDeletingMessagesConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/SelfDeletingMessagesConfig.WithStatusNoLock" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/SelfDeletingMessagesConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Put config for selfDeletingMessages" + } + }, + "/teams/{tid}/features/sndFactorPasswordChallenge": { + "get": { + "description": " [internal route ID: (\"get\", SndFactorPasswordChallengeConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/SndFactorPasswordChallengeConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Get config for sndFactorPasswordChallenge" + }, + "put": { + "description": " [internal route ID: (\"put\", SndFactorPasswordChallengeConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/SndFactorPasswordChallengeConfig.WithStatusNoLock" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/SndFactorPasswordChallengeConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Put config for sndFactorPasswordChallenge" + } + }, + "/teams/{tid}/features/sso": { + "get": { + "description": " [internal route ID: (\"get\", SSOConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/SSOConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Get config for sso" + } + }, + "/teams/{tid}/features/validateSAMLemails": { + "get": { + "description": " [internal route ID: (\"get\", ValidateSAMLEmailsConfig)]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ValidateSAMLEmailsConfig.WithStatus" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "operation-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Get config for validateSAMLemails" + } + }, + "/teams/{tid}/get-members-by-ids-using-post": { + "post": { + "description": " [internal route ID: \"get-team-members-by-ids\"]\n\nThe `has_more` field in the response body is always `false`.", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "description": "Maximum results to be returned", + "in": "query", + "name": "maxResults", + "required": false, + "schema": { + "format": "int32", + "maximum": 2000, + "minimum": 1, + "type": "integer" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/UserIdList" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/TeamMemberList" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "too-many-uids", + "message": "Can only process 2000 user ids per request." + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "too-many-uids" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body` or `maxResults`\n\nCan only process 2000 user ids per request. (label: `too-many-uids`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)" + } + }, + "summary": "Get team members by user id list" + } + }, + "/teams/{tid}/invitations": { + "get": { + "description": " [internal route ID: \"get-team-invitations\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "description": "Invitation id to start from (ascending).", + "in": "query", + "name": "start", + "required": false, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "description": "Number of results to return (default 100, max 500).", + "in": "query", + "name": "size", + "required": false, + "schema": { + "format": "int32", + "maximum": 500, + "minimum": 1, + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvitationList" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/InvitationList" + } + } + }, + "description": "List of sent invitations" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "insufficient-permissions", + "message": "Insufficient team permissions" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "insufficient-permissions" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Insufficient team permissions (label: `insufficient-permissions`)" + } + }, + "summary": "List the sent team invitations" + }, + "post": { + "description": " [internal route ID: \"send-team-invitation\"]\n\nInvitations are sent by email. The maximum allowed number of pending team invitations is equal to the team size.", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/InvitationRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Invitation" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Invitation" + } + } + }, + "description": "Invitation was created and sent.", + "headers": { + "Location": { + "schema": { + "format": "url", + "type": "string" + } + } + } + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-email", + "message": "Invalid e-mail address." + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-email" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nInvalid e-mail address. (label: `invalid-email`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "insufficient-permissions", + "message": "Insufficient team permissions" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "insufficient-permissions", + "too-many-team-invitations", + "blacklisted-email", + "no-identity", + "no-email" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Insufficient team permissions (label: `insufficient-permissions`)\n\nToo many team invitations for this team (label: `too-many-team-invitations`)\n\nThe given e-mail address has been blacklisted due to a permanent bounce or a complaint. (label: `blacklisted-email`)\n\nThe user has no verified identity (email or phone number) (label: `no-identity`)\n\nThis operation requires the user to have a verified email address. (label: `no-email`)" + } + }, + "summary": "Create and send a new team invitation." + } + }, + "/teams/{tid}/invitations/{iid}": { + "delete": { + "description": " [internal route ID: \"delete-team-invitation\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "in": "path", + "name": "iid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Invitation deleted" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "insufficient-permissions", + "message": "Insufficient team permissions" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "insufficient-permissions" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Insufficient team permissions (label: `insufficient-permissions`)" + } + }, + "summary": "Delete a pending team invitation by ID." + }, + "get": { + "description": " [internal route ID: \"get-team-invitation\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "in": "path", + "name": "iid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Invitation" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Invitation" + } + } + }, + "description": "Invitation" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "insufficient-permissions", + "message": "Insufficient team permissions" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "insufficient-permissions" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Insufficient team permissions (label: `insufficient-permissions`)" + }, + "404": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Notification not found." + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Notification not found." + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` or `iid` or Notification not found. (label: `not-found`)" + } + }, + "summary": "Get a pending team invitation by ID." + } + }, + "/teams/{tid}/legalhold/consent": { + "post": { + "description": " [internal route ID: \"consent-to-legal-hold\"]\n\nCalls federation service brig on get-users-by-ids
Calls federation service galley on on-mls-message-sent
Calls federation service galley on on-conversation-updated", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "Grant consent successful" + }, + "204": { + "description": "Consent already granted" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "invalid-op", + "message": "Invalid operation" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-op", + "action-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid operation (label: `invalid-op`)\n\nInsufficient authorization (missing remove_conversation_member) (label: `action-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team-member", + "message": "Team member not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam member not found (label: `no-team-member`)" + }, + "500": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 500, + "label": "legalhold-internal", + "message": "legal hold service: could not block connections when resolving policy conflicts." + }, + "properties": { + "code": { + "enum": [ + 500 + ], + "type": "integer" + }, + "label": { + "enum": [ + "legalhold-internal", + "legalhold-illegal-op" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "legal hold service: could not block connections when resolving policy conflicts. (label: `legalhold-internal`)\n\ninternal server error: inconsistent change of user's legalhold state (label: `legalhold-illegal-op`)" + } + }, + "summary": "Consent to legal hold" + } + }, + "/teams/{tid}/legalhold/settings": { + "delete": { + "description": " [internal route ID: \"delete-legal-hold-settings\"]\n\nThis endpoint can lead to the following events being sent:\n- ClientRemoved event to members with a legalhold client (via brig)\n- UserLegalHoldDisabled event to contacts of members with a legalhold client (via brig)
Calls federation service brig on get-users-by-ids
Calls federation service galley on on-mls-message-sent
Calls federation service galley on on-conversation-updated", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/RemoveLegalHoldSettingsRequest" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Legal hold service settings deleted" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "legalhold-not-registered", + "message": "legal hold service has not been registered for this team" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "legalhold-not-registered" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nlegal hold service has not been registered for this team (label: `legalhold-not-registered`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "legalhold-disable-unimplemented", + "message": "legal hold cannot be disabled for whitelisted teams" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "legalhold-disable-unimplemented", + "legalhold-not-enabled", + "invalid-op", + "action-denied", + "no-team-member", + "operation-denied", + "code-authentication-required", + "code-authentication-failed", + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "legal hold cannot be disabled for whitelisted teams (label: `legalhold-disable-unimplemented`)\n\nlegal hold is not enabled for this team (label: `legalhold-not-enabled`)\n\nInvalid operation (label: `invalid-op`)\n\nInsufficient authorization (missing remove_conversation_member) (label: `action-denied`)\n\nRequesting user is not a team member (label: `no-team-member`)\n\nInsufficient permissions (label: `operation-denied`)\n\nVerification code required (label: `code-authentication-required`)\n\nCode authentication failed (label: `code-authentication-failed`)\n\nThis operation requires reauthentication (label: `access-denied`)" + }, + "500": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 500, + "label": "legalhold-internal", + "message": "legal hold service: could not block connections when resolving policy conflicts." + }, + "properties": { + "code": { + "enum": [ + 500 + ], + "type": "integer" + }, + "label": { + "enum": [ + "legalhold-internal", + "legalhold-illegal-op" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "legal hold service: could not block connections when resolving policy conflicts. (label: `legalhold-internal`)\n\ninternal server error: inconsistent change of user's legalhold state (label: `legalhold-illegal-op`)" + } + }, + "summary": "Delete legal hold service settings" + }, + "get": { + "description": " [internal route ID: \"get-legal-hold-settings\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ViewLegalHoldService" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "operation-denied", + "message": "Insufficient permissions" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "operation-denied", + "no-team-member" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Insufficient permissions (label: `operation-denied`)\n\nRequesting user is not a team member (label: `no-team-member`)" + } + }, + "summary": "Get legal hold service settings" + }, + "post": { + "description": " [internal route ID: \"create-legal-hold-settings\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/NewLegalHoldService" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ViewLegalHoldService" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ViewLegalHoldService" + } + } + }, + "description": "Legal hold service settings created" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "legalhold-status-bad", + "message": "legal hold service: invalid response" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "legalhold-status-bad", + "legalhold-invalid-key" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nlegal hold service: invalid response (label: `legalhold-status-bad`)\n\nlegal hold service pubkey is invalid (label: `legalhold-invalid-key`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "legalhold-not-enabled", + "message": "legal hold is not enabled for this team" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "legalhold-not-enabled", + "operation-denied", + "no-team-member" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "legal hold is not enabled for this team (label: `legalhold-not-enabled`)\n\nInsufficient permissions (label: `operation-denied`)\n\nRequesting user is not a team member (label: `no-team-member`)" + } + }, + "summary": "Create legal hold service settings" + } + }, + "/teams/{tid}/legalhold/{uid}": { + "delete": { + "description": " [internal route ID: \"disable-legal-hold-for-user\"]\n\nThis endpoint can lead to the following events being sent:\n- ClientRemoved event to the user owning the client (via brig)\n- UserLegalHoldDisabled event to contacts of the user owning the client (via brig)
Calls federation service brig on get-users-by-ids
Calls federation service galley on on-mls-message-sent
Calls federation service galley on on-conversation-updated", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "in": "path", + "name": "uid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/DisableLegalHoldForUserRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Disable legal hold successful" + }, + "204": { + "description": "Legal hold was not enabled" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "legalhold-not-registered", + "message": "legal hold service has not been registered for this team" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "legalhold-not-registered" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nlegal hold service has not been registered for this team (label: `legalhold-not-registered`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "operation-denied", + "message": "Insufficient permissions" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "operation-denied", + "no-team-member", + "action-denied", + "code-authentication-required", + "code-authentication-failed", + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Insufficient permissions (label: `operation-denied`)\n\nRequesting user is not a team member (label: `no-team-member`)\n\nInsufficient authorization (missing remove_conversation_member) (label: `action-denied`)\n\nVerification code required (label: `code-authentication-required`)\n\nCode authentication failed (label: `code-authentication-failed`)\n\nThis operation requires reauthentication (label: `access-denied`)" + }, + "500": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 500, + "label": "legalhold-internal", + "message": "legal hold service: could not block connections when resolving policy conflicts." + }, + "properties": { + "code": { + "enum": [ + 500 + ], + "type": "integer" + }, + "label": { + "enum": [ + "legalhold-internal", + "legalhold-illegal-op" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "legal hold service: could not block connections when resolving policy conflicts. (label: `legalhold-internal`)\n\ninternal server error: inconsistent change of user's legalhold state (label: `legalhold-illegal-op`)" + } + }, + "summary": "Disable legal hold for user" + }, + "get": { + "description": " [internal route ID: \"get-legal-hold\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "in": "path", + "name": "uid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/UserLegalHoldStatusResponse" + } + } + }, + "description": "" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team-member", + "message": "Team member not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` or `uid` not found\n\nTeam member not found (label: `no-team-member`)" + } + }, + "summary": "Get legal hold status" + }, + "post": { + "description": " [internal route ID: \"request-legal-hold-device\"]\n\nThis endpoint can lead to the following events being sent:\n- LegalHoldClientRequested event to contacts of the user the device is requested for, if they didn't already have a legalhold client (via brig)
Calls federation service brig on get-users-by-ids
Calls federation service galley on on-mls-message-sent
Calls federation service galley on on-conversation-updated", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "in": "path", + "name": "uid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "Request device successful" + }, + "204": { + "description": "Request device already pending" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "legalhold-not-registered", + "message": "legal hold service has not been registered for this team" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "legalhold-not-registered", + "legalhold-status-bad" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "legal hold service has not been registered for this team (label: `legalhold-not-registered`)\n\nlegal hold service: invalid response (label: `legalhold-status-bad`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "legalhold-not-enabled", + "message": "legal hold is not enabled for this team" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "legalhold-not-enabled", + "operation-denied", + "no-team-member", + "action-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "legal hold is not enabled for this team (label: `legalhold-not-enabled`)\n\nInsufficient permissions (label: `operation-denied`)\n\nRequesting user is not a team member (label: `no-team-member`)\n\nInsufficient authorization (missing remove_conversation_member) (label: `action-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team-member", + "message": "Team member not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` or `uid` not found\n\nTeam member not found (label: `no-team-member`)" + }, + "409": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 409, + "label": "legalhold-no-consent", + "message": "user has not given consent to using legal hold" + }, + "properties": { + "code": { + "enum": [ + 409 + ], + "type": "integer" + }, + "label": { + "enum": [ + "legalhold-no-consent", + "legalhold-already-enabled" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "user has not given consent to using legal hold (label: `legalhold-no-consent`)\n\nlegal hold is already enabled for this user (label: `legalhold-already-enabled`)" + }, + "500": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 500, + "label": "legalhold-illegal-op", + "message": "internal server error: inconsistent change of user's legalhold state" + }, + "properties": { + "code": { + "enum": [ + 500 + ], + "type": "integer" + }, + "label": { + "enum": [ + "legalhold-illegal-op", + "legalhold-internal" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "internal server error: inconsistent change of user's legalhold state (label: `legalhold-illegal-op`)\n\nlegal hold service: could not block connections when resolving policy conflicts. (label: `legalhold-internal`)" + } + }, + "summary": "Request legal hold device" + } + }, + "/teams/{tid}/legalhold/{uid}/approve": { + "put": { + "description": " [internal route ID: \"approve-legal-hold-device\"]\n\nThis endpoint can lead to the following events being sent:\n- ClientAdded event to the user owning the client (via brig)\n- UserLegalHoldEnabled event to contacts of the user owning the client (via brig)\n- ClientRemoved event to the user, if removing old client due to max number (via brig)
Calls federation service brig on get-users-by-ids
Calls federation service galley on on-mls-message-sent
Calls federation service galley on on-conversation-updated", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "in": "path", + "name": "uid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ApproveLegalHoldForUserRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Legal hold approved" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "legalhold-not-registered", + "message": "legal hold service has not been registered for this team" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "legalhold-not-registered" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid `body`\n\nlegal hold service has not been registered for this team (label: `legalhold-not-registered`)" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "legalhold-not-enabled", + "message": "legal hold is not enabled for this team" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "legalhold-not-enabled", + "no-team-member", + "action-denied", + "access-denied", + "code-authentication-required", + "code-authentication-failed" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "legal hold is not enabled for this team (label: `legalhold-not-enabled`)\n\nRequesting user is not a team member (label: `no-team-member`)\n\nInsufficient authorization (missing remove_conversation_member) (label: `action-denied`)\n\nYou do not have permission to access this resource (label: `access-denied`)\n\nVerification code required (label: `code-authentication-required`)\n\nCode authentication failed (label: `code-authentication-failed`)\n\nThis operation requires reauthentication (label: `access-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "legalhold-no-device-allocated", + "message": "no legal hold device is registered for this user. POST /teams/:tid/legalhold/:uid/ to start the flow." + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "legalhold-no-device-allocated" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` or `uid` not found\n\nno legal hold device is registered for this user. POST /teams/:tid/legalhold/:uid/ to start the flow. (label: `legalhold-no-device-allocated`)" + }, + "409": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 409, + "label": "legalhold-already-enabled", + "message": "legal hold is already enabled for this user" + }, + "properties": { + "code": { + "enum": [ + 409 + ], + "type": "integer" + }, + "label": { + "enum": [ + "legalhold-already-enabled" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "legal hold is already enabled for this user (label: `legalhold-already-enabled`)" + }, + "412": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 412, + "label": "legalhold-not-pending", + "message": "legal hold cannot be approved without being in a pending state" + }, + "properties": { + "code": { + "enum": [ + 412 + ], + "type": "integer" + }, + "label": { + "enum": [ + "legalhold-not-pending" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "legal hold cannot be approved without being in a pending state (label: `legalhold-not-pending`)" + }, + "500": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 500, + "label": "legalhold-internal", + "message": "legal hold service: could not block connections when resolving policy conflicts." + }, + "properties": { + "code": { + "enum": [ + 500 + ], + "type": "integer" + }, + "label": { + "enum": [ + "legalhold-internal", + "legalhold-illegal-op" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "legal hold service: could not block connections when resolving policy conflicts. (label: `legalhold-internal`)\n\ninternal server error: inconsistent change of user's legalhold state (label: `legalhold-illegal-op`)" + } + }, + "summary": "Approve legal hold device" + } + }, + "/teams/{tid}/members": { + "get": { + "description": " [internal route ID: \"get-team-members\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "description": "Maximum results to be returned", + "in": "query", + "name": "maxResults", + "required": false, + "schema": { + "format": "int32", + "maximum": 2000, + "minimum": 1, + "type": "integer" + } + }, + { + "description": "Optional, when not specified, the first page will be returned.Every returned page contains a `pagingState`, this should be supplied to retrieve the next page.", + "in": "query", + "name": "pagingState", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/TeamMembersPage" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)" + } + }, + "summary": "Get team members" + }, + "put": { + "description": " [internal route ID: \"update-team-member\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/NewTeamMember" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "operation-denied", + "message": "Insufficient permissions" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "operation-denied", + "no-team-member", + "too-many-team-admins", + "invalid-permissions", + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Insufficient permissions (label: `operation-denied`)\n\nRequesting user is not a team member (label: `no-team-member`)\n\nMaximum number of admins per team reached (label: `too-many-team-admins`)\n\nThe specified permissions are invalid (label: `invalid-permissions`)\n\nYou do not have permission to access this resource (label: `access-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team-member", + "message": "Team member not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member", + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam member not found (label: `no-team-member`)\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Update an existing team member" + } + }, + "/teams/{tid}/members/csv": { + "get": { + "description": " [internal route ID: \"get-team-members-csv\"]\n\nThe endpoint returns data in chunked transfer encoding. Internal server errors might result in a failed transfer instead of a 500 response.", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "text/csv": {} + }, + "description": "CSV of team members" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "access-denied", + "message": "You do not have permission to access this resource" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "access-denied" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "You do not have permission to access this resource (label: `access-denied`)" + } + }, + "summary": "Get all members of the team as a CSV file" + } + }, + "/teams/{tid}/members/{uid}": { + "delete": { + "description": " [internal route ID: \"delete-team-member\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "in": "path", + "name": "uid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/TeamMemberDeleteData" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "" + }, + "202": { + "description": "Team member scheduled for deletion" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "operation-denied", + "message": "Insufficient permissions" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "operation-denied", + "no-team-member", + "access-denied", + "code-authentication-required", + "code-authentication-failed" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Insufficient permissions (label: `operation-denied`)\n\nRequesting user is not a team member (label: `no-team-member`)\n\nYou do not have permission to access this resource (label: `access-denied`)\n\nVerification code required (label: `code-authentication-required`)\n\nCode authentication failed (label: `code-authentication-failed`)\n\nThis operation requires reauthentication (label: `access-denied`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team", + "no-team-member" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` or `uid` not found\n\nTeam not found (label: `no-team`)\n\nTeam member not found (label: `no-team-member`)" + } + }, + "summary": "Remove an existing team member" + }, + "get": { + "description": " [internal route ID: \"get-team-member\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "in": "path", + "name": "uid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/TeamMember" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "no-team-member", + "message": "Requesting user is not a team member" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Requesting user is not a team member (label: `no-team-member`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team-member", + "message": "Team member not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team-member" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` or `uid` not found\n\nTeam member not found (label: `no-team-member`)" + } + }, + "summary": "Get single team member" + } + }, + "/teams/{tid}/search": { + "get": { + "description": " [internal route ID: \"browse-team\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "description": "Search expression", + "in": "query", + "name": "q", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Role filter, eg. `member,partner`. Empty list means do not filter.", + "in": "query", + "name": "frole", + "required": false, + "schema": { + "items": { + "enum": [ + "owner", + "admin", + "member", + "partner" + ], + "type": "string" + }, + "type": "array" + } + }, + { + "description": "Can be one of name, handle, email, saml_idp, managed_by, role, created_at.", + "in": "query", + "name": "sortby", + "required": false, + "schema": { + "enum": [ + "name", + "handle", + "email", + "saml_idp", + "managed_by", + "role", + "created_at" + ], + "type": "string" + } + }, + { + "description": "Can be one of asc, desc.", + "in": "query", + "name": "sortorder", + "required": false, + "schema": { + "enum": [ + "asc", + "desc" + ], + "type": "string" + } + }, + { + "description": "Number of results to return (min: 1, max: 500, default: 15)", + "in": "query", + "name": "size", + "required": false, + "schema": { + "format": "int32", + "maximum": 500, + "minimum": 1, + "type": "integer" + } + }, + { + "description": "Optional, when not specified, the first page will be returned. Every returned page contains a `paging_state`, this should be supplied to retrieve the next page.", + "in": "query", + "name": "pagingState", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchResult" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/SearchResult" + } + } + }, + "description": "Search results" + } + }, + "summary": "Browse team for members (requires add-user permission)" + } + }, + "/teams/{tid}/search-visibility": { + "get": { + "description": " [internal route ID: \"get-search-visibility\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/TeamSearchVisibilityView" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "operation-denied", + "message": "Insufficient permissions" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "operation-denied", + "no-team-member" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Insufficient permissions (label: `operation-denied`)\n\nRequesting user is not a team member (label: `no-team-member`)" + } + }, + "summary": "Shows the value for search visibility" + }, + "put": { + "description": " [internal route ID: \"set-search-visibility\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/TeamSearchVisibilityView" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Search visibility set" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "team-search-visibility-not-enabled", + "message": "Custom search is not available for this team" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "team-search-visibility-not-enabled", + "operation-denied", + "no-team-member" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Custom search is not available for this team (label: `team-search-visibility-not-enabled`)\n\nInsufficient permissions (label: `operation-denied`)\n\nRequesting user is not a team member (label: `no-team-member`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "no-team", + "message": "Team not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "no-team" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`tid` not found\n\nTeam not found (label: `no-team`)" + } + }, + "summary": "Sets the search visibility for the whole team" + } + }, + "/teams/{tid}/size": { + "get": { + "description": " [internal route ID: \"get-team-size\"]\n\nCan be out of sync by roughly the `refresh_interval` of the ES index.", + "parameters": [ + { + "in": "path", + "name": "tid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TeamSize" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/TeamSize" + } + } + }, + "description": "Number of team members" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-invitation-code", + "message": "Invalid invitation code." + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-invitation-code" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Invalid invitation code. (label: `invalid-invitation-code`)" + } + }, + "summary": "Get the number of team members as an integer" + } + }, + "/users/handles": { + "post": { + "description": " [internal route ID: \"check-user-handles\"]\n\n", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/CheckHandles" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Handle" + }, + "type": "array" + } + }, + "application/json;charset=utf-8": { + "schema": { + "items": { + "$ref": "#/components/schemas/Handle" + }, + "type": "array" + } + } + }, + "description": "List of free handles" + } + }, + "summary": "Check availability of user handles" + } + }, + "/users/handles/{handle}": { + "head": { + "description": " [internal route ID: \"check-user-handle\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "handle", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "example": [], + "items": {}, + "maxItems": 0, + "type": "array" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": [], + "items": {}, + "maxItems": 0, + "type": "array" + } + } + }, + "description": "Handle is taken" + }, + "400": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 400, + "label": "invalid-handle", + "message": "The given handle is invalid" + }, + "properties": { + "code": { + "enum": [ + 400 + ], + "type": "integer" + }, + "label": { + "enum": [ + "invalid-handle" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "The given handle is invalid (label: `invalid-handle`)" + }, + "404": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "Handle not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`handle` not found\n\nHandle not found (label: `not-found`)" + } + }, + "summary": "Check whether a user handle can be taken" + } + }, + "/users/list-clients": { + "post": { + "description": " [internal route ID: \"list-clients-bulk@v2\"]\n\nIf a backend is unreachable, the clients from that backend will be omitted from the responseCalls federation service brig on get-user-clients", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/LimitedQualifiedUserIdList_500" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "properties": { + "qualified_user_map": { + "$ref": "#/components/schemas/QualifiedUserMap_Set_PubClient" + } + }, + "type": "object" + } + } + }, + "description": "" + } + }, + "summary": "List all clients for a set of user ids" + } + }, + "/users/list-prekeys": { + "post": { + "description": " [internal route ID: \"get-multi-user-prekey-bundle-qualified\"]\n\nYou can't request information for more users than maximum conversation size.Calls federation service brig on claim-multi-prekey-bundle", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/QualifiedUserClients" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/QualifiedUserClientPrekeyMapV4" + } + } + }, + "description": "" + } + }, + "summary": "(deprecated) Given a map of user IDs to client IDs return a prekey for each one." + } + }, + "/users/{uid_domain}/{uid}": { + "get": { + "description": " [internal route ID: \"get-user-qualified\"]\n\nCalls federation service brig on get-users-by-ids", + "parameters": [ + { + "in": "path", + "name": "uid_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "User Id", + "in": "path", + "name": "uid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserProfile" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/UserProfile" + } + } + }, + "description": "User found" + }, + "404": { + "content": { + "application/json": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "User not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + }, + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 404, + "label": "not-found", + "message": "User not found" + }, + "properties": { + "code": { + "enum": [ + 404 + ], + "type": "integer" + }, + "label": { + "enum": [ + "not-found" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "`uid_domain` or `uid` or User not found (label: `not-found`)" + } + }, + "summary": "Get a user by Domain and UserId" + } + }, + "/users/{uid_domain}/{uid}/clients": { + "get": { + "description": " [internal route ID: \"get-user-clients-qualified\"]\n\nCalls federation service brig on get-user-clients", + "parameters": [ + { + "in": "path", + "name": "uid_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "User Id", + "in": "path", + "name": "uid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "items": { + "$ref": "#/components/schemas/PubClient" + }, + "type": "array" + } + } + }, + "description": "" + } + }, + "summary": "Get all of a user's clients" + } + }, + "/users/{uid_domain}/{uid}/clients/{client}": { + "get": { + "description": " [internal route ID: \"get-user-client-qualified\"]\n\nCalls federation service brig on get-user-clients", + "parameters": [ + { + "in": "path", + "name": "uid_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "User Id", + "in": "path", + "name": "uid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "description": "ClientId", + "in": "path", + "name": "client", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/PubClient" + } + } + }, + "description": "" + } + }, + "summary": "Get a specific client of a user" + } + }, + "/users/{uid_domain}/{uid}/prekeys": { + "get": { + "description": " [internal route ID: \"get-users-prekey-bundle-qualified\"]\n\nCalls federation service brig on claim-prekey-bundle", + "parameters": [ + { + "in": "path", + "name": "uid_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "User Id", + "in": "path", + "name": "uid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/PrekeyBundle" + } + } + }, + "description": "" + } + }, + "summary": "Get a prekey for each client of a user." + } + }, + "/users/{uid_domain}/{uid}/prekeys/{client}": { + "get": { + "description": " [internal route ID: \"get-users-prekeys-client-qualified\"]\n\nCalls federation service brig on claim-prekey", + "parameters": [ + { + "in": "path", + "name": "uid_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "User Id", + "in": "path", + "name": "uid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "description": "ClientId", + "in": "path", + "name": "client", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/ClientPrekey" + } + } + }, + "description": "" + } + }, + "summary": "Get a prekey for a specific client of a user." + } + }, + "/users/{uid_domain}/{uid}/supported-protocols": { + "get": { + "description": " [internal route ID: \"get-supported-protocols\"]\n\n", + "parameters": [ + { + "in": "path", + "name": "uid_domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "User Id", + "in": "path", + "name": "uid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/BaseProtocol" + }, + "type": "array", + "uniqueItems": true + } + }, + "application/json;charset=utf-8": { + "schema": { + "items": { + "$ref": "#/components/schemas/BaseProtocol" + }, + "type": "array", + "uniqueItems": true + } + } + }, + "description": "Protocols supported by the user" + } + }, + "summary": "Get a user's supported protocols" + } + }, + "/users/{uid}/email": { + "put": { + "description": " [internal route ID: \"update-user-email\"]\n\nIf the user has a pending email validation, the validation email will be resent.", + "parameters": [ + { + "description": "User Id", + "in": "path", + "name": "uid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/EmailUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": [], + "items": {}, + "maxItems": 0, + "type": "array" + } + } + }, + "description": "" + } + }, + "summary": "Resend email address validation email." + } + }, + "/users/{uid}/rich-info": { + "get": { + "description": " [internal route ID: \"get-rich-info\"]\n\n", + "parameters": [ + { + "description": "User Id", + "in": "path", + "name": "uid", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RichInfoAssocList" + } + }, + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/RichInfoAssocList" + } + } + }, + "description": "Rich info about the user" + }, + "403": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "example": { + "code": 403, + "label": "insufficient-permissions", + "message": "Insufficient team permissions" + }, + "properties": { + "code": { + "enum": [ + 403 + ], + "type": "integer" + }, + "label": { + "enum": [ + "insufficient-permissions" + ], + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "label", + "message" + ], + "type": "object" + } + } + }, + "description": "Insufficient team permissions (label: `insufficient-permissions`)" + } + }, + "summary": "Get a user's rich info" + } + }, + "/verification-code/send": { + "post": { + "description": " [internal route ID: \"send-verification-code\"]\n\n", + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/SendVerificationCode" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Verification code sent." + } + }, + "summary": "Send a verification code to a given email address." + } + } + }, + "security": [ + { + "ZAuth": [] + } + ], + "servers": [ + { + "url": "/v6" + } + ] +} diff --git a/services/galley/galley.integration.yaml b/services/galley/galley.integration.yaml index 8dbbac73ff6..e7094e4238d 100644 --- a/services/galley/galley.integration.yaml +++ b/services/galley/galley.integration.yaml @@ -49,10 +49,10 @@ settings: federationDomain: example.com mlsPrivateKeyPaths: removal: - ed25519: test/resources/ed25519.pem - ecdsa_secp256r1_sha256: test/resources/ecdsa_secp256r1_sha256.pem - ecdsa_secp384r1_sha384: test/resources/ecdsa_secp384r1_sha384.pem - ecdsa_secp521r1_sha512: test/resources/ecdsa_secp521r1_sha512.pem + ed25519: test/resources/backendA/ed25519.pem + ecdsa_secp256r1_sha256: test/resources/backendA/ecdsa_secp256r1_sha256.pem + ecdsa_secp384r1_sha384: test/resources/backendA/ecdsa_secp384r1_sha384.pem + ecdsa_secp521r1_sha512: test/resources/backendA/ecdsa_secp521r1_sha512.pem guestLinkTTLSeconds: 604800 # We explicitly do not disable any API version. Please make sure the configuration value is the same in all these configs: # brig, cannon, cargohold, galley, gundeck, proxy, spar. diff --git a/services/galley/src/Galley/API/Error.hs b/services/galley/src/Galley/API/Error.hs index 6a1c4d7c97c..60f87a8ca52 100644 --- a/services/galley/src/Galley/API/Error.hs +++ b/services/galley/src/Galley/API/Error.hs @@ -65,12 +65,14 @@ data InvalidInput | InvalidRange LText | InvalidUUID4 | InvalidPayload LText + | FederationFunctionNotSupported LText instance APIError InvalidInput where toResponse CustomRolesNotSupported = toResponse $ badRequest "Custom roles not supported" toResponse (InvalidRange t) = toResponse $ invalidRange t toResponse InvalidUUID4 = toResponse invalidUUID4 toResponse (InvalidPayload t) = toResponse $ invalidPayload t + toResponse (FederationFunctionNotSupported t) = toResponse $ federationFunctionNotSupported t ---------------------------------------------------------------------------- -- Other errors @@ -87,6 +89,9 @@ invalidPayload = Wai.mkError status400 "invalid-payload" badRequest :: LText -> Wai.Error badRequest = Wai.mkError status400 "bad-request" +federationFunctionNotSupported :: LText -> Wai.Error +federationFunctionNotSupported = Wai.mkError status400 "federation-function-not-supported" + invalidUUID4 :: Wai.Error invalidUUID4 = Wai.mkError status400 "client-error" "Invalid UUID v4 format" diff --git a/services/galley/src/Galley/API/Federation.hs b/services/galley/src/Galley/API/Federation.hs index caba6054e6e..0d24aaf6259 100644 --- a/services/galley/src/Galley/API/Federation.hs +++ b/services/galley/src/Galley/API/Federation.hs @@ -39,6 +39,7 @@ import Data.Text.Lazy qualified as LT import Data.Time.Clock import Galley.API.Action import Galley.API.Error +import Galley.API.MLS import Galley.API.MLS.Enabled import Galley.API.MLS.GroupInfo import Galley.API.MLS.Message @@ -90,6 +91,7 @@ import Wire.API.Federation.Error import Wire.API.Federation.Version import Wire.API.MLS.Credential import Wire.API.MLS.GroupInfo +import Wire.API.MLS.Keys import Wire.API.MLS.Serialisation import Wire.API.MLS.SubConversation import Wire.API.Message @@ -104,6 +106,7 @@ federationSitemap :: ServerT FederationAPI (Sem GalleyEffects) federationSitemap = Named @"on-conversation-created" onConversationCreated + :<|> Named @"get-conversations@v1" getConversationsV1 :<|> Named @"get-conversations" getConversations :<|> Named @"leave-conversation" (callsFed (exposeAnnotations leaveConversation)) :<|> Named @"send-message" (callsFed (exposeAnnotations sendMessage)) @@ -117,6 +120,7 @@ federationSitemap = :<|> Named @"get-sub-conversation" getSubConversationForRemoteUser :<|> Named @"delete-sub-conversation" (callsFed deleteSubConversationForRemoteUser) :<|> Named @"leave-sub-conversation" (callsFed leaveSubConversation) + :<|> Named @"get-one2one-conversation@v1" getOne2OneConversationV1 :<|> Named @"get-one2one-conversation" getOne2OneConversation :<|> Named @"on-client-removed" onClientRemoved :<|> Named @"on-message-sent" onMessageSent @@ -198,17 +202,27 @@ onConversationCreated domain rc = do pushConversationEvent Nothing event (qualifyAs loc [qUnqualified . Public.memId $ mem]) [] pure EmptyResponse -getConversations :: +getConversationsV1 :: ( Member ConversationStore r, Member (Input (Local ())) r ) => Domain -> GetConversationsRequest -> Sem r GetConversationsResponse +getConversationsV1 domain req = + getConversationsResponseFromV2 <$> getConversations domain req + +getConversations :: + ( Member ConversationStore r, + Member (Input (Local ())) r + ) => + Domain -> + GetConversationsRequest -> + Sem r GetConversationsResponseV2 getConversations domain (GetConversationsRequest uid cids) = do let ruid = toRemoteUnsafe domain uid loc <- qualifyLocal () - GetConversationsResponse + GetConversationsResponseV2 . mapMaybe (Mapping.conversationToRemote (tDomain loc) ruid) <$> E.getConversations cids @@ -734,34 +748,62 @@ deleteSubConversationForRemoteUser domain DeleteSubConversationFedRequest {..} = lconv <- qualifyLocal dscreqConv deleteLocalSubConversation qusr lconv dscreqSubConv dsc +getOne2OneConversationV1 :: + ( Member (Input (Local ())) r, + Member BrigAccess r, + Member (Error InvalidInput) r + ) => + Domain -> + GetOne2OneConversationRequest -> + Sem r GetOne2OneConversationResponse +getOne2OneConversationV1 domain (GetOne2OneConversationRequest self other) = + fmap (Imports.fromRight GetOne2OneConversationNotConnected) + . runError @(Tagged 'NotConnected ()) + $ do + lother <- qualifyLocal other + let rself = toRemoteUnsafe domain self + ensureConnectedToRemotes lother [rself] + foldQualified + lother + (const . throw $ FederationFunctionNotSupported "Getting 1:1 conversations is not supported over federation API < V2.") + (const (pure GetOne2OneConversationBackendMismatch)) + (one2OneConvId BaseProtocolMLSTag (tUntagged lother) (tUntagged rself)) + getOne2OneConversation :: ( Member ConversationStore r, Member (Input (Local ())) r, Member (Error InternalError) r, - Member BrigAccess r + Member BrigAccess r, + Member (Input Env) r ) => Domain -> GetOne2OneConversationRequest -> - Sem r GetOne2OneConversationResponse + Sem r GetOne2OneConversationResponseV2 getOne2OneConversation domain (GetOne2OneConversationRequest self other) = - fmap (Imports.fromRight GetOne2OneConversationNotConnected) + fmap (Imports.fromRight GetOne2OneConversationV2MLSNotEnabled) + . runError @(Tagged 'MLSNotEnabled ()) + . fmap (Imports.fromRight GetOne2OneConversationV2NotConnected) . runError @(Tagged 'NotConnected ()) $ do lother <- qualifyLocal other let rself = toRemoteUnsafe domain self - ensureConnectedToRemotes lother [rself] let getLocal lconv = do mconv <- E.getConversation (tUnqualified lconv) - fmap GetOne2OneConversationOk $ case mconv of + mlsPublicKeys <- mlsKeysToPublic <$$> getMLSPrivateKeys + conv <- case mconv of Nothing -> pure (localMLSOne2OneConversationAsRemote lconv) Just conv -> note (InternalErrorWithDescription "Unexpected member list in 1-1 conversation") (conversationToRemote (tDomain lother) rself conv) + pure . GetOne2OneConversationV2Ok $ RemoteMLSOne2OneConversation conv mlsPublicKeys + + ensureConnectedToRemotes lother [rself] + foldQualified lother getLocal - (const (pure GetOne2OneConversationBackendMismatch)) + (const (pure GetOne2OneConversationV2BackendMismatch)) (one2OneConvId BaseProtocolMLSTag (tUntagged lother) (tUntagged rself)) -------------------------------------------------------------------------------- diff --git a/services/galley/src/Galley/API/Internal.hs b/services/galley/src/Galley/API/Internal.hs index 849f4d29dcf..1485821431d 100644 --- a/services/galley/src/Galley/API/Internal.hs +++ b/services/galley/src/Galley/API/Internal.hs @@ -132,7 +132,7 @@ conversationAPI = <@> mkNamedAPI @"conversation-unblock-unqualified" Update.unblockConvUnqualified <@> mkNamedAPI @"conversation-unblock" Update.unblockConv <@> mkNamedAPI @"conversation-meta" Query.getConversationMeta - <@> mkNamedAPI @"conversation-mls-one-to-one" Query.getMLSOne2OneConversation + <@> mkNamedAPI @"conversation-mls-one-to-one" Query.getMLSOne2OneConversationInternal <@> mkNamedAPI @"conversation-mls-one-to-one-established" Query.isMLSOne2OneEstablished legalholdWhitelistedTeamsAPI :: API ILegalholdWhitelistedTeamsAPI GalleyEffects diff --git a/services/galley/src/Galley/API/MLS.hs b/services/galley/src/Galley/API/MLS.hs index 2b8c0b6fd67..2854b91d0ed 100644 --- a/services/galley/src/Galley/API/MLS.hs +++ b/services/galley/src/Galley/API/MLS.hs @@ -25,8 +25,6 @@ module Galley.API.MLS ) where -import Data.Id -import Data.Qualified import Galley.API.MLS.Enabled import Galley.API.MLS.Message import Galley.Env @@ -41,7 +39,5 @@ getMLSPublicKeys :: ( Member (Input Env) r, Member (ErrorS 'MLSNotEnabled) r ) => - Local UserId -> Sem r (MLSKeysByPurpose MLSPublicKeys) -getMLSPublicKeys _ = do - fmap mlsKeysToPublic <$> getMLSPrivateKeys +getMLSPublicKeys = mlsKeysToPublic <$$> getMLSPrivateKeys diff --git a/services/galley/src/Galley/API/MLS/One2One.hs b/services/galley/src/Galley/API/MLS/One2One.hs index 0815d6a28e6..7c04e4417d9 100644 --- a/services/galley/src/Galley/API/MLS/One2One.hs +++ b/services/galley/src/Galley/API/MLS/One2One.hs @@ -37,6 +37,7 @@ import Wire.API.Conversation.Protocol import Wire.API.Conversation.Role import Wire.API.Federation.API.Galley import Wire.API.MLS.Group.Serialisation +import Wire.API.MLS.Keys import Wire.API.MLS.SubConversation import Wire.API.User @@ -64,7 +65,7 @@ localMLSOne2OneConversation lself (tUntagged -> convId) = -- conversation to be returned to a remote backend. localMLSOne2OneConversationAsRemote :: Local ConvId -> - RemoteConversation + RemoteConversationV2 localMLSOne2OneConversationAsRemote lcnv = let members = RemoteConvMembers @@ -72,7 +73,7 @@ localMLSOne2OneConversationAsRemote lcnv = others = [] } (metadata, mlsData) = localMLSOne2OneConversationMetadata (tUntagged lcnv) - in RemoteConversation + in RemoteConversationV2 { id = tUnqualified lcnv, metadata = metadata, members = members, @@ -100,19 +101,24 @@ localMLSOne2OneConversationMetadata convId = remoteMLSOne2OneConversation :: Local UserId -> Remote UserId -> - RemoteConversation -> - Conversation + RemoteMLSOne2OneConversation -> + (MLSOne2OneConversation MLSPublicKey) remoteMLSOne2OneConversation lself rother rc = let members = ConvMembers { cmSelf = defMember (tUntagged lself), - cmOthers = rc.members.others + cmOthers = rc.conversation.members.others } - in Conversation - { cnvQualifiedId = tUntagged (qualifyAs rother rc.id), - cnvMetadata = rc.metadata, - cnvMembers = members, - cnvProtocol = rc.protocol + conv = + Conversation + { cnvQualifiedId = tUntagged (qualifyAs rother rc.conversation.id), + cnvMetadata = rc.conversation.metadata, + cnvMembers = members, + cnvProtocol = rc.conversation.protocol + } + in MLSOne2OneConversation + { conversation = conv, + publicKeys = rc.publicKeys } -- | Create a new record for an MLS 1-1 conversation in the database and add diff --git a/services/galley/src/Galley/API/Mapping.hs b/services/galley/src/Galley/API/Mapping.hs index ec6f0993e12..5a9d7615a6b 100644 --- a/services/galley/src/Galley/API/Mapping.hs +++ b/services/galley/src/Galley/API/Mapping.hs @@ -97,7 +97,7 @@ conversationViewMaybe luid remoteOthers localOthers conv = do remoteConversationView :: Local UserId -> MemberStatus -> - Remote RemoteConversation -> + Remote RemoteConversationV2 -> Conversation remoteConversationView uid status (tUntagged -> Qualified rconv rDomain) = let mems = rconv.members @@ -125,7 +125,7 @@ conversationToRemote :: Domain -> Remote UserId -> Data.Conversation -> - Maybe RemoteConversation + Maybe RemoteConversationV2 conversationToRemote localDomain ruid conv = do let (selfs, rothers) = partition ((== ruid) . rmId) (Data.convRemoteMembers conv) lothers = Data.convLocalMembers conv @@ -134,7 +134,7 @@ conversationToRemote localDomain ruid conv = do map (localMemberToOther localDomain) lothers <> map remoteMemberToOther rothers pure $ - RemoteConversation + RemoteConversationV2 { id = Data.convId conv, metadata = Data.convMetadata conv, members = diff --git a/services/galley/src/Galley/API/Public/Conversation.hs b/services/galley/src/Galley/API/Public/Conversation.hs index 487e6893c85..24e0b9c4963 100644 --- a/services/galley/src/Galley/API/Public/Conversation.hs +++ b/services/galley/src/Galley/API/Public/Conversation.hs @@ -62,8 +62,8 @@ conversationAPI = <@> mkNamedAPI @"get-subconversation-group-info" (callsFed getSubConversationGroupInfo) <@> mkNamedAPI @"create-one-to-one-conversation@v2" (callsFed createOne2OneConversation) <@> mkNamedAPI @"create-one-to-one-conversation" (callsFed createOne2OneConversation) - <@> mkNamedAPI @"get-one-to-one-mls-conversation@v5" getMLSOne2OneConversation - <@> mkNamedAPI @"get-one-to-one-mls-conversation" getMLSOne2OneConversation + <@> mkNamedAPI @"get-one-to-one-mls-conversation@v5" getMLSOne2OneConversationV5 + <@> mkNamedAPI @"get-one-to-one-mls-conversation@v6" getMLSOne2OneConversationV6 <@> mkNamedAPI @"add-members-to-conversation-unqualified" (callsFed addMembersUnqualified) <@> mkNamedAPI @"add-members-to-conversation-unqualified2" (callsFed addMembersUnqualifiedV2) <@> mkNamedAPI @"add-members-to-conversation" (callsFed addMembers) diff --git a/services/galley/src/Galley/API/Public/MLS.hs b/services/galley/src/Galley/API/Public/MLS.hs index fa05f9bf5d6..e068923b7fa 100644 --- a/services/galley/src/Galley/API/Public/MLS.hs +++ b/services/galley/src/Galley/API/Public/MLS.hs @@ -19,6 +19,7 @@ module Galley.API.Public.MLS where import Galley.API.MLS import Galley.App +import Imports import Wire.API.MakesFederatedCall import Wire.API.Routes.API import Wire.API.Routes.Public.Galley.MLS @@ -27,4 +28,4 @@ mlsAPI :: API MLSAPI GalleyEffects mlsAPI = mkNamedAPI @"mls-message" (callsFed (exposeAnnotations postMLSMessageFromLocalUser)) <@> mkNamedAPI @"mls-commit-bundle" (callsFed (exposeAnnotations postMLSCommitBundleFromLocalUser)) - <@> mkNamedAPI @"mls-public-keys" getMLSPublicKeys + <@> mkNamedAPI @"mls-public-keys" (const getMLSPublicKeys) diff --git a/services/galley/src/Galley/API/Query.hs b/services/galley/src/Galley/API/Query.hs index 9d1220e659f..09ab5001282 100644 --- a/services/galley/src/Galley/API/Query.hs +++ b/services/galley/src/Galley/API/Query.hs @@ -1,4 +1,5 @@ {-# LANGUAGE RecordWildCards #-} +{-# OPTIONS_GHC -Wno-ambiguous-fields #-} -- This file is part of the Wire Server implementation. -- @@ -37,7 +38,9 @@ module Galley.API.Query ensureConvAdmin, getMLSSelfConversation, getMLSSelfConversationWithError, - getMLSOne2OneConversation, + getMLSOne2OneConversationV5, + getMLSOne2OneConversationV6, + getMLSOne2OneConversationInternal, isMLSOne2OneEstablished, ) where @@ -45,6 +48,7 @@ where import Cassandra qualified as C import Control.Lens import Control.Monad.Extra +import Data.ByteString.Conversion import Data.ByteString.Lazy qualified as LBS import Data.Code import Data.CommaSeparatedList @@ -58,6 +62,7 @@ import Data.Range import Data.Set qualified as Set import Galley.API.Error import Galley.API.MLS +import Galley.API.MLS.Enabled import Galley.API.MLS.One2One import Galley.API.MLS.Types import Galley.API.Mapping @@ -84,6 +89,7 @@ import Network.Wai.Utilities hiding (Error) import Polysemy import Polysemy.Error import Polysemy.Input +import Polysemy.TinyLog (TinyLog) import Polysemy.TinyLog qualified as P import System.Logger.Class qualified as Logger import Wire.API.Conversation hiding (Member) @@ -96,8 +102,10 @@ import Wire.API.Error import Wire.API.Error.Galley import Wire.API.Federation.API import Wire.API.Federation.API.Galley -import Wire.API.Federation.Client (FederatorClient) +import Wire.API.Federation.Client (FederatorClient, getNegotiatedVersion) import Wire.API.Federation.Error +import Wire.API.Federation.Version qualified as Federation +import Wire.API.MLS.Keys import Wire.API.Provider.Bot qualified as Public import Wire.API.Routes.MultiTablePaging qualified as Public import Wire.API.Team.Feature as Public hiding (setStatus) @@ -248,7 +256,7 @@ getRemoteConversationsWithFailures :: getRemoteConversationsWithFailures lusr convs = do -- get self member statuses from the database statusMap <- E.getRemoteConversationStatus (tUnqualified lusr) convs - let remoteView :: Remote RemoteConversation -> Conversation + let remoteView :: Remote RemoteConversationV2 -> Conversation remoteView rconv = Mapping.remoteConversationView lusr @@ -264,8 +272,15 @@ getRemoteConversationsWithFailures lusr convs = do | otherwise = [failedGetConversationLocally (map tUntagged locallyNotFound)] -- request conversations from remote backends - let rpc :: GetConversationsRequest -> FederatorClient 'Galley GetConversationsResponse - rpc = fedClient @'Galley @"get-conversations" + let rpc :: GetConversationsRequest -> FederatorClient 'Galley GetConversationsResponseV2 + rpc req = do + mFedVersion <- getNegotiatedVersion + case mFedVersion of + Nothing -> error "impossible" + Just fedVersion -> + if fedVersion < Federation.V2 + then getConversationsResponseToV2 <$> fedClient @'Galley @"get-conversations@v1" req + else fedClient @'Galley @"get-conversations" req resp <- E.runFederatedConcurrentlyEither locallyFound $ \someConvs -> rpc $ GetConversationsRequest (tUnqualified lusr) (tUnqualified someConvs) @@ -274,9 +289,9 @@ getRemoteConversationsWithFailures lusr convs = do <$> traverse handleFailure resp where handleFailure :: - Member P.TinyLog r => - Either (Remote [ConvId], FederationError) (Remote GetConversationsResponse) -> - Sem r (Either FailedGetConversation [Remote RemoteConversation]) + (Member P.TinyLog r) => + Either (Remote [ConvId], FederationError) (Remote GetConversationsResponseV2) -> + Sem r (Either FailedGetConversation [Remote RemoteConversationV2]) handleFailure (Left (rcids, e)) = do P.warn $ Logger.msg ("Error occurred while fetching remote conversations" :: ByteString) @@ -732,7 +747,7 @@ getMLSSelfConversation lusr = do -- uses the same function to calculate the conversation ID and corresponding -- group ID, however we /do/ assume that the two backends agree on which of the -- two is responsible for hosting the conversation. -getMLSOne2OneConversation :: +getMLSOne2OneConversationV5 :: ( Member BrigAccess r, Member ConversationStore r, Member (Input Env) r, @@ -740,6 +755,7 @@ getMLSOne2OneConversation :: Member (Error InternalError) r, Member (ErrorS 'MLSNotEnabled) r, Member (ErrorS 'NotConnected) r, + Member (ErrorS 'MLSFederatedOne2OneNotSupported) r, Member FederatorAccess r, Member TeamStore r, Member P.TinyLog r @@ -747,7 +763,45 @@ getMLSOne2OneConversation :: Local UserId -> Qualified UserId -> Sem r Conversation -getMLSOne2OneConversation lself qother = do +getMLSOne2OneConversationV5 lself qother = do + if isLocal lself qother + then getMLSOne2OneConversationInternal lself qother + else throwS @MLSFederatedOne2OneNotSupported + +getMLSOne2OneConversationInternal :: + ( Member BrigAccess r, + Member ConversationStore r, + Member (Input Env) r, + Member (Error FederationError) r, + Member (Error InternalError) r, + Member (ErrorS 'MLSNotEnabled) r, + Member (ErrorS 'NotConnected) r, + Member FederatorAccess r, + Member TeamStore r, + Member P.TinyLog r + ) => + Local UserId -> + Qualified UserId -> + Sem r Conversation +getMLSOne2OneConversationInternal lself qother = + (.conversation) <$> getMLSOne2OneConversationV6 lself qother + +getMLSOne2OneConversationV6 :: + ( Member BrigAccess r, + Member ConversationStore r, + Member (Input Env) r, + Member (Error FederationError) r, + Member (Error InternalError) r, + Member (ErrorS 'MLSNotEnabled) r, + Member (ErrorS 'NotConnected) r, + Member FederatorAccess r, + Member TeamStore r, + Member P.TinyLog r + ) => + Local UserId -> + Qualified UserId -> + Sem r (MLSOne2OneConversation MLSPublicKey) +getMLSOne2OneConversationV6 lself qother = do assertMLSEnabled ensureConnectedOrSameTeam lself [qother] let convId = one2OneConvId BaseProtocolMLSTag (tUntagged lself) qother @@ -760,27 +814,37 @@ getMLSOne2OneConversation lself qother = do getLocalMLSOne2OneConversation :: ( Member ConversationStore r, Member (Error InternalError) r, - Member P.TinyLog r + Member P.TinyLog r, + Member (Input Env) r, + Member (ErrorS MLSNotEnabled) r ) => Local UserId -> Local ConvId -> - Sem r Conversation + Sem r (MLSOne2OneConversation MLSPublicKey) getLocalMLSOne2OneConversation lself lconv = do mconv <- E.getConversation (tUnqualified lconv) - case mconv of + keys <- mlsKeysToPublic <$$> getMLSPrivateKeys + conv <- case mconv of Nothing -> pure (localMLSOne2OneConversation lself lconv) Just conv -> conversationView lself conv + pure $ + MLSOne2OneConversation + { conversation = conv, + publicKeys = keys + } getRemoteMLSOne2OneConversation :: ( Member (Error InternalError) r, Member (Error FederationError) r, Member (ErrorS 'NotConnected) r, - Member FederatorAccess r + Member FederatorAccess r, + Member (ErrorS MLSNotEnabled) r, + Member TinyLog r ) => Local UserId -> Qualified UserId -> Remote conv -> - Sem r Conversation + Sem r (MLSOne2OneConversation MLSPublicKey) getRemoteMLSOne2OneConversation lself qother rconv = do -- a conversation can only be remote if it is hosted on the other user's domain rother <- @@ -789,15 +853,32 @@ getRemoteMLSOne2OneConversation lself qother rconv = do else throw (InternalErrorWithDescription "Unexpected 1-1 conversation domain") resp <- - E.runFederated rconv $ - fedClient @'Galley @"get-one2one-conversation" $ - GetOne2OneConversationRequest (tUnqualified lself) (tUnqualified rother) + E.runFederated rconv $ do + negotiatedVersion <- getNegotiatedVersion + case negotiatedVersion of + Nothing -> error "impossible" + Just Federation.V0 -> pure . Left . FederationCallFailure $ FederatorClientVersionNegotiationError RemoteTooOld + Just Federation.V1 -> pure . Left . FederationCallFailure $ FederatorClientVersionNegotiationError RemoteTooOld + Just _ -> + fmap Right . fedClient @'Galley @"get-one2one-conversation" $ + GetOne2OneConversationRequest (tUnqualified lself) (tUnqualified rother) case resp of - GetOne2OneConversationOk rc -> + Right (GetOne2OneConversationV2Ok rc) -> pure (remoteMLSOne2OneConversation lself rother rc) - GetOne2OneConversationBackendMismatch -> + Right GetOne2OneConversationV2BackendMismatch -> throw (FederationUnexpectedBody "Backend mismatch when retrieving a remote 1-1 conversation") - GetOne2OneConversationNotConnected -> throwS @'NotConnected + Right GetOne2OneConversationV2NotConnected -> throwS @'NotConnected + Right GetOne2OneConversationV2MLSNotEnabled -> do + -- This is confusing to clients because we do not tell them which backend + -- doesn't have MLS enabled, which would nice information for fixing + -- problems in real world. We do the same thing when sending Welcome + -- messages, so for now, let's do the same thing. + P.warn $ + Logger.field "domain" (toByteString' (tDomain rother)) + . Logger.msg + ("Cannot get remote MLSOne2OneConversation because MLS is not enabled on remote" :: ByteString) + throwS @'MLSNotEnabled + Left e -> throw e -- | Check if an MLS 1-1 conversation has been established, namely if its epoch -- is non-zero. The conversation will only be stored in the database when its @@ -814,7 +895,8 @@ isMLSOne2OneEstablished :: Member (Error InternalError) r, Member (ErrorS 'MLSNotEnabled) r, Member (ErrorS 'NotConnected) r, - Member FederatorAccess r + Member FederatorAccess r, + Member TinyLog r ) => Local UserId -> Qualified UserId -> @@ -844,14 +926,16 @@ isRemoteMLSOne2OneEstablished :: ( Member (ErrorS 'NotConnected) r, Member (Error FederationError) r, Member (Error InternalError) r, - Member FederatorAccess r + Member FederatorAccess r, + Member (ErrorS MLSNotEnabled) r, + Member TinyLog r ) => Local UserId -> Qualified UserId -> Remote conv -> Sem r Bool isRemoteMLSOne2OneEstablished lself qother rconv = do - conv <- getRemoteMLSOne2OneConversation lself qother rconv + conv <- (.conversation) <$> getRemoteMLSOne2OneConversation lself qother rconv pure . (> 0) $ case cnvProtocol conv of ProtocolProteus -> 0 ProtocolMLS meta -> ep meta diff --git a/services/galley/test/integration/API.hs b/services/galley/test/integration/API.hs index 686ebfda2f9..02c26a86b5d 100644 --- a/services/galley/test/integration/API.hs +++ b/services/galley/test/integration/API.hs @@ -85,7 +85,6 @@ import Wire.API.Conversation import Wire.API.Conversation qualified as C import Wire.API.Conversation.Action import Wire.API.Conversation.Code hiding (Value) -import Wire.API.Conversation.Protocol import Wire.API.Conversation.Role import Wire.API.Conversation.Typing import Wire.API.Error.Galley @@ -176,7 +175,6 @@ tests s = test s "get conversations/:domain/:cnv - local" testGetQualifiedLocalConv, test s "get conversations/:domain/:cnv - local, not found" testGetQualifiedLocalConvNotFound, test s "get conversations/:domain/:cnv - local, not participating" testGetQualifiedLocalConvNotParticipating, - test s "get conversations/:domain/:cnv - remote" testGetQualifiedRemoteConv, test s "get conversations/:domain/:cnv - remote, not found" testGetQualifiedRemoteConvNotFound, test s "get conversations/:domain/:cnv - remote, not found on remote" testGetQualifiedRemoteConvNotFoundOnRemote, test s "post conversations/list/v2" testBulkGetQualifiedConvs, @@ -2312,42 +2310,6 @@ testGetQualifiedLocalConvNotParticipating = do const 403 === statusCode const (Just "access-denied") === view (at "label") . responseJsonUnsafe @Object -testGetQualifiedRemoteConv :: TestM () -testGetQualifiedRemoteConv = do - aliceQ <- randomQualifiedUser - let aliceId = qUnqualified aliceQ - loc <- flip toLocalUnsafe () <$> viewFederationDomain - bobId <- randomId - convId <- randomId - let remoteDomain = Domain "far-away.example.com" - bobQ = Qualified bobId remoteDomain - remoteConvId = Qualified convId remoteDomain - bobAsOtherMember = OtherMember bobQ Nothing roleNameWireAdmin - aliceAsLocal = - LocalMember aliceId defMemberStatus Nothing roleNameWireAdmin - aliceAsOtherMember = localMemberToOther (qDomain aliceQ) aliceAsLocal - aliceAsSelfMember = localMemberToSelf loc aliceAsLocal - - connectWithRemoteUser aliceId bobQ - registerRemoteConv remoteConvId bobId Nothing (Set.fromList [aliceAsOtherMember]) - - let mockConversation = mkProteusConv convId bobId roleNameWireAdmin [bobAsOtherMember] - remoteConversationResponse = GetConversationsResponse [mockConversation] - expected = - Conversation - remoteConvId - mockConversation.metadata - (ConvMembers aliceAsSelfMember mockConversation.members.others) - ProtocolProteus - - (respAll, _) <- - withTempMockFederator' - (mockReply remoteConversationResponse) - (getConvQualified aliceId remoteConvId) - - conv <- responseJsonUnsafe <$> (pure respAll getRequest asum - [ guard (d == remoteDomainA) *> mockReply (GetConversationsResponse [mockConversationA]), - guard (d == remoteDomainB) *> mockReply (GetConversationsResponse [mockConversationB]), + [ guard (d == remoteDomainA) *> mockReply (GetConversationsResponseV2 [mockConversationA]), + guard (d == remoteDomainB) *> mockReply (GetConversationsResponseV2 [mockConversationB]), guard (d == remoteDomainC) *> liftIO (throw (DiscoveryFailureSrvNotAvailable "domainC")), do r <- getRequest @@ -3135,7 +3097,7 @@ putRemoteConvMemberOk update = do (qUnqualified qbob) roleNameWireMember [localMemberToOther remoteDomain bobAsLocal] - remoteConversationResponse = GetConversationsResponse [mockConversation] + remoteConversationResponse = GetConversationsResponseV2 [mockConversation] (rs, _) <- withTempMockFederator' (mockReply remoteConversationResponse) @@ -3457,7 +3419,7 @@ testOne2OneConversationRequest shouldBeLocal actor desired = do pure . map omQualifiedId . cmOthers . cnvMembers $ conv RemoteActor -> do fedGalleyClient <- view tsFedGalleyClient - GetConversationsResponse convs <- + GetConversationsResponseV2 convs <- runFedClient @"get-conversations" fedGalleyClient (tDomain bob) $ GetConversationsRequest { userId = tUnqualified bob, @@ -3476,7 +3438,7 @@ testOne2OneConversationRequest shouldBeLocal actor desired = do found <- do let rconv = mkProteusConv (qUnqualified convId) (tUnqualified bob) roleNameWireAdmin [] (resp, _) <- - withTempMockFederator' (mockReply (GetConversationsResponse [rconv])) $ + withTempMockFederator' (mockReply (GetConversationsResponseV2 [rconv])) $ getConvQualified (tUnqualified alice) convId pure $ statusCode resp == 200 liftIO $ found @?= ((actor, desired) == (LocalActor, Included)) diff --git a/services/galley/test/integration/API/Federation.hs b/services/galley/test/integration/API/Federation.hs index e6bd8eea883..173a45d1fce 100644 --- a/services/galley/test/integration/API/Federation.hs +++ b/services/galley/test/integration/API/Federation.hs @@ -137,7 +137,7 @@ getConversationsAllFound = do fedGalleyClient <- view tsFedGalleyClient - GetConversationsResponse convs <- + GetConversationsResponseV2 convs <- runFedClient @"get-conversations" fedGalleyClient (qDomain aliceQ) $ GetConversationsRequest (qUnqualified aliceQ) @@ -183,7 +183,7 @@ getConversationsNotPartOf = do fedGalleyClient <- view tsFedGalleyClient rando <- Id <$> liftIO nextRandom - GetConversationsResponse convs <- + GetConversationsResponseV2 convs <- runFedClient @"get-conversations" fedGalleyClient localDomain $ GetConversationsRequest rando [qUnqualified . cnvQualifiedId $ cnv1] liftIO $ assertEqual "conversation list not empty" [] convs diff --git a/services/galley/test/integration/API/Util.hs b/services/galley/test/integration/API/Util.hs index 4de8b00e8df..d03d3d43079 100644 --- a/services/galley/test/integration/API/Util.hs +++ b/services/galley/test/integration/API/Util.hs @@ -2479,9 +2479,9 @@ mkProteusConv :: UserId -> RoleName -> [OtherMember] -> - RemoteConversation + RemoteConversationV2 mkProteusConv cnvId creator selfRole otherMembers = - RemoteConversation + RemoteConversationV2 cnvId ( ConversationMetadata RegularConv diff --git a/services/galley/test/resources/ecdsa_secp256r1_sha256.pem b/services/galley/test/resources/backendA/ecdsa_secp256r1_sha256.pem similarity index 100% rename from services/galley/test/resources/ecdsa_secp256r1_sha256.pem rename to services/galley/test/resources/backendA/ecdsa_secp256r1_sha256.pem diff --git a/services/galley/test/resources/ecdsa_secp384r1_sha384.pem b/services/galley/test/resources/backendA/ecdsa_secp384r1_sha384.pem similarity index 100% rename from services/galley/test/resources/ecdsa_secp384r1_sha384.pem rename to services/galley/test/resources/backendA/ecdsa_secp384r1_sha384.pem diff --git a/services/galley/test/resources/ecdsa_secp521r1_sha512.pem b/services/galley/test/resources/backendA/ecdsa_secp521r1_sha512.pem similarity index 100% rename from services/galley/test/resources/ecdsa_secp521r1_sha512.pem rename to services/galley/test/resources/backendA/ecdsa_secp521r1_sha512.pem diff --git a/services/galley/test/resources/ed25519.pem b/services/galley/test/resources/backendA/ed25519.pem similarity index 100% rename from services/galley/test/resources/ed25519.pem rename to services/galley/test/resources/backendA/ed25519.pem diff --git a/services/galley/test/resources/backendB/ecdsa_secp256r1_sha256.pem b/services/galley/test/resources/backendB/ecdsa_secp256r1_sha256.pem new file mode 100644 index 00000000000..260cdfe0e3a --- /dev/null +++ b/services/galley/test/resources/backendB/ecdsa_secp256r1_sha256.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgCUxypWAvn5V0pRz7 +DGYAhCmwZAkDW7Kid0CvsaLwutahRANCAARFmzfgJgQBGVCEJRB1WYGm2J0167aw +YRG7cSb74vHuaHaKio0c24n7o/daaBNOZMaBCi30lhL1YEzLI0wVJF51 +-----END PRIVATE KEY----- diff --git a/services/galley/test/resources/backendB/ecdsa_secp384r1_sha384.pem b/services/galley/test/resources/backendB/ecdsa_secp384r1_sha384.pem new file mode 100644 index 00000000000..8d4ede9322f --- /dev/null +++ b/services/galley/test/resources/backendB/ecdsa_secp384r1_sha384.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAfG49ge824AADZtVDW +JwWuJj/MoULnpNB1K3G4iRRvODb5E5yH7myhKSb3oeHnKaShZANiAARm+FFL69DT +Qk3tAVFQBP7ND7eu1Oq4VYyxcmynJj/NFIpCCgOs28AcKo6adqXOJizgeGf2/W4P +x+7Vi+Ir0TyIMpWBqo61G2jMDKMF23Yw/85tO1NdcL00As7kLF54nso= +-----END PRIVATE KEY----- diff --git a/services/galley/test/resources/backendB/ecdsa_secp521r1_sha512.pem b/services/galley/test/resources/backendB/ecdsa_secp521r1_sha512.pem new file mode 100644 index 00000000000..4dc9d6cb35e --- /dev/null +++ b/services/galley/test/resources/backendB/ecdsa_secp521r1_sha512.pem @@ -0,0 +1,8 @@ +-----BEGIN PRIVATE KEY----- +MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIACsFBjEdDV0Xb6AZM +g8fu/CLS+Tcd6MHQLEZ/G1abf7EIPTr8nCLvJqDEGVtgzqzqiW/ej8YOJLmIX7Xb +WLsPCr6hgYkDgYYABAG4C1/gxxaXR+8y/L3PGd45A3Rc54dpWVUvSWD7M50msW0c +Gs5gzLSMobNGUFeLfjzB0BbSdFALqawAsmBDi/LqrACkHUvBjUp4DfjkJwWdJWwz +/goHs+u1GB9MWVqsFmNUVgHjPDTbC4npMgVXWLELo3O9IzDAuvM2KbTjKo3djd/u +yw== +-----END PRIVATE KEY----- diff --git a/services/galley/test/resources/backendB/ed25519.pem b/services/galley/test/resources/backendB/ed25519.pem new file mode 100644 index 00000000000..14ca43a284b --- /dev/null +++ b/services/galley/test/resources/backendB/ed25519.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIO5vqw3LGpDiQ9AVsLuNp9BoWqXX+dat9PDYtcSgefcr +-----END PRIVATE KEY----- diff --git a/services/galley/test/resources/dynBackend1/ecdsa_secp256r1_sha256.pem b/services/galley/test/resources/dynBackend1/ecdsa_secp256r1_sha256.pem new file mode 100644 index 00000000000..4aa899a53c1 --- /dev/null +++ b/services/galley/test/resources/dynBackend1/ecdsa_secp256r1_sha256.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgX3aqY4POOuFTDjI6 +rNyuCUARS0karqX6omz+ZHLBpmChRANCAAQBr9wUSSDssSVQylUfrIRoN6uNxJHu +/IfMnvieXDhS42a/R59G0YnZ+43Z0OHiclfIMYvMT4UzwmKNqF8rKGTh +-----END PRIVATE KEY----- diff --git a/services/galley/test/resources/dynBackend1/ecdsa_secp384r1_sha384.pem b/services/galley/test/resources/dynBackend1/ecdsa_secp384r1_sha384.pem new file mode 100644 index 00000000000..d234e3c8372 --- /dev/null +++ b/services/galley/test/resources/dynBackend1/ecdsa_secp384r1_sha384.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBEDdTqi5zs5Zk/guXe +tbonA/9ZACWuoREM0sFPtIj3Sm0oOtRr7XkzHybAzKcTQwqhZANiAARyFcyRUpNM +b8XTSYk9AoVNxQjSFc5ENr99G/WJj6PPXinSo5ixazYbBGXt8N/jSr5U7JJHNQOp +/j0ZE2Ba/ARTU6bBySW5cuPn45o56c7aAv8743wc73Vvx57JOgCZxeY= +-----END PRIVATE KEY----- diff --git a/services/galley/test/resources/dynBackend1/ecdsa_secp521r1_sha512.pem b/services/galley/test/resources/dynBackend1/ecdsa_secp521r1_sha512.pem new file mode 100644 index 00000000000..eabcd72c8ba --- /dev/null +++ b/services/galley/test/resources/dynBackend1/ecdsa_secp521r1_sha512.pem @@ -0,0 +1,8 @@ +-----BEGIN PRIVATE KEY----- +MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIB+aX/6XEvPsiiRep9 +yk9IzN7PWTOGXVB0/8MlvPJZEN/xhmaQQhhs+nCZ5PNo+/03bTe6ge4k+oxnjzLO +m4ST2fyhgYkDgYYABAFpStisSMDJyecgrzj/xyAoCVo1rMq5PqhgpaQ8uiR2Auwn +dVLk4RdC7Zqxx1j6gKy0YihlUeHQt7gr4/+6Q3muBwCt2IghBqyZL0by9A1LKRvS +vUJxwv1Iu8Sl2uljRP62QuE9ETOiH8BnCA+GRmwOUFZnxH/NLvT/OQSClUFQydVK +mQ== +-----END PRIVATE KEY----- diff --git a/services/galley/test/resources/dynBackend1/ed25519.pem b/services/galley/test/resources/dynBackend1/ed25519.pem new file mode 100644 index 00000000000..5fa82d186bb --- /dev/null +++ b/services/galley/test/resources/dynBackend1/ed25519.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEICBjsYFFgSL4mLwbQKlP1eF/AHyb+1z4Vz6kDr1lMG3/ +-----END PRIVATE KEY----- diff --git a/services/galley/test/resources/dynBackend2/ecdsa_secp256r1_sha256.pem b/services/galley/test/resources/dynBackend2/ecdsa_secp256r1_sha256.pem new file mode 100644 index 00000000000..d71d16694f6 --- /dev/null +++ b/services/galley/test/resources/dynBackend2/ecdsa_secp256r1_sha256.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgEfaYkGCkcUbE/EhE +BMGZU9RKCyvgi/UESXvhQFgSje+hRANCAATnGRIEOs7xR64zD7yB2zbmwkgHL35o +scFaK0P+zSQ+Q6St4j+cb9+igjn8aFddVJiuWnMPsceLg9KueVXgNSWW +-----END PRIVATE KEY----- diff --git a/services/galley/test/resources/dynBackend2/ecdsa_secp384r1_sha384.pem b/services/galley/test/resources/dynBackend2/ecdsa_secp384r1_sha384.pem new file mode 100644 index 00000000000..271c34cab68 --- /dev/null +++ b/services/galley/test/resources/dynBackend2/ecdsa_secp384r1_sha384.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAo8PEN99hC+GQ1L5eO +eQWP9w2/qL/gVOwzb48HloaDWkZlhTlth7N8v0KMkoyh3EmhZANiAARF8+nQz+cX +/fGTX9wrz+kn+mZHV0+Kh2j3n8bNcPp94L3MR/8t1qqQvoAvGFLUZULF7H7cU5st +HBiHxbVbz76Mqol9ZPbwJzCmcI+WAf1qPIiOjOWkceZM/YznUP/RM6Q= +-----END PRIVATE KEY----- diff --git a/services/galley/test/resources/dynBackend2/ecdsa_secp521r1_sha512.pem b/services/galley/test/resources/dynBackend2/ecdsa_secp521r1_sha512.pem new file mode 100644 index 00000000000..95a40e376e3 --- /dev/null +++ b/services/galley/test/resources/dynBackend2/ecdsa_secp521r1_sha512.pem @@ -0,0 +1,8 @@ +-----BEGIN PRIVATE KEY----- +MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAbXN6cs+k71Pd3+gJ +hYTa5yMmmcitnp5myCOoKp3bvULH6naFQkg4PrMH3cnpgPcVaj6ZK+6m0qnlLg+x +PXQ9pQChgYkDgYYABAA6GUqOxi4NHNTSC+tphkagMqygFkrEzLHudYmwk2OwEDhG +cuf+ICuS+FmepZuMAI5QGqMNHLXttH0JXSHoob9eZAEWy8b0xOdj1GZHdNdk4aXL +tvwuzsZpeBKpMNwnTwggcJsepbBzXPANSyCnDyoCmKaHAQwtca2KFlwBB3uMRHve +2Q== +-----END PRIVATE KEY----- diff --git a/services/galley/test/resources/dynBackend2/ed25519.pem b/services/galley/test/resources/dynBackend2/ed25519.pem new file mode 100644 index 00000000000..c73ba496405 --- /dev/null +++ b/services/galley/test/resources/dynBackend2/ed25519.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIBhKAk3pknMLukvifvnT6ujsMvlLKTa/1IjXS2lD5XTz +-----END PRIVATE KEY----- diff --git a/services/galley/test/resources/dynBackend3/ecdsa_secp256r1_sha256.pem b/services/galley/test/resources/dynBackend3/ecdsa_secp256r1_sha256.pem new file mode 100644 index 00000000000..b3dbe2f828d --- /dev/null +++ b/services/galley/test/resources/dynBackend3/ecdsa_secp256r1_sha256.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgNhdHUB18rQWrJU4r +epN5CxOREiAYzKsGNuZ6//q5gbuhRANCAAQUvVXIJuKB81w1HBwgriR3UJ/Dy0se +u88O3pO+X28Uon/LczW9K2hzU5HwR+JF5NiKJTi3QQBzeaQTc1ybljf4 +-----END PRIVATE KEY----- diff --git a/services/galley/test/resources/dynBackend3/ecdsa_secp384r1_sha384.pem b/services/galley/test/resources/dynBackend3/ecdsa_secp384r1_sha384.pem new file mode 100644 index 00000000000..9606a469ff5 --- /dev/null +++ b/services/galley/test/resources/dynBackend3/ecdsa_secp384r1_sha384.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDClNF0H/IIY4Z8WPOs1 +G4JonwQYqNuYnWgRGiDWvhXyXTI+uuhoN0PM9juzYuaJsoKhZANiAARB1q6sJd82 +MMI/4vJQbJBSnzOQWKNr8K+tsNOAGKuFGBBX1kOGsPiUJyEq64nnT97MJRfc/MW2 +A9gjrbMlM4fNoF3V6bqykfMDRBmOQ8oWovNFiH9Csbz46sjAsQKSw9g= +-----END PRIVATE KEY----- diff --git a/services/galley/test/resources/dynBackend3/ecdsa_secp521r1_sha512.pem b/services/galley/test/resources/dynBackend3/ecdsa_secp521r1_sha512.pem new file mode 100644 index 00000000000..00229707a10 --- /dev/null +++ b/services/galley/test/resources/dynBackend3/ecdsa_secp521r1_sha512.pem @@ -0,0 +1,8 @@ +-----BEGIN PRIVATE KEY----- +MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAewieMnAzbnPuliiT +3Qj7v4zoxV308YQ2hDa17AWFEEg4ujeTHfRGZLjyMyh+fGnDVegBzJfdHfuExCcj +2RtSvp2hgYkDgYYABADr9KSC4esqdqkAoQcDvZa9mbVgow0+P/BS8Cj2Q5dnh0cj +2+p+F7cIEnvJJ7AY5heizlAyTxSv5U4Zx4Iein6A8QBqD7B0I0bkY2/ucVS1th0Z +9QAanTIqwxQ3HgJccHctI/M0QPgimOLYSmHpDmFlMSgtjKrJ6nOWgY1D2ev8l4n7 +xQ== +-----END PRIVATE KEY----- diff --git a/services/galley/test/resources/dynBackend3/ed25519.pem b/services/galley/test/resources/dynBackend3/ed25519.pem new file mode 100644 index 00000000000..55dc2d42ff8 --- /dev/null +++ b/services/galley/test/resources/dynBackend3/ed25519.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIF7bh0Ix5rSrpGVYJCn/sZZJO46pPsF8yPO46wDcB6bF +-----END PRIVATE KEY----- diff --git a/services/galley/test/resources/foo.sh b/services/galley/test/resources/foo.sh new file mode 100755 index 00000000000..ba3c2463847 --- /dev/null +++ b/services/galley/test/resources/foo.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +openssl genpkey -algorithm ed25519 > ed25519.pem +openssl genpkey -algorithm ec -pkeyopt ec_paramgen_curve:P-256 > ecdsa_secp256r1_sha256.pem +openssl genpkey -algorithm ec -pkeyopt ec_paramgen_curve:P-384 > ecdsa_secp384r1_sha384.pem +openssl genpkey -algorithm ec -pkeyopt ec_paramgen_curve:P-521 > ecdsa_secp521r1_sha512.pem diff --git a/services/galley/test/unit/Test/Galley/Mapping.hs b/services/galley/test/unit/Test/Galley/Mapping.hs index c18bb63f903..b52bf67fd92 100644 --- a/services/galley/test/unit/Test/Galley/Mapping.hs +++ b/services/galley/test/unit/Test/Galley/Mapping.hs @@ -41,7 +41,7 @@ import Wire.API.Conversation.Protocol import Wire.API.Conversation.Role import Wire.API.Federation.API.Galley ( RemoteConvMembers (..), - RemoteConversation (..), + RemoteConversationV2 (..), ) import Wire.Sem.Logger qualified as P diff --git a/services/integration.yaml b/services/integration.yaml index 00d54a5efa3..31b8e4c947e 100644 --- a/services/integration.yaml +++ b/services/integration.yaml @@ -61,7 +61,7 @@ federatorExternal: host: 127.0.0.1 port: 8098 -# This domain is configured using coredns runing along with the rest of +# This domain is configured using coredns running along with the rest of # docker-ephemeral setup. There is only an SRV record for # _wire-server-federator._tcp.example.com originDomain: example.com @@ -118,22 +118,41 @@ backendTwo: originDomain: b.example.com - redis2: host: 127.0.0.1 port: 6379 connectionMode: master + enableTls: false + insecureSkipVerifyTls: false dynamicBackends: dynamic-backend-1: domain: d1.example.com federatorExternalPort: 10098 + mlsPrivateKeyPaths: + removal: + ed25519: "test/resources/dynBackend1/ed25519.pem" + ecdsa_secp256r1_sha256: "test/resources/dynBackend1/ecdsa_secp256r1_sha256.pem" + ecdsa_secp384r1_sha384: "test/resources/dynBackend1/ecdsa_secp384r1_sha384.pem" + ecdsa_secp521r1_sha512: "test/resources/dynBackend1/ecdsa_secp521r1_sha512.pem" dynamic-backend-2: domain: d2.example.com federatorExternalPort: 11098 + mlsPrivateKeyPaths: + removal: + ed25519: "test/resources/dynBackend2/ed25519.pem" + ecdsa_secp256r1_sha256: "test/resources/dynBackend2/ecdsa_secp256r1_sha256.pem" + ecdsa_secp384r1_sha384: "test/resources/dynBackend2/ecdsa_secp384r1_sha384.pem" + ecdsa_secp521r1_sha512: "test/resources/dynBackend2/ecdsa_secp521r1_sha512.pem" dynamic-backend-3: domain: d3.example.com federatorExternalPort: 12098 + mlsPrivateKeyPaths: + removal: + ed25519: "test/resources/dynBackend3/ed25519.pem" + ecdsa_secp256r1_sha256: "test/resources/dynBackend3/ecdsa_secp256r1_sha256.pem" + ecdsa_secp384r1_sha384: "test/resources/dynBackend3/ecdsa_secp384r1_sha384.pem" + ecdsa_secp521r1_sha512: "test/resources/dynBackend3/ecdsa_secp521r1_sha512.pem" rabbitmq: host: localhost @@ -181,3 +200,44 @@ federation-v0: stern: host: 127.0.0.1 port: 21091 + +federation-v1: + originDomain: federation-v1.example.com + brig: + host: 127.0.0.1 + port: 22082 + cannon: + host: 127.0.0.1 + port: 22083 + cargohold: + host: 127.0.0.1 + port: 22084 + federatorInternal: + host: 127.0.0.1 + port: 22097 + federatorExternal: + host: 127.0.0.1 + port: 22098 + galley: + host: 127.0.0.1 + port: 22085 + gundeck: + host: 127.0.0.1 + port: 22086 + nginz: + host: 127.0.0.1 + port: 22080 + spar: + host: 127.0.0.1 + port: 22088 + proxy: + host: 127.0.0.1 + port: 22087 + backgroundWorker: + host: 127.0.0.1 + port: 22089 + stern: + host: 127.0.0.1 + port: 22091 + +integrationTestHostName: "localhost" diff --git a/services/nginz/integration-test/conf/nginz/integration-ca-key.pem b/services/nginz/integration-test/conf/nginz/integration-ca-key.pem index c92a5f13598..1017ec966b5 100644 --- a/services/nginz/integration-test/conf/nginz/integration-ca-key.pem +++ b/services/nginz/integration-test/conf/nginz/integration-ca-key.pem @@ -1,27 +1,28 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAxxSgxLUyMcwQHhE3ziEbPgn+m9L8EkXscvsWA6Ma4n5owK2m -Y7aG3n2ZRYlW+3VMBi0StrPh+lmXZWzgyw+bxHkaNYFTpK70xUGTx3lHfyu6EpKB -O0El1SNExnrx8ONUbLyY/LwnjlC5dWOB9l4PNylUf4DUr7eAlFrMIP2v2/FWVz9b -ffm5fWry4fW7iRcLGQRKNYrv/B798dVxEj9xoh2OiW6p8/tuEND6NMkJiGstHTJY -tmhwmF74WAxo7EQHD6iNgpr5errAQwrZheSGYNlbj078lA9mpE7BcPwEdDYgnI4R -Lblp0vcXvI3uw4Ne4+EMCDxJhXX+SvacAtBPMQIDAQABAoIBAQCCfuwPZTLc34Wl -H+YzsRHZbdW+sONY2wruJ9Y7VhwWYYQq4OkTrZ7kkvH4WdlxhWbrGXqN1oYHg9iw -vFjx4m5ZsIRUlEyOw7xg3OaQt9f87V3QNMaPX9n7x12auRaEr480o7+o03EeYZ3f -6/VR2EAjCW2BEqLX9/JJzObHrWMsSPMaXIV60V8K7kou1Ol3gdERG/+vAKhqzjFv -xdGQ0J6UHuYsZ5GP+xc1VLmE4WFQBAxcGbm+KWIHPeR/cq40JSsv5iRY5wBYRERK -szqDtwYAObf6DK4qSe8KKHMCfXP1RjWm5cvSebwyIjIqCnFXXUXpE0UNBkiUTVx6 -9xFpaJSRAoGBANuF2ucn0QwPNlyWdkAy9ItVmtwO+Rq8nlFai8KOt/Dauw5IqNye -xy6I8oEHCVrl7rHU6XRXqo3rJHrskcSJhYbiV+dYwusUnkqB27X/qv+CYJ/MQRC6 -v++ceNu/ybAF4UXxbIkEKR5BYaUAjXIN8kGp6Y1hF/wkUmjHQMQiwJP1AoGBAOgp -MOukoVDUQpu8Izzt9ff++S2531LUL66BCmxPQR3vhdxnYLFqs216uTevDrGTgLRS -mXwddVHLKW+zJiGZ0QssnCHmPzzg+USLQzCqJLUKCOoT9s3sDq/TsJwVuZy6sPcr -qWJ/sC9Ge+ZB6CRDrvZGdMSFvRkGT0cpT/mW4gTNAoGAX8ZxsCJmCV3luNWIeBAD -M3tA2jvKIQkkBZh8m4DK7dFwhRXcXo0Dl+D19KdORJNG5d1fkXviFJL/0oW+P0JE -uImuEmheelP/j8BCTJBkWZ/XakCiLptbvD3HWRC+/QZDt9FSKiFfkyyxXlz0WUuO -Y2mvVRiEb2Slc8NjFW+YF7UCgYAG2Pgtaxyq8qfISiNL78TafLXCyIGywrlpTzsM -eMX16ROsrYvnj7sdFoqR/uLTEAOyzeDjDUdhkzl0pvcP9KZ6yuUMBuuEkyonAGiJ -7erJQDOFG/OinTzNqNPDtsxTuBnyIGKNmjCLJGwRHY6IS0rEzs0w4rTyIQKDmc9X -EEE4XQKBgQDWr+hrUDfMa0JeiiB+mFCebNZptDMe0WN5oh+l1FkNlhSauaIb19Fu -qNtrC/F7ZihJW7i4xzEeakaZLpTEMjhdDLD4aQ+RiqW8iR7qmLKDYKWd8g2sanL1 -Tspko3Sj2Oq00BAU5tlNvVvxIaGraQ+HfNRi7p1HEPm7CeLg4ucmTg== ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCZHfooV9E6ekRu +FHvZEBb0xxctgy/KwvLveeocte//bE8iHDJEzrWTHsPrmvmazc19cdVj+AU+UWUA +P8MuoqBKT+QVRcXvbHZlamGsA/Quflyk3pBBRd4CNDCBmeFbptoXVDh08A2rQIHY +qrAcLgsRzObXqyLc2UrK5GBZQISEpjPQrFBTkfta6qTDVkUA+c82dEpOR3sjNf/b +B75K6tuK6iqpINP7kTkxzMna5rkgKxubSqpHW9s6CcDrPLKJq2kcwdSTpuf4v96Q +k+Yga+YH/HixrW9hrl4Jp0yNdvUdmvi24n6kwt9YyDIP8jXxblODHVBvcBOddoIY +V2SPh6HPAgMBAAECggEAAKZ48SzrouTxX3T6JazaJsX7BOLMFCoJysHoyvXBhC3b +PtjXJHsAQxSxXlHKgkF3gEiN0HPgNH3iAO3vD/FO3vdB5Q6RIvgsnzekHI0+aegi +z+xwuRDpOsx/8ZJMEQ3qBTQCsrRw9RY8DEXao56qcLPpvbwTVNQeFjMsGZTwOyP7 +4IjnYTwt+YrH4UNZPlBMm6ph/1qukTYqdBK2dEKXwxzAvpo+JPQ6SgcKfU3uHjmN +PofzM39l3+ZVoG7WdHEcqN/0tyFz/q/pQMOveyjRFn6fa7HDBEVrOwTKETBlJyFN +nVgMCFqwL2bltu1NNs90w9+q+6VoNME9Wlk3CUuGSQKBgQDRC7jwSvtA0sWOKft6 +QdEK5s8HEP1RUuNBUOETY6jo6I3HBPLWk1W2CiUtDJxMsKdz5QCrHp//LmB9gULk +bZ4tfEXQbbBOYnlbo2cM6VTBf36znnlosHFgF+NqBiYin2cbkkOMvJTa01efvkq9 +02raQNLMP0z4GfZwnDUdNk/85QKBgQC7glD2cJ7QP7VouVYIrmk42LLBfQxOFsbx +NptMf+UID1ayG3Qf3tiRVGtaQ/DRd3uPG+Wuumy5bxwOSCF1P7UmLY+1Rpz2DBda +JGrcHfuhBxI8WineMUwqnDWMtEPwDi9/9WlAoyXbWw4MzwiHCXgPfV7iBXgrAC1/ +ULB81rVsowKBgQCLSUdBfIRqzcVqExkHfeEeZWmeKLjQvezD8XL2q1m5TnJhIC/5 +vxPGBn58xMFD7BS3COfoHLC4o5sRJNaAQ3W4kuwlk2B86eo4n+ii1rltcFjor3fv +xFjWkTQqycwRF6ro2Qz/Mgvwvg7NVkqQrtSsdbK++pJ7YTkuETbmrvCe7QKBgQCf +SXfvsgInlEdOPEtKuqbmRKet2MWwPIcp+CJ7HRZ5/1W9nbbLMCq3YoiDuL2Fo8OR +8bfu861S5YFm3H2XtdP0J7Yx31eNaP4ZdGBWtx3AUFp8bHeuqiAy/lo7OhOQhOxy +/g44e5+4NSS9Ws66sB+OwQjuZokLtm3v/qK+mkKqkwKBgH9ostvH6Iqy9BRZ0hG7 +/EhQAGB2zekJOaOnMqYElzL1PY53SyVv/7jre3OwhUQLE1O3JQGTaj48/5EPeBUC +0jFg5AUM7nD53pbCtGlaKhbBB27lCN/tGKK5OVx4nRw0uRPGiJJ3ZXQps+lE6ZTa +AtzSX1cRPfA/Ff/lB3RR9I5Y +-----END PRIVATE KEY----- diff --git a/services/nginz/integration-test/conf/nginz/integration-ca.pem b/services/nginz/integration-test/conf/nginz/integration-ca.pem index 2315c7c7404..6a33fa9e2c3 100644 --- a/services/nginz/integration-test/conf/nginz/integration-ca.pem +++ b/services/nginz/integration-test/conf/nginz/integration-ca.pem @@ -1,19 +1,19 @@ -----BEGIN CERTIFICATE----- -MIIDAjCCAeqgAwIBAgIULBRPt7tLLvsw7kciIdjbXB8tddQwDQYJKoZIhvcNAQEL -BQAwGTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5jb20wHhcNMjMxMTIxMTM1ODAwWhcN -MjgxMTE5MTM1ODAwWjAZMRcwFQYDVQQDEw5jYS5leGFtcGxlLmNvbTCCASIwDQYJ -KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMcUoMS1MjHMEB4RN84hGz4J/pvS/BJF -7HL7FgOjGuJ+aMCtpmO2ht59mUWJVvt1TAYtEraz4fpZl2Vs4MsPm8R5GjWBU6Su -9MVBk8d5R38ruhKSgTtBJdUjRMZ68fDjVGy8mPy8J45QuXVjgfZeDzcpVH+A1K+3 -gJRazCD9r9vxVlc/W335uX1q8uH1u4kXCxkESjWK7/we/fHVcRI/caIdjoluqfP7 -bhDQ+jTJCYhrLR0yWLZocJhe+FgMaOxEBw+ojYKa+Xq6wEMK2YXkhmDZW49O/JQP -ZqROwXD8BHQ2IJyOES25adL3F7yN7sODXuPhDAg8SYV1/kr2nALQTzECAwEAAaNC -MEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJO6 -JJpzdazNjXtum3zX8UYWaQIJMA0GCSqGSIb3DQEBCwUAA4IBAQCoV7sw9CgICo9O -JacaB+P0Uk0dnISjsrKpcAKnuVdh1rN94+beXyttSBgQtDgVBehlESN+/B9fefLb -lhVxgCYq8inx4wZs22h8ZkjpJiOmBDjvHwgkCQOoh/Kog9gkmDr4qbFahU5GpaTp -x1rlNF3qaNRvZSVoxIVwYYiexKS5/KYMedII2EoBMHcFj0qKMhdDIT1Uw2PJZwiA -qjGDsSnLS+VeA8Zluc3m/os0ynjR6BEFQF1sn/OGO0eFaSMxXz0+Z4vT3J+c08Be -z2uZWQBgCiV/bL8F5xgokbHx+Vl0lz+1PEoFre8IJihmcnT8ZPWv/8eWPAr0gavH -+R0lNAyw +MIIDEzCCAfugAwIBAgIUEfjIXW9tD1WgwNHJ+kC3r6Cmv5swDQYJKoZIhvcNAQEL +BQAwGTEXMBUGA1UEAwwOY2EuZXhhbXBsZS5jb20wHhcNMjQwOTAzMTIwMzM3WhcN +MzQwOTAxMTIwMzM3WjAZMRcwFQYDVQQDDA5jYS5leGFtcGxlLmNvbTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAJkd+ihX0Tp6RG4Ue9kQFvTHFy2DL8rC +8u956hy17/9sTyIcMkTOtZMew+ua+ZrNzX1x1WP4BT5RZQA/wy6ioEpP5BVFxe9s +dmVqYawD9C5+XKTekEFF3gI0MIGZ4Vum2hdUOHTwDatAgdiqsBwuCxHM5terItzZ +SsrkYFlAhISmM9CsUFOR+1rqpMNWRQD5zzZ0Sk5HeyM1/9sHvkrq24rqKqkg0/uR +OTHMydrmuSArG5tKqkdb2zoJwOs8somraRzB1JOm5/i/3pCT5iBr5gf8eLGtb2Gu +XgmnTI129R2a+LbifqTC31jIMg/yNfFuU4MdUG9wE512ghhXZI+Hoc8CAwEAAaNT +MFEwHQYDVR0OBBYEFFqlhDsVqlH8UUKGOtCDE9xmYL3hMB8GA1UdIwQYMBaAFFql +hDsVqlH8UUKGOtCDE9xmYL3hMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBADQyh0k3xhcrNOqhvzAK1/A3TY5Hq1FE/a17Yiq8DIiLCJG/nAN60GBx +m7zuL8xoRJ4ylwIswa4z4rHj9p6M6tIbi2tTfJsbyB+FjyFRWoBmTngqNCiw7QUR +/ofSliuEu/YIjphR8LmTBvy4fVccTwXDaBPEGf2iN+DFmryLHxVpsVh3AA0uUSy0 +e2bZJLRwv1z0saC5KGHpWb6RJbAP2nRw5omcorMtP1KW8XyVESiJm7hDZAx6VLgD +k4GcEOUEq9CJs9UVAkIIDS87CfppHZEGPDK3Ufro5AhIwA3hpSJkTgzkf0TcQKIr +4E/zJSeTld4rMC26ghWodIwGRyofTP0= -----END CERTIFICATE----- diff --git a/services/nginz/integration-test/conf/nginz/integration-leaf-key.pem b/services/nginz/integration-test/conf/nginz/integration-leaf-key.pem index 8ed90523cd3..e4ee0a09ab1 100644 --- a/services/nginz/integration-test/conf/nginz/integration-leaf-key.pem +++ b/services/nginz/integration-test/conf/nginz/integration-leaf-key.pem @@ -1,27 +1,28 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEA1ueRV5jCjz+AWOmFzKkkjqPrCj1GGz8VDm5HLm4e7EO/LGXk -+RAYeKupGF9eqGBkiYfw9eZrjbf+uf5mpe7qKGrP67iCEzyjkbMMB8I89dcLwp7Y -uXYWfHw4NdFkSZoE0gmZ6Jh7EK+G2n+PZUaS9T43QoqMv0pFQ1roZpVMKkjnkW5J -4cU3JfXQQzdNCMiXlpGAIL0cKee6cwkPpGC1X3/6XQDyW7Q9nOjSw0mPmiZuK4iR -qwdy4edjKhcvJxuxHw215hVi0QVqbUcNzffS0mO+VIXz2IEbdzUwhSZJISsHQEOa -27UrBdRSg+Wb3FDQ+J8IeS6PR5JwjBcwt+DAjQIDAQABAoIBAHXZSS/TOqZZeWXI -sbH4824xX7weu+pHHqHqQaiphNWllRmgyv72H6VU5YbTDdKiAaAV50LB2CtAQjT2 -2I2YRdpiMKEgblxkPYKxwCAlGU7rXayddVXG9y/O4vhIWomuJ4SS9U7DB4Gv7/C0 -UQuFtyM7ugwIdISWEwOLv7Q5nSn2DYYXapNSmCUYv2FJEd57MJFtZ+CTHPu+ALxY -/qCGga8WBQ9Io/4A6UWN76m5IREeGh/pBwwhestpvUB9hXXe037Z11G3j/mNjqmz -SoUdEXnXpqJMA4c73hrryZR7TRPjRQx2P7YTyMwwOaJenhCS2F7ohJrwXNEtfbXt -Tb4mAQECgYEA8Qc4YqbF+xDmav1Mw7tpQ34EW7U1BF6RW+zpaRVVYXc+hZq8Rscl -yhzvYI2F4b9qOXw73Vdj3Hbd3f3BRC2ayMUk82pmbFEhZjQR9cGaLH1JfNXBdgz+ -wenmdczUAhmDiIseXTYdXL0FFgc9F/UFzmAYmD/kkMHTO2wnfeAci00CgYEA5EDv -UJzW/hWUtawWfg0Bw+H5RR2W/28dGG+680zazZwVHtDF7sEiThmR8AlLu74tWUMg -PBREdxOui5qRhmZO3y3JLJ8mjmEUQqC4x1NWReZCAcWGTNXn/PHsWPlK82qp/Q98 -lYJLShtbOOgo1hUPYeQ3hFnDi8HM3QssEeYB6kECgYA0kdSUf7dyuQ7oivKxRjEB -TXz5254Co/WkTRnjl4mVxoJWdZdXAJyXZpQ3RObMhAlRHG2aKzNWpH5jqrL6gc/e -tlEG3lAUk+Vq+zRnm6Baz8C1f5HAg7kU5kUjsFcVVidAIseuoNzqmzd+xHlovkJT -7tWub1EU2ZGOxloetEDFiQKBgQCfPrp4OGQ6cp4EvaIXoUV4/0Aku0cswL3A3brF -ofoJdvq5PBjLwQ0JBgfuOt4OhtkmrJFhuRYnKaEeHuGmrdwbEtuG+SYyMYKsFWu1 -DOxk6gdlKwTOuHIY5EPrs0laWDFur45Q1M1oT3uuUTKkYZ8QweMFwIaQC8687N17 -Q0hUwQKBgQDu55deAXAAS9FCqT4qidyxmvjdpkn8BKZhetss+t0m7Rum9OJCiMI5 -90exbnlRtUP4soNOccS3w3ie2HPspdlIsllYnd4/KaHQbdEoGtvrF5rM77X+81N1 -xPgNsMJM167VEWWJJCE+rkeWiF+irrjiHj7QlLmKkK4bmEzp5XuLyg== ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDPYxemHc21P8y2 +DIDNsuUzVpnc1bj81QIQ5a2aFSVtQFr3Dhpo0XXyu0KHKR64ZelmW33Yoe2J0zwl +R66fg07whBcZspsonXmsUB5uFFF/Dcv9shBNcg67jjynXPDvJK2sC37GCar5ar/v +11v/+Iq6LNerC/OqMJlMYyFDif1BvESbnw+9hsihUT9qK5s6md5krEV1/ro0Noh7 +kBgflylEKjapfCrVMnAMgaMV55jrk5BTpoR6KCDuDmXPk3Ed304Op/krGazfGB2j +1rz3IPfKOOtIgxn8n5PDYmShVUw1R3Rps1LeEyhsH1EGuAvVx1mKhB3QGMqFtIfo +wOpkgZPdAgMBAAECggEAVMurFUB1ZkkmXj9hgPnHLoUX10xJ3ZMIy7jlkS1ZRsD8 +EK0jDj2q0OtRSet9xJ7i3nfFTojzE5obqxCSrWUmp0ATI+4789DjuZluv8quAdm1 +0U73zHq43GZNlY7yco2YN1Lh7H5yepXz0dDILLLGolYIfscdw7YoUCvuI2vt8eyY +FaMkOi8Bs32Abn+53MhaNkEOVemmwP/u2rD81IY7pXQtF+1XfSxYBipHCB7phC1g +dr6ITU9CoF3SXvjYL6uP/7W1Du/CuaDSijVZJkBfQSgbkQbBr6pAovumtpNZgZvO +bixqS4oTZqsd+Rgl3YBOpx+JbuUFL0fG+uQBv1yZbwKBgQD8n7s1w/ouUGwW+qGi +4EFTYVDFD2Rgvg3oFPHt72zreaNXSy8YWrK9Mas/Fzu5knSE3f3O0S1WvqWdt/z6 +uPDLTpg9fIWX6v+hPX09F5ekeLlUDBazBT1PQUD0qd8PiyFNB1F+ffSPTxmKScjc +hTqQCtnun6rlICalWO9VvGt6QwKBgQDSKJkjxIGLtXBaXfMQiF9dQDrMk2it5grl +w0OnpPhYvpdp+Cfi01kMUrnfHwF0v92BqeqoKZo4DwJXkrmwf5kNnftxmRqbk9gE +dJq/E/6SELyT/chtzXfxC/wTmyyxhfZUJvJUxlaZ6KP/86t8A22DJcFO2Z85iUGH +8zy2UJUnXwKBgBvSs9m+FeXX8a+uNvMrY8Z9J1os0c9d30Y6WFLuVb6xjO3mV++E +vb7co5G1S1yq5q5jjLqkiyvMn4z5YKF0kQCzTU0oU8ZhmXn2vb5mxMrWiQLauf1J +jHEYLMFFnE2n8yj6r10RHkhSW+vBKKAxBDwtFceUSkwl+FupqeJ1eBjlAoGBAMou ++LWqdZ89HSwzOobrTCPgiTELmCfFKzLE2q/MTIjEQ9NVRLo57m+mnt+DatkxRR9b +oz/JVm8cMXqi1DZza4HoPWGalDic0bPnooC18bIAnAwcmdjZVcz3ZLpQDX10jfmD +xpu8fNBxOmYhvRcADTmg9wqu3zpxTDRI1F3pxLUtAoGAGfsX4bve5cLm49Oa1p0H +kEErLMuAMIKQNVsbzVELepLYr+uwEXBCXyyoIf79ABDvUHbzxMEwgANuet/4PQzS +yB1qzFk6GDvqZ5dfPUgMUWH9wvD1qEGp6yxkyESGt8CNwnu8GI50NAeSh2/JeUIa +r/u+m2vnJjOXpJdOJ+7f6yM= +-----END PRIVATE KEY----- diff --git a/services/nginz/integration-test/conf/nginz/integration-leaf.pem b/services/nginz/integration-test/conf/nginz/integration-leaf.pem index d8e7ee0955c..abd724df6b1 100644 --- a/services/nginz/integration-test/conf/nginz/integration-leaf.pem +++ b/services/nginz/integration-test/conf/nginz/integration-leaf.pem @@ -1,21 +1,20 @@ -----BEGIN CERTIFICATE----- -MIIDcjCCAlqgAwIBAgIUXlJ06fjgHbzEvIRscFvEwxpsioMwDQYJKoZIhvcNAQEL -BQAwGTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5jb20wHhcNMjMxMTIxMTM1ODAwWhcN -MjQxMTIwMTM1ODAwWjAAMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA -1ueRV5jCjz+AWOmFzKkkjqPrCj1GGz8VDm5HLm4e7EO/LGXk+RAYeKupGF9eqGBk -iYfw9eZrjbf+uf5mpe7qKGrP67iCEzyjkbMMB8I89dcLwp7YuXYWfHw4NdFkSZoE -0gmZ6Jh7EK+G2n+PZUaS9T43QoqMv0pFQ1roZpVMKkjnkW5J4cU3JfXQQzdNCMiX -lpGAIL0cKee6cwkPpGC1X3/6XQDyW7Q9nOjSw0mPmiZuK4iRqwdy4edjKhcvJxux -Hw215hVi0QVqbUcNzffS0mO+VIXz2IEbdzUwhSZJISsHQEOa27UrBdRSg+Wb3FDQ -+J8IeS6PR5JwjBcwt+DAjQIDAQABo4HKMIHHMA4GA1UdDwEB/wQEAwIFoDAdBgNV -HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4E -FgQUWm43ORCCQGlDu3JaPIm15lsr5swwHwYDVR0jBBgwFoAUk7okmnN1rM2Ne26b -fNfxRhZpAgkwSAYDVR0RAQH/BD4wPIIZKi5pbnRlZ3JhdGlvbi5leGFtcGxlLmNv -bYIUaG9zdC5kb2NrZXIuaW50ZXJuYWyCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsF -AAOCAQEAfrlC1maUJMg5n61YEpBwIS9O0LLhNidZ6dBEPwDiBwskzkTKoWksSR+n -7OytNFQvrdclejxIyvoOvBhLqNY4pFYdNRUu42GESUpCA6cQlW3a9QchTEuNASWR -AdrmGmjXYwPFGjnVUVPR+Abs9lG7/8eDYoq1B1AdBkW1EJ7+0/DrLOLDtloxYmBF -bydmLcesdPvgBLkHfBlOG54jH/ILXHAHxskWmGqixY6L1svhrcnwsindxRcfT4QB -fAtNDfAfiftUdb96QJfpwN1/N1oEHFl2D0ynE8sFOuVFm0gQ6mblH+Vahune6cSK -7SDUwM9Ia1OAO/r2cdEAvCrQqaeDZQ== +MIIDQTCCAimgAwIBAgIBADANBgkqhkiG9w0BAQsFADAZMRcwFQYDVQQDDA5jYS5l +eGFtcGxlLmNvbTAeFw0yNDA5MDMxMjAzMzhaFw0zNDA5MDExMjAzMzhaMAAwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPYxemHc21P8y2DIDNsuUzVpnc +1bj81QIQ5a2aFSVtQFr3Dhpo0XXyu0KHKR64ZelmW33Yoe2J0zwlR66fg07whBcZ +spsonXmsUB5uFFF/Dcv9shBNcg67jjynXPDvJK2sC37GCar5ar/v11v/+Iq6LNer +C/OqMJlMYyFDif1BvESbnw+9hsihUT9qK5s6md5krEV1/ro0Noh7kBgflylEKjap +fCrVMnAMgaMV55jrk5BTpoR6KCDuDmXPk3Ed304Op/krGazfGB2j1rz3IPfKOOtI +gxn8n5PDYmShVUw1R3Rps1LeEyhsH1EGuAvVx1mKhB3QGMqFtIfowOpkgZPdAgMB +AAGjgawwgakwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMEgGA1UdEQEB +/wQ+MDyCGSouaW50ZWdyYXRpb24uZXhhbXBsZS5jb22CFGhvc3QuZG9ja2VyLmlu +dGVybmFsgglsb2NhbGhvc3QwHQYDVR0OBBYEFBkpAu3ILiU4gtEYffAU7zxGHPC6 +MB8GA1UdIwQYMBaAFFqlhDsVqlH8UUKGOtCDE9xmYL3hMA0GCSqGSIb3DQEBCwUA +A4IBAQBB1VsthdoVT9ExXkfKixotbXm6+eBgYenK1R5Qx/UX3JrlI1nF/8rKMg5e +7QfMCydSJwVEQdvnXD3ddVhUTYRActQvnJwWTyXfeiezrfDCTLu4SNpLOP7ojFlq +9ZX/E9GC0axTIUmEIy8YIC3JJ2PAlvw9qMzrsivyAgbof3NX+9XXKfwZHBwSLsO1 +Gxr9zkL+U/qww7TvyJD1LqBR0UEd9pZriorpVVFAa/JlFQX5ip1Smcd6m97nq20N +qpUIalra+K6qHxjHVwA2UxVgbO9bLFIBmp9pNvSm+5umAKkmqFnHRNAHfCy/IFGl +3fw8u9mXJ8LzUR4tiS0cVb6bwQzd -----END CERTIFICATE-----