diff --git a/CHANGELOG.md b/CHANGELOG.md
index 15fc6bff7b0..72abe2bef8c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,58 @@
-->
+# [2021-06-08]
+
+## Release Notes
+
+This release doesn't require any extra considerations to deploy.
+
+## Release Notes for Wire.com Cloud operators
+
+Deploy brig before galley (#1526, #1549)
+
+## Features
+* Update versions of webapp, team-settings, account-pages (#1559)
+* Add missing /list-users route (#1572)
+* [Legalhold] Block device handshake in case of LH policy conflict (#1526)
+* [Legalhold] Fix: Connection type when unblocking after LH (#1549)
+* [Legalhold] Allow Legalhold for large teams (>2000) if enabled via whitelist (#1546)
+* [Legalhold] Add ClientCapabilities to NewClient. (#1552)
+* [Legalhold] Dynamic whitelisted teams & whitelist-teams-and-implicit-consent feature in tests (#1557, #1574)
+* [Federation] Add remote members to conversations (#1529)
+* [Federation] Federation: new endpoint: GET /conversations/{domain}/{cnv} (#1566)
+* [Federation] Parametric mock federator (#1558)
+* [Federation] Add more information to federation errors (#1560)
+* [Federation] Add remote users when creating a conversation (#1569)
+* [Federation] Update conversation membership in a remote backend (#1540)
+* [Federation] expose /conversations/{cnv}/members/v2 for federation backends (#1543)
+
+## Bug fixes and other updates
+* Fix MIME-type of asset artifacts
+* Add some missing charts (#1533)
+
+# Internal changes
+* Qualify users and conversations in Event (#1547)
+* Make botsAndUsers pure (#1562)
+* Set swagger type of text schema (#1561)
+* More examples in schema-profunctor documentation (#1539)
+* Refactoring-friendly FutureWork data type (#1550)
+* nginz/Dockerfile: Run 'apk add' verbosely for debugging (#1565)
+* Introduce a generalized version of wai-extra Session type constructor (#1563)
+* Avoid wrapping error in rethrow middleware (#1567)
+* wire-api: Introduce ErrorDescription (#1573)
+* [Federation] Use Servant.respond instead of explicit SOP (#1535)
+* [Federation] Add end2end test for adding remote users to a conversation (#1538)
+* [Federation] Add required fields to Swagger for SchemaP (#1536)
+* [Federation] Add Galley component to federator API (#1555)
+* [Federation] Generalises the mock federator to work with any MonadIO m monad (#1564)
+* [Federation] Introduces the HasGalley class (#1568)
+* [Federation] Servantify JSON endpoint to send messages (#1532)
+* [Federation] federator: rename Brig -> Service and add galley (#1570)
+
+## Documentation
+* Update Rich Info docs (#1544)
+
# [2021-05-26]
## Release Notes
diff --git a/Makefile b/Makefile
index 03f9616dc46..52583a0911a 100644
--- a/Makefile
+++ b/Makefile
@@ -14,7 +14,7 @@ CHARTS_INTEGRATION := wire-server databases-ephemeral fake-aws nginx-ingress-
# (e.g. move charts/brig to charts/wire-server/brig)
# this list could be generated from the folder names under ./charts/ like so:
# CHARTS_RELEASE := $(shell find charts/ -maxdepth 1 -type d | xargs -n 1 basename | grep -v charts)
-CHARTS_RELEASE := wire-server databases-ephemeral fake-aws aws-ingress backoffice calling-test demo-smtp elasticsearch-curator elasticsearch-external fluent-bit minio-external cassandra-external nginx-ingress-controller nginx-ingress-services reaper wire-server-metrics sftd
+CHARTS_RELEASE := wire-server redis-ephemeral databases-ephemeral fake-aws fake-aws-s3 fake-aws-sqs aws-ingress fluent-bit kibana backoffice calling-test demo-smtp elasticsearch-curator elasticsearch-external elasticsearch-ephemeral fluent-bit minio-external cassandra-external nginx-ingress-controller nginx-ingress-services reaper wire-server-metrics sftd
BUILDAH_PUSH ?= 0
KIND_CLUSTER_NAME := wire-server
BUILDAH_KIND_LOAD ?= 1
diff --git a/charts/account-pages/values.yaml b/charts/account-pages/values.yaml
index 16ce2745fe8..c042e3282bc 100644
--- a/charts/account-pages/values.yaml
+++ b/charts/account-pages/values.yaml
@@ -9,7 +9,7 @@ resources:
cpu: "1"
image:
repository: quay.io/wire/account
- tag: 2.1.4-5f9c54-v0.26.5-production
+ tag: 2.1.6-7ee369-v0.27.5-production
service:
https:
externalPort: 443
diff --git a/charts/brig/templates/configmap.yaml b/charts/brig/templates/configmap.yaml
index a28c47bf5a0..e01d29bd9a7 100644
--- a/charts/brig/templates/configmap.yaml
+++ b/charts/brig/templates/configmap.yaml
@@ -46,6 +46,7 @@ data:
host: gundeck
port: 8080
+ {{- if .enableFederator }}
# TODO remove this
federator:
host: federator
@@ -54,6 +55,7 @@ data:
federatorInternal:
host: federator
port: 8080
+ {{- end }}
{{- with .aws }}
aws:
diff --git a/charts/brig/values.yaml b/charts/brig/values.yaml
index 8423defc48d..e609dbd00e4 100644
--- a/charts/brig/values.yaml
+++ b/charts/brig/values.yaml
@@ -29,6 +29,7 @@ config:
# -- If set to false, 'dynamoDBEndpoint' _must_ be set.
randomPrekeys: true
useSES: true
+ enableFederator: false # keep enableFederator default in sync with galley chart's config.enableFederator as well as wire-server chart's tag.federator
emailSMS:
general:
templateBranding:
diff --git a/charts/federator/templates/configmap.yaml b/charts/federator/templates/configmap.yaml
index 2ac33f888ed..bf757849476 100644
--- a/charts/federator/templates/configmap.yaml
+++ b/charts/federator/templates/configmap.yaml
@@ -27,6 +27,10 @@ data:
host: brig
port: 8080
+ galley:
+ host: galley
+ port: 8080
+
{{- with .Values.config }}
logNetStrings: True # log using netstrings encoding:
diff --git a/charts/federator/templates/tests/configmap.yaml b/charts/federator/templates/tests/configmap.yaml
index 0858a03f78b..df817abac4b 100644
--- a/charts/federator/templates/tests/configmap.yaml
+++ b/charts/federator/templates/tests/configmap.yaml
@@ -13,4 +13,6 @@ data:
brig:
host: brig
port: 8080
-
+ galley:
+ host: galley
+ port: 8080
diff --git a/charts/galley/templates/configmap.yaml b/charts/galley/templates/configmap.yaml
index b357a0ac574..c648db2b986 100644
--- a/charts/galley/templates/configmap.yaml
+++ b/charts/galley/templates/configmap.yaml
@@ -32,6 +32,12 @@ data:
host: spar
port: 8080
+ {{- if .enableFederator }}
+ federator:
+ host: federator
+ port: 8080
+ {{- end }}
+
{{- if (.journal) }}
journal:
queueName: {{ .journal.queue }}
diff --git a/charts/galley/templates/tests/configmap.yaml b/charts/galley/templates/tests/configmap.yaml
index b55bcd44804..42a487f66fb 100644
--- a/charts/galley/templates/tests/configmap.yaml
+++ b/charts/galley/templates/tests/configmap.yaml
@@ -16,6 +16,10 @@ data:
host: cannon
port: 8080
+ federator:
+ host: federator
+ port: 8080
+
provider:
privateKey: /etc/wire/integration-secrets/provider-privatekey.pem
publicKey: /etc/wire/integration-secrets/provider-publickey.pem
diff --git a/charts/galley/values.yaml b/charts/galley/values.yaml
index 2d6d8e94e41..6a1689ef0d5 100644
--- a/charts/galley/values.yaml
+++ b/charts/galley/values.yaml
@@ -19,6 +19,7 @@ config:
cassandra:
host: aws-cassandra
replicaCount: 3
+ enableFederator: false # keep enableFederator default in sync with brig chart's config.enableFederator as well as wire-server chart's tag.federator
settings:
maxTeamSize: 500
maxConvSize: 500
diff --git a/charts/nginz/values.yaml b/charts/nginz/values.yaml
index d7d0fc999be..3f5645d1d32 100644
--- a/charts/nginz/values.yaml
+++ b/charts/nginz/values.yaml
@@ -80,6 +80,9 @@ nginx_conf:
envs:
- all
doc: true
+ - path: /list-users
+ envs:
+ - all
- path: ~* ^/api/swagger.json$
disable_zauth: true
envs:
@@ -210,6 +213,11 @@ nginx_conf:
- staging
disable_zauth: true
basic_auth: true
+ - path: ~* ^/i/legalhold/whitelisted-teams(.*)
+ envs:
+ - staging
+ disable_zauth: true
+ basic_auth: true
- path: /cookies
envs:
- all
diff --git a/charts/team-settings/values.yaml b/charts/team-settings/values.yaml
index fe74641e43f..2073ce51ad3 100644
--- a/charts/team-settings/values.yaml
+++ b/charts/team-settings/values.yaml
@@ -9,7 +9,7 @@ resources:
cpu: "1"
image:
repository: quay.io/wire/team-settings
- tag: 3.4.0-03c7a9-v0.28.2-production
+ tag: 3.5.1-e08322-v0.28.10-production
service:
https:
externalPort: 443
diff --git a/charts/webapp/values.yaml b/charts/webapp/values.yaml
index 913fda9a24c..2c06db8b496 100644
--- a/charts/webapp/values.yaml
+++ b/charts/webapp/values.yaml
@@ -9,7 +9,7 @@ resources:
cpu: "1"
image:
repository: quay.io/wire/webapp
- tag: 2021-04-01-production.0-254d51-v0.28.3-production
+ tag: 2021-05-10-production.0-2e9ab3-v0.28.10-production
service:
https:
externalPort: 443
diff --git a/charts/wire-server/values.yaml b/charts/wire-server/values.yaml
index 32f4b577251..de41554b588 100644
--- a/charts/wire-server/values.yaml
+++ b/charts/wire-server/values.yaml
@@ -9,5 +9,5 @@ tags:
team-settings: false
account-pages: false
legalhold: false
- federator: false
+ federator: false # see also galley.config.enableFederator and brig.config.enableFederator
sftd: false
diff --git a/deploy/services-demo/conf/nginz/nginx.conf b/deploy/services-demo/conf/nginz/nginx.conf
index d9582fd5217..5c9eb5227e2 100644
--- a/deploy/services-demo/conf/nginz/nginx.conf
+++ b/deploy/services-demo/conf/nginz/nginx.conf
@@ -226,6 +226,11 @@ http {
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;
diff --git a/docs/reference/user/rich-info.md b/docs/reference/user/rich-info.md
index 301c5b671af..e6ecb60c297 100644
--- a/docs/reference/user/rich-info.md
+++ b/docs/reference/user/rich-info.md
@@ -70,35 +70,76 @@ Connected users who are not members of user's team will not receive an event (no
## SCIM support {#RefRichInfoScim}
-Rich info can be pushed to Wire by setting the `"richInfo"` field belonging to the `"urn:wire:scim:schemas:profile:1.0"` extension. Both `PUT /scim/v2/Users/:id` and `POST /scim/v2/Users/:id` can contain rich info. Here is an example for `PUT`:
+Rich info can be pushed to Wire by setting JSON keys under the `"urn:ietf:params:scim:schemas:extension:wire:1.0:User"` extension. Both `PUT /scim/v2/Users/:id` , `PATCH /scim/v2/Users/:id` and `POST /scim/v2/Users/:id` can contain rich info. Here is an example for `PUT`:
```javascript
PUT /scim/v2/Users/:id
{
...,
- "urn:wire:scim:schemas:profile:1.0": {
- "richInfo": [
- {
- "type": "Department",
- "value": "Sales & Marketing"
- },
- {
- "type": "Favorite color",
- "value": "Blue"
- }
- ]
+ "urn:ietf:params:scim:schemas:extension:wire:1.0:User": {
+ "Department": "Sales & Marketing",
+ "FavoriteColor": "Blue"
}
}
```
+Here is an example for `PATCH`:
+
+```json
+PATCH /scim/v2/Users/:id
+
+{
+ "schemas": [
+ "urn:ietf:params:scim:api:messages:2.0:PatchOp"
+ ],
+ "operations": [
+ {
+ "op": "add",
+ "path": "urn:ietf:params:scim:schemas:extension:wire:1.0:User:Department",
+ "value": "Development "
+ },
+ {
+ "op": "replace",
+ "path": "urn:ietf:params:scim:schemas:extension:wire:1.0:User:Country",
+ "value": "Germany"
+ },
+ {
+ "op": "remove",
+ "path": "urn:ietf:params:scim:schemas:extension:wire:1.0:User:City"
+ }
+ ]
+}
+
+```
+
Rich info set via SCIM can be queried by doing a `GET /scim/v2/Users` or `GET /scim/v2/Users/:id` query.
-### SCIM provisioning agent support {#RefRichInfoScimAgents}
+### Set up SCIM RichInfo mapping in Azure {#RefRichInfoScimAgents}
+
+Go to your provisioning page
+
+![image](https://user-images.githubusercontent.com/628387/119977043-393b3000-bfb8-11eb-9e5b-18a955ca3181.png)
+
+Click "Edit attribute mappings"
+
+Then click "Mappings" And then click **Synchronize Azure Active Directory Users to _appname_**
+![image](https://user-images.githubusercontent.com/628387/119977488-c9797500-bfb8-11eb-81b8-46376f5fdadb.png)
+
+Click "Show Advanced options" and then **Edit attribute list for _appname_**
+![image](https://user-images.githubusercontent.com/628387/119977905-3f7ddc00-bfb9-11eb-90e2-28da82c6f13e.png)
+
+Add a new attribute name. The type should be `String` and the name should be prefixed with `urn:ietf:params:scim:schemas:extension:wire:1.0:User:`
+e.g. `urn:ietf:params:scim:schemas:extension:wire:1.0:User:Location`
+
+![image](https://user-images.githubusercontent.com/628387/119978050-70f6a780-bfb9-11eb-8919-93e32bf76d79.png)
+
+Hit **Save** and afterwards hit **Add New Mapping**
+
+Select the Azure AD Source attribute you want to map, and map it to the custom **Target Attribute** that you just added.
+![image](https://user-images.githubusercontent.com/628387/119978316-c5018c00-bfb9-11eb-9290-2076ac1a05df.png)
-* Okta: unable to push fields in the format we require (checked on 2019-02-21).
-* OneLogin: likely able to push fields.
## Limitations {#RefRichInfoLimitations}
diff --git a/hack/helm_vars/wire-server/values.yaml b/hack/helm_vars/wire-server/values.yaml
index 9232085f7c3..15540df0a98 100644
--- a/hack/helm_vars/wire-server/values.yaml
+++ b/hack/helm_vars/wire-server/values.yaml
@@ -6,7 +6,7 @@ tags:
cannon: true
cargohold: true
spar: true
- federator: true
+ federator: true # also see galley.config.enableFederator and brig.config.enableFederator
proxy: false
webapp: false
team-settings: false
@@ -53,6 +53,7 @@ brig:
sessionTokenTimeout: 20
accessTokenTimeout: 30
providerTokenTimeout: 60
+ enableFederator: true # keep in sync with galley.config.enableFederator and tags.federator!
optSettings:
setActivationTimeout: 5
# keep this in sync with brigSettingsTeamInvitationTimeout in spar/templates/tests/configmap.yaml
@@ -140,6 +141,7 @@ galley:
cassandra:
host: cassandra-ephemeral
replicaCount: 1
+ enableFederator: true # keep in sync with brig.config.enableFederator and tags.federator!
settings:
maxConvAndTeamSize: 16
maxTeamSize: 32
@@ -150,7 +152,7 @@ galley:
federationDomain: integration.example.com
featureFlags:
sso: disabled-by-default # this needs to be the default; tests can enable it when needed.
- legalhold: disabled-by-default
+ legalhold: whitelist-teams-and-implicit-consent
teamSearchVisibility: disabled-by-default
journal:
endpoint: http://fake-aws-sqs:4568
diff --git a/libs/api-bot/src/Network/Wire/Bot/Monad.hs b/libs/api-bot/src/Network/Wire/Bot/Monad.hs
index db2ff93f34e..d1a984452c5 100644
--- a/libs/api-bot/src/Network/Wire/Bot/Monad.hs
+++ b/libs/api-bot/src/Network/Wire/Bot/Monad.hs
@@ -387,7 +387,8 @@ addBotClient self cty label = do
newClientType = cty,
newClientClass = Nothing,
newClientCookie = Nothing,
- newClientModel = Nothing
+ newClientModel = Nothing,
+ newClientCapabilities = Nothing
}
cid <- clientId <$> runBotSession self (registerClient nc)
clt <- BotClient cid label box <$> liftIO Clients.empty
diff --git a/libs/api-client/src/Network/Wire/Client/API/Conversation.hs b/libs/api-client/src/Network/Wire/Client/API/Conversation.hs
index 8983b7f8c85..80345558b11 100644
--- a/libs/api-client/src/Network/Wire/Client/API/Conversation.hs
+++ b/libs/api-client/src/Network/Wire/Client/API/Conversation.hs
@@ -140,6 +140,6 @@ createConv users name = sessionRequest req rsc readBody
method POST
. path "conversations"
. acceptJson
- . json (NewConvUnmanaged (NewConv users name mempty Nothing Nothing Nothing Nothing roleNameWireAdmin))
+ . json (NewConvUnmanaged (NewConv users [] name mempty Nothing Nothing Nothing Nothing roleNameWireAdmin))
$ empty
rsc = status201 :| []
diff --git a/libs/api-client/src/Network/Wire/Client/API/Push.hs b/libs/api-client/src/Network/Wire/Client/API/Push.hs
index 03c9af39ae6..df6e6b18f35 100644
--- a/libs/api-client/src/Network/Wire/Client/API/Push.hs
+++ b/libs/api-client/src/Network/Wire/Client/API/Push.hs
@@ -36,6 +36,7 @@ module Network.Wire.Client.API.Push
OtrMessage (..),
SimpleMembers (..),
SimpleMember (..),
+ smId,
UserIdList (..),
UserInfo (..),
diff --git a/libs/bilge/bilge.cabal b/libs/bilge/bilge.cabal
index 2aea6c96374..ca21e9ed253 100644
--- a/libs/bilge/bilge.cabal
+++ b/libs/bilge/bilge.cabal
@@ -4,7 +4,7 @@ cabal-version: 1.12
--
-- see: https://github.com/sol/hpack
--
--- hash: d7b6994200506c693bb43f8b717b697cb25b91d7f649aea638af47d010c72c40
+-- hash: 8edb13a7bddfafe7d2906bff5e3671bd529be1c1726e113907c70a373cfc2606
name: bilge
version: 0.22.0
@@ -30,6 +30,7 @@ library
Bilge.Response
Bilge.Retry
Bilge.RPC
+ Bilge.TestSession
other-modules:
Paths_bilge
hs-source-dirs:
diff --git a/libs/bilge/src/Bilge/IO.hs b/libs/bilge/src/Bilge/IO.hs
index 34aa87291b1..b5d12a05cb0 100644
--- a/libs/bilge/src/Bilge/IO.hs
+++ b/libs/bilge/src/Bilge/IO.hs
@@ -2,7 +2,6 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
@@ -70,11 +69,11 @@ where
import Bilge.Request
import Bilge.Response
+import Bilge.TestSession
import Control.Monad.Base
import Control.Monad.Catch
import Control.Monad.Trans.Control
-import qualified Data.ByteString.Lazy as LB
-import qualified Data.ByteString.Lazy as Lazy
+import qualified Data.ByteString.Lazy as LBS
import Data.CaseInsensitive (CI)
import Imports hiding (head)
import Network.HTTP.Client as Client hiding (httpLbs, method)
@@ -82,7 +81,7 @@ import qualified Network.HTTP.Client as Client (method)
import qualified Network.HTTP.Client.Internal as Client (Response (..), ResponseClose (..))
import Network.HTTP.Types
import qualified Network.Wai as Wai
-import qualified Network.Wai.Test as Wai
+import qualified Network.Wai.Test as WaiTest
-- | Debug settings may cause debug information to be printed to stdout.
data Debug
@@ -135,39 +134,42 @@ trivialBodyReader bodyBytes = do
mkBodyReader bodyVar = do
atomically $ swapTVar bodyVar ""
-instance MonadHttp Wai.Session where
+instance MonadHttp WaiTest.Session where
+ handleRequestWithCont req cont = unSessionT $ handleRequestWithCont req cont
+
+instance MonadIO m => MonadHttp (SessionT m) where
handleRequestWithCont req cont = do
reqBody <- liftIO $ getHttpClientRequestBody (Client.requestBody req)
-- `srequest` sets the requestBody for us
- wResponse :: Wai.SResponse <- Wai.srequest (Wai.SRequest wRequest reqBody)
- bodyReader <- liftIO $ trivialBodyReader $ LB.toStrict $ Wai.simpleBody wResponse
+ wResponse :: WaiTest.SResponse <- liftSession $ WaiTest.srequest (WaiTest.SRequest wRequest reqBody)
+ bodyReader <- liftIO $ trivialBodyReader $ LBS.toStrict $ WaiTest.simpleBody wResponse
let bilgeResponse :: Response BodyReader
bilgeResponse = toBilgeResponse bodyReader wResponse
+
liftIO $ cont bilgeResponse
where
wRequest :: Wai.Request
wRequest =
- flip Wai.setPath (Client.path req <> Client.queryString req) $
+ flip WaiTest.setPath (Client.path req <> Client.queryString req) $
Wai.defaultRequest
{ Wai.requestMethod = Client.method req,
Wai.httpVersion = Client.requestVersion req,
Wai.requestHeaders = Client.requestHeaders req,
Wai.isSecure = Client.secure req,
- Wai.remoteHost = error "no remote host",
Wai.requestHeaderHost = lookupHeader "HOST" req,
Wai.requestHeaderRange = lookupHeader "RANGE" req,
Wai.requestHeaderReferer = lookupHeader "REFERER" req,
Wai.requestHeaderUserAgent = lookupHeader "USER-AGENT" req
}
- toBilgeResponse :: BodyReader -> Wai.SResponse -> Response BodyReader
- toBilgeResponse bodyReader Wai.SResponse {Wai.simpleStatus, Wai.simpleHeaders} =
+ toBilgeResponse :: BodyReader -> WaiTest.SResponse -> Response BodyReader
+ toBilgeResponse bodyReader WaiTest.SResponse {WaiTest.simpleStatus, WaiTest.simpleHeaders} =
Client.Response
{ responseStatus = simpleStatus,
-- I just picked an arbitrary version; shouldn't matter.
responseVersion = http11,
responseHeaders = simpleHeaders,
responseBody = bodyReader,
- responseCookieJar = mempty,
+ Client.responseCookieJar = mempty,
Client.responseClose' = Client.ResponseClose $ pure ()
}
lookupHeader :: CI ByteString -> Client.Request -> Maybe ByteString
@@ -178,7 +180,7 @@ instance MonadHttp Wai.Session where
getHttpClientRequestBody :: HasCallStack => Client.RequestBody -> IO LByteString
getHttpClientRequestBody = \case
Client.RequestBodyLBS lbs -> pure lbs
- Client.RequestBodyBS bs -> pure (Lazy.fromStrict bs)
+ Client.RequestBodyBS bs -> pure (LBS.fromStrict bs)
Client.RequestBodyBuilder _ _ -> notImplemented "RequestBodyBuilder"
Client.RequestBodyStream _ _ -> notImplemented "RequestBodyStream"
Client.RequestBodyStreamChunked _ -> notImplemented "RequestBodyStreamChunked"
@@ -224,7 +226,7 @@ get,
patch ::
(MonadIO m, MonadHttp m) =>
(Request -> Request) ->
- m (Response (Maybe Lazy.ByteString))
+ m (Response (Maybe LByteString))
get f = httpLbs empty (method GET . f)
post f = httpLbs empty (method POST . f)
put f = httpLbs empty (method PUT . f)
@@ -245,7 +247,7 @@ get',
(MonadIO m, MonadHttp m) =>
Request ->
(Request -> Request) ->
- m (Response (Maybe Lazy.ByteString))
+ m (Response (Maybe LByteString))
get' r f = httpLbs r (method GET . f)
post' r f = httpLbs r (method POST . f)
put' r f = httpLbs r (method PUT . f)
@@ -259,7 +261,7 @@ httpLbs ::
(MonadIO m, MonadHttp m) =>
Request ->
(Request -> Request) ->
- m (Response (Maybe Lazy.ByteString))
+ m (Response (Maybe LByteString))
httpLbs r f = http r f consumeBody
http ::
@@ -275,7 +277,7 @@ httpDebug ::
Debug ->
Request ->
(Request -> Request) ->
- (Response (Maybe Lazy.ByteString) -> IO a) ->
+ (Response (Maybe LByteString) -> IO a) ->
m a
httpDebug debug r f h = do
let rq = f r
@@ -291,11 +293,11 @@ httpDebug debug r f h = do
putStrLn "--"
h rsp
-consumeBody :: Response BodyReader -> IO (Response (Maybe Lazy.ByteString))
+consumeBody :: Response BodyReader -> IO (Response (Maybe LBS.ByteString))
consumeBody r = do
chunks <- brConsume (responseBody r)
let bdy =
if null chunks
then Nothing
- else Just (Lazy.fromChunks chunks)
+ else Just (LBS.fromChunks chunks)
return $ r {responseBody = bdy}
diff --git a/libs/bilge/src/Bilge/TestSession.hs b/libs/bilge/src/Bilge/TestSession.hs
new file mode 100644
index 00000000000..72c3cff3fe9
--- /dev/null
+++ b/libs/bilge/src/Bilge/TestSession.hs
@@ -0,0 +1,28 @@
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+
+module Bilge.TestSession where
+
+import Control.Monad.Catch (MonadCatch, MonadMask, MonadThrow)
+import Control.Monad.State (StateT)
+import qualified Control.Monad.State as ST
+import Imports
+import qualified Network.Wai as Wai
+import qualified Network.Wai.Test as WaiTest
+import qualified Network.Wai.Test.Internal as WaiTest
+
+newtype SessionT m a = SessionT {unSessionT :: ReaderT Wai.Application (StateT WaiTest.ClientState m) a}
+ deriving newtype (Functor, Applicative, Monad, MonadThrow, MonadCatch, MonadMask, MonadIO, MonadFail)
+
+instance MonadTrans SessionT where
+ lift = SessionT . lift . lift
+
+liftSession :: MonadIO m => WaiTest.Session a -> SessionT m a
+liftSession session = SessionT $ do
+ app <- ask
+ clientState <- lift ST.get
+ let resultInState = runReaderT session app
+ let resultInIO = ST.evalStateT resultInState clientState
+ liftIO resultInIO
+
+runSessionT :: Monad m => SessionT m a -> Wai.Application -> m a
+runSessionT session app = ST.evalStateT (runReaderT (unSessionT session) app) WaiTest.initState
diff --git a/libs/brig-types/src/Brig/Types/Connection.hs b/libs/brig-types/src/Brig/Types/Connection.hs
index 1403815ad00..3d6b37ff99a 100644
--- a/libs/brig-types/src/Brig/Types/Connection.hs
+++ b/libs/brig-types/src/Brig/Types/Connection.hs
@@ -55,7 +55,7 @@ data UserIds = UserIds
-- | Data that is passed to the @\/i\/users\/connections-status@ endpoint.
data ConnectionsStatusRequest = ConnectionsStatusRequest
{ csrFrom :: ![UserId],
- csrTo :: ![UserId]
+ csrTo :: !(Maybe [UserId])
}
deriving (Eq, Show, Generic)
diff --git a/libs/galley-types/src/Galley/Types/Teams/Intra.hs b/libs/galley-types/src/Galley/Types/Teams/Intra.hs
index cde3f0f5b4e..8659fdba6a0 100644
--- a/libs/galley-types/src/Galley/Types/Teams/Intra.hs
+++ b/libs/galley-types/src/Galley/Types/Teams/Intra.hs
@@ -23,6 +23,7 @@ module Galley.Types.Teams.Intra
TeamData (..),
TeamStatusUpdate (..),
TeamName (..),
+ GuardLegalholdPolicyConflicts (..),
)
where
@@ -33,6 +34,10 @@ import Data.Json.Util
import Data.Time (UTCTime)
import Galley.Types.Teams (Team)
import Imports
+import Test.QuickCheck.Arbitrary (Arbitrary)
+import Wire.API.Arbitrary (GenericUniform (..))
+import Wire.API.Message (UserClients)
+import Wire.API.Team.LegalHold (LegalholdProtectee)
data TeamStatus
= Active
@@ -102,3 +107,14 @@ newtype TeamName = TeamName
deriving (Eq, Show, Generic)
deriveJSON toJSONFieldName ''TeamName
+
+data GuardLegalholdPolicyConflicts = GuardLegalholdPolicyConflicts
+ { glhProtectee :: LegalholdProtectee,
+ glhUserClients :: UserClients
+ }
+ deriving (Show, Eq, Generic)
+ deriving (Arbitrary) via (GenericUniform GuardLegalholdPolicyConflicts)
+
+instance ToJSON GuardLegalholdPolicyConflicts
+
+instance FromJSON GuardLegalholdPolicyConflicts
diff --git a/libs/galley-types/test/unit/Test/Galley/Types.hs b/libs/galley-types/test/unit/Test/Galley/Types.hs
index e5b7f963a3a..dba17a7cdf3 100644
--- a/libs/galley-types/test/unit/Test/Galley/Types.hs
+++ b/libs/galley-types/test/unit/Test/Galley/Types.hs
@@ -24,6 +24,7 @@ module Test.Galley.Types where
import Control.Lens
import Data.Set hiding (drop)
import Galley.Types.Teams
+import Galley.Types.Teams.Intra (GuardLegalholdPolicyConflicts)
import Imports
import Test.Galley.Roundtrip (testRoundTrip)
import Test.QuickCheck (Arbitrary (arbitrary))
@@ -52,7 +53,8 @@ tests =
-- accordingly. Just maintain the property that adding a new feature name will break
-- this test, and force future develpers to consider what permissions they want to set.
assertBool "all covered" (all (roleHasPerm RoleExternalPartner) (ViewTeamFeature <$> [minBound ..])),
- testRoundTrip @FeatureFlags
+ testRoundTrip @FeatureFlags,
+ testRoundTrip @GuardLegalholdPolicyConflicts
]
instance Arbitrary FeatureFlags where
diff --git a/libs/schema-profunctor/README.md b/libs/schema-profunctor/README.md
index 86741dc107c..427daaa3884 100644
--- a/libs/schema-profunctor/README.md
+++ b/libs/schema-profunctor/README.md
@@ -143,8 +143,8 @@ we can produce a schema for `Invite` as follows:
```haskell
inviteSchema :: ValueSchema NamedSwaggerDoc Invite
inviteSchema = object "Invite" $ Invite
- <$> users .= field "users" (array schema)
- <*> permissions .= field "permissions" (nonEmptyArray schema)
+ <$> users .= field "users" (nonEmptyrray schema)
+ <*> permissions .= field "permissions" (array schema)
```
Here, we cannot use `schema` to deduce the schema for the list or the
@@ -203,7 +203,71 @@ Finally, we add a name to the schema using the `named`
combinator. This does nothing to the JSON encoding-deconding part of
the schema, and only affects the documentation.
-It is important to note how this sum type example is realised as a
+### Enumerations
+
+As a special case of sum types, we have *enumerations*, where every
+summand is a unit type (i.e. the corresponding constructor has no
+arguments). These require some special support, so there are specific
+combinators `element` and `enum`, which can be used to produce schemas
+for enumerations.
+
+For example, consider the type:
+
+```haskell
+data Access
+ = PrivateAccess
+ | InviteAccess
+ | LinkAccess
+ | CodeAccess
+```
+
+We can define a schema for `Access` as follows:
+
+```haskell
+accessSchema :: ValueSchema NamedSwaggerDoc Access
+accessSchema = enum @Text "Access" $
+ mconcat
+ [ element "private" PrivateAccess,
+ element "invite" InviteAccess,
+ element "link" LinkAccess,
+ element "code" CodeAccess
+ ]
+```
+
+The `element` combinator takes two arguments: the value corresponding to
+a case alternative on the JSON side, and the corresponding value on the
+Haskell side. All the intermediate schemas returned by `element` are
+joined together using the `Monoid` instance, and finaly passed to
+`enum`, which takes care of creating the final schema. Note the `@Text`
+type annotation for `enum`, which is required when using
+`OverloadedStrings`.
+
+Similar schema definitions can be used for enumerations that are
+implemented using types other than strings on the JSON side. For
+example:
+
+```haskell
+data ConvType
+ = RegularConv
+ | SelfConv
+ | One2OneConv
+ | ConnectConv
+
+convTypeSchema = enum @Integer "ConvType" $
+ mconcat
+ [ element 0 RegularConv,
+ element 1 SelfConv,
+ element 2 One2OneConv,
+ element 3 ConnectConv
+ ]
+```
+
+uses integers instead of strings for the enumeration values on the JSON
+side.
+
+### Tagged unions
+
+It is important to note how the sum type example above is realised as a
"tagged" union on the haskell side, but an "untagged" one on the JSON
side. That means that the JSON values that this schema parses and
produces distinguish the two cases simply using the type of the
@@ -211,14 +275,168 @@ underlying value. In particular, this approach would not work for sums
that cannot be distinguished in this way, like for example `Either Int
Int`.
-In those cases, one can for example make use of the `field` and
-`object` combinators to move values inside JSON objects, and use the
-keys as tags on the JSON side. Ultimately, since JSON does not
-directly have a notion of sum types, how these types are represented
-is up to the application, and the `SchemaP` combinators should have
-enough flexibility to accommodate most choices of encoding.
+In those cases, one can for example make use of the `field` and `object`
+combinators to move values inside JSON objects, and use the keys as tags
+on the JSON side. Ultimately, since JSON does not directly have a notion
+of sum types, how these types are represented is up to the application,
+and the `SchemaP` combinators have enough flexibility to accommodate
+most choices of encoding.
+
+For example, consider the `Detail` type again:
+
+```haskell
+data Detail
+ = Name Text
+ | Age Int
+```
+
+but this time, we want to create a schema where the correct summand is tagged on the JSON side by wrapping the value corresponding to a `Detail` into a JSON object containing a `tag` field, like:
+
+```json
+{"tag": "name", "value": "Alice"}
+```
+
+This can be achieved by introducing a type for tags, and using the
+`bind` and `dispatch` combinators as follows:
+
+```haskell
+data DetailTag = NameTag | AgeTag
+ deriving (Eq, Enum, Bounded)
+
+tagSchema :: ValueSchema NamedSwaggerDoc DetailTag
+tagSchema = enum @Text "Detail Tag" $
+ mconcat [ element "name" NameTag, element "age" AgeTag ]
+
+detailSchema :: ValueSchema NamedSwaggerDoc Detail
+detailSchema = object "Detail" $
+ fromTagged <$> toTagged .= bind
+ (fst .= field "tag" tagSchema)
+ (snd .= fieldOver _1 "value" untaggedSchema)
+ where
+ toTagged :: Detail -> (DetailTag, Detail)
+ toTagged d@(Name _) = (NameTag, d)
+ toTagged d@(Age _) = (AgeTag, d)
+
+ fromTagged :: (DetailTag, Detail) -> Detail
+ fromTagged = snd
+
+ untaggedSchema = dispatch $ \case
+ NameTag -> tag _Name (unnamed schema)
+ AgeTag -> tag _Age (unnamed schema)
+```
+
+Here `bind` provides a limited monadic interface for `SchemaP`: its
+first argument is a parser for some "tag" value, which in this case is
+simply an enumeration schema for `DetailTag`, while the second argument
+is a special schema whose parser part takes a tag as an extra argument.
+
+Almost always, one is supposed to create this second argument for `bind`
+using the `dispatch` combinator, which takes a function mapping tag
+values to their corresponding schemas. Note that the tag type is
+required to have instances for `Bounded` and `Enum`, to ensure that we
+are able to generate a (finite) documentation for the dispatching
+parser.
+
+The schema returned by `bind` is for a pair of a tag and a value, so in
+the example we use `toTagged` and `fromTagged` to convert it back and
+forth to simply a value of type `Detail`. This part might be different
+in other situations, depending on how exactly the tagged union is
+represented on the Haskell side.
+
+### Optional fields and default values
+
+To define a schema for a JSON object, there are multiple ways to deal
+with the serialisation of optional fields, which we will illustrate
+here.
+
+The simplest (and most common) scenario is an optional field represented by a
+`Maybe` type, that is simply omitted from the generated JSON if it happens to
+be `Nothing`.
+
+For example:
+
+```haskell
+data User = User
+ { userName :: Text,
+ userHandle :: Maybe Text,
+ userExpire :: Maybe Int
+ }
+
+userSchema = object "User" $
+ User
+ <$> userName .= field "name" schema
+ <*> userHandle .= opt (field "handle" schema)
+ <*> userExpire .= opt (field "expire" schema)
+```
+
+Here we apply the `opt` combinator to the optional field, to turn it from a
+schema for `Text` into a schema for `Maybe Text`. The parser for `userHandle`
+will return `Nothing` when the field is missing (or is `null`), and
+correspondingly the serialiser will not produce the field at all when its value
+is `Nothing`.
+
+Another possibility is a field that, when missing, is assumed to have a given default value. Most likely, in this case we do not want the field to be omitted when serialising. The schema can then be defined simply by using the `Alternative` instance of `SchemaP` to provide the default value:
+
+```haskell
+userSchemaWithDefaultName :: ValueSchema NamedSwaggerDoc User
+userSchemaWithDefaultName =
+ object "User" $
+ User
+ <$> userName .= (field "name" schema <|> pure "")
+ <*> userHandle .= opt (field "handle" schema)
+ <*> userExpire .= opt (field "expire" schema)
+```
+
+Now the `name` field is optional, and it is set to the empty string when missing.
+However, the field will still be present in the generated JSON when its value
+is the empty string. If we want the field to be omitted in that case, we can
+use the previous approach, and then convert back and forth from `Maybe Text`:
+
+```haskell
+userSchemaWithDefaultName' :: ValueSchema NamedSwaggerDoc User
+userSchemaWithDefaultName' =
+ object "User" $
+ User
+ <$> (getOptText . userName) .= (fromMaybe "" <$> opt (field "name" schema))
+ <*> userHandle .= opt (field "handle" schema)
+ <*> userExpire .= opt (field "expire" schema)
+ where
+ getOptText :: Text -> Maybe Text
+ getOptText "" = Nothing
+ getOptText t = Just t
+```
+
+In some cases, it might be desirable for the serialiser to output a field even
+when its value is `Nothing`. In that case, we can essentially combine the
+techniques of the previous two examples:
+
+```haskell
+userSchema' :: ValueSchema NamedSwaggerDoc User
+userSchema' = object "User" $ User
+ <$> field "name" schema
+ <*> lax (field "handle" (optWithDefault Aeson.null schema))
+ <*> opt (field "expire" schema)
+```
-## Advanced usage
+Two things to note here:
+
+ - the `optWithDefault` combinator is applied to the schema value *inside*
+ `field`, because the value to use if the value is `Nothing` (`Aeson.null` in
+ this case) applies to the value of the field, and not the containing object.
+ - we have wrapped the whole field inside a call to the `lax` combinator. All
+ this does is to add a `pure Nothing` alternative for the field, which ensures
+ we get a `Nothing` value (as opposed to a failure) when the field is not
+ present at all in the JSON object.
+
+One might wonder why we are using the special combinator `optWithDefault` here
+instead of simply using the `Alternative` instance (via `optional` or
+directly). The reason is that the `Alternative` instance only really affects
+the parser (and its return type), whereas here we also want to encode the fact
+that the serialiser should output the default when the value of the field is
+`Nothing`. That means we need to also change the input type to a `Maybe`, which
+is what `opt` and `optWithDefault` do.
+
+### Redundant fields
Sometimes, JSON encoding of haskell types is not as straightfoward as
in the previous examples. For example, for backward-compatibility
diff --git a/libs/schema-profunctor/package.yaml b/libs/schema-profunctor/package.yaml
index 9057e4041da..4e96e552574 100644
--- a/libs/schema-profunctor/package.yaml
+++ b/libs/schema-profunctor/package.yaml
@@ -23,6 +23,7 @@ library:
- text
- transformers
- vector
+ - containers
tests:
schemas-tests:
main: Main.hs
diff --git a/libs/schema-profunctor/schema-profunctor.cabal b/libs/schema-profunctor/schema-profunctor.cabal
index b698c6bc054..16d81386c32 100644
--- a/libs/schema-profunctor/schema-profunctor.cabal
+++ b/libs/schema-profunctor/schema-profunctor.cabal
@@ -4,7 +4,7 @@ cabal-version: 1.12
--
-- see: https://github.com/sol/hpack
--
--- hash: 11ed18fc8f6fc6cc51f29a022f7695bc086b893b80a35ed8beb5f0840d1d8b45
+-- hash: f1d1bde721143e6e1f8346c434abffcc73f4d5c58eb40d463f337805bbfff766
name: schema-profunctor
version: 0.1.0
@@ -31,6 +31,7 @@ library
, base >=4 && <5
, bifunctors
, comonad
+ , containers
, imports
, lens
, profunctors
diff --git a/libs/schema-profunctor/src/Data/Schema.hs b/libs/schema-profunctor/src/Data/Schema.hs
index 964cb13d546..10466cb5520 100644
--- a/libs/schema-profunctor/src/Data/Schema.hs
+++ b/libs/schema-profunctor/src/Data/Schema.hs
@@ -47,6 +47,7 @@ module Data.Schema
fieldWithDocModifier,
fieldOver,
array,
+ set,
nonEmptyArray,
map_,
enum,
@@ -72,7 +73,8 @@ where
import Control.Applicative
import Control.Comonad
-import Control.Lens hiding (element, enum, (.=))
+import Control.Lens hiding (element, enum, set, (.=))
+import qualified Control.Lens as Lens
import Control.Monad.Trans.Cont
import qualified Data.Aeson.Types as A
import Data.Bifunctor.Joker
@@ -81,6 +83,7 @@ import qualified Data.List.NonEmpty as NonEmpty
import Data.Monoid hiding (Product)
import Data.Profunctor (Star (..))
import Data.Proxy (Proxy (..))
+import qualified Data.Set as Set
import qualified Data.Swagger as S
import qualified Data.Swagger.Declare as S
import qualified Data.Text as T
@@ -125,18 +128,22 @@ instance Semigroup (SchemaOut v a b) where
instance Monoid (SchemaOut v a b) where
mempty = SchemaOut (pure empty)
+-- | A near-semiring (aka seminearring).
+--
+-- This is used for schema documentation types, to support different behaviours
+-- for composing schemas sequentially vs alternatively.
+class Monoid m => NearSemiRing m where
+ zero :: m
+ add :: m -> m -> m
+
newtype SchemaDoc doc a b = SchemaDoc {getDoc :: doc}
- deriving (Functor, Semigroup, Monoid)
+ deriving (Functor, Semigroup, Monoid, NearSemiRing)
deriving (Applicative) via (Const doc)
deriving (Profunctor, Choice) via Joker (Const doc)
--- This instance is not exactly correct, distributivity does not hold
--- in general.
--- FUTUREWORK: introduce a NearSemiRing type class and replace the
--- `Monoid doc` constraint with `NearSemiRing doc`.
-instance Monoid doc => Alternative (SchemaDoc doc a) where
- empty = mempty
- (<|>) = (<>)
+instance NearSemiRing doc => Alternative (SchemaDoc doc a) where
+ empty = zero
+ (<|>) = add
class HasDoc a a' doc doc' | a a' -> doc doc' where
doc :: Lens a a' doc doc'
@@ -205,7 +212,7 @@ instance (Monoid doc, Monoid v') => Applicative (SchemaP doc v v' a) where
SchemaP d1 i1 o1 <*> SchemaP d2 i2 o2 =
SchemaP (d1 <*> d2) (i1 <*> i2) (o1 <*> o2)
-instance (Monoid doc, Monoid v') => Alternative (SchemaP doc v v' a) where
+instance (NearSemiRing doc, Monoid v') => Alternative (SchemaP doc v v' a) where
empty = SchemaP empty empty empty
SchemaP d1 i1 o1 <|> SchemaP d2 i2 o2 =
SchemaP (d1 <|> d2) (i1 <|> i2) (o1 <|> o2)
@@ -228,7 +235,7 @@ instance Choice (SchemaP doc v v') where
right' (SchemaP d i o) = SchemaP (right' d) (right' i) (right' o)
instance HasDoc (SchemaP doc v v' a b) (SchemaP doc' v v' a b) doc doc' where
- doc = lens schemaDoc $ \(SchemaP d i o) d' -> SchemaP (set doc d' d) i o
+ doc = lens schemaDoc $ \(SchemaP d i o) d' -> SchemaP (Lens.set doc d' d) i o
withParser :: SchemaP doc v w a b -> (b -> A.Parser b') -> SchemaP doc v w a b'
withParser (SchemaP (SchemaDoc d) (SchemaIn p) (SchemaOut o)) q =
@@ -363,6 +370,18 @@ array sch = SchemaP (SchemaDoc s) (SchemaIn r) (SchemaOut w)
s = mkArray (schemaDoc sch)
w x = A.Array . V.fromList <$> mapM (schemaOut sch) x
+set ::
+ (HasArray ndoc doc, HasName ndoc, Ord a) =>
+ ValueSchema ndoc a ->
+ ValueSchema doc (Set a)
+set sch = SchemaP (SchemaDoc s) (SchemaIn r) (SchemaOut w)
+ where
+ name = maybe "set" ("set of " <>) (getName (schemaDoc sch))
+ r = A.withArray (T.unpack name) $ \arr ->
+ fmap Set.fromList . mapM (schemaIn sch) $ V.toList arr
+ s = mkArray (schemaDoc sch)
+ w x = A.Array . V.fromList <$> mapM (schemaOut sch) (Set.toList x)
+
nonEmptyArray ::
(HasArray ndoc doc, HasName ndoc, HasMinItems doc (Maybe Integer)) =>
ValueSchema ndoc a ->
@@ -434,17 +453,17 @@ enum name sch = SchemaP (SchemaDoc d) (SchemaIn i) (SchemaOut o)
-- This is most commonly used for optional fields. The parser will
-- return 'Nothing' if the field is missing, and conversely the
-- serialiser will simply omit the field when its value is 'Nothing'.
-opt :: Monoid w => SchemaP d v w a b -> SchemaP d v w (Maybe a) (Maybe b)
+opt :: HasOpt d => Monoid w => SchemaP d v w a b -> SchemaP d v w (Maybe a) (Maybe b)
opt = optWithDefault mempty
-- | An optional schema with a specified failure value
--
-- This is a more general version of 'opt' that allows a custom
-- serialisation 'Nothing' value.
-optWithDefault :: w -> SchemaP d v w a b -> SchemaP d v w (Maybe a) (Maybe b)
+optWithDefault :: HasOpt d => w -> SchemaP d v w a b -> SchemaP d v w (Maybe a) (Maybe b)
optWithDefault w0 sch = SchemaP (SchemaDoc d) (SchemaIn i) (SchemaOut o)
where
- d = schemaDoc sch
+ d = mkOpt (schemaDoc sch)
i = optional . schemaIn sch
o = maybe (pure w0) (schemaOut sch)
@@ -454,7 +473,7 @@ optWithDefault w0 sch = SchemaP (SchemaDoc d) (SchemaIn i) (SchemaOut o)
-- @lax sch@ is just like the one for @sch@, except that it returns
-- 'Nothing' in case of failure.
lax :: Alternative f => f (Maybe a) -> f (Maybe a)
-lax = fmap join . optional
+lax = (<|> pure Nothing)
-- | A schema depending on a parsed value.
--
@@ -499,9 +518,11 @@ text :: Text -> ValueSchema NamedSwaggerDoc Text
text name =
named name $
mkSchema
- (pure mempty)
+ (pure d)
(A.withText (T.unpack name) pure)
(pure . A.String)
+ where
+ d = mempty & S.type_ ?~ S.SwaggerString
-- | A schema for a textual value with possible failure.
parsedText ::
@@ -539,6 +560,11 @@ instance Semigroup s => Semigroup (WithDeclare s) where
instance Monoid s => Monoid (WithDeclare s) where
mempty = WithDeclare (pure ()) mempty
+instance NearSemiRing s => NearSemiRing (WithDeclare s) where
+ zero = WithDeclare (pure ()) zero
+ add (WithDeclare d1 s1) (WithDeclare d2 s2) =
+ WithDeclare (d1 >> d2) (add s1 s2)
+
runDeclare :: WithDeclare s -> Declare s
runDeclare (WithDeclare m s) = s <$ m
@@ -551,6 +577,13 @@ type SwaggerDoc = WithDeclare S.Schema
type NamedSwaggerDoc = WithDeclare S.NamedSchema
+-- addition of schemas is used by the alternative instance, and it works like
+-- multiplication (i.e. the Monoid instance), except that it intersects required
+-- fields instead of concatenating them
+instance NearSemiRing S.Schema where
+ zero = mempty
+ add x y = (x <> y) & S.required .~ intersect (x ^. S.required) (y ^. S.required)
+
-- This class abstracts over SwaggerDoc and NamedSwaggerDoc
class HasSchemaRef doc where
schemaRef :: doc -> WithDeclare (S.Referenced S.Schema)
@@ -591,6 +624,9 @@ class Monoid doc => HasArray ndoc doc | ndoc -> doc where
class Monoid doc => HasMap ndoc doc | ndoc -> doc where
mkMap :: ndoc -> doc
+class HasOpt doc where
+ mkOpt :: doc -> doc
+
class HasEnum doc where
mkEnum :: Text -> [A.Value] -> doc
@@ -601,6 +637,7 @@ instance HasSchemaRef doc => HasField doc SwaggerDoc where
mempty
& S.type_ ?~ S.SwaggerObject
& S.properties . at name ?~ ref
+ & S.required .~ [name]
instance HasObject SwaggerDoc NamedSwaggerDoc where
mkObject name decl = S.NamedSchema (Just name) <$> decl
@@ -637,6 +674,12 @@ instance HasEnum NamedSwaggerDoc where
& S.type_ ?~ S.SwaggerString
& S.enum_ ?~ labels
+instance HasOpt SwaggerDoc where
+ mkOpt = (S.schema . S.required) .~ []
+
+instance HasOpt NamedSwaggerDoc where
+ mkOpt = (S.schema . S.required) .~ []
+
-- | A type with a canonical typed schema definition.
--
-- Using ToSchema, one can split a complicated shema definition
@@ -678,6 +721,8 @@ instance ToSchema Int32 where schema = genericToSchema
instance ToSchema Int64 where schema = genericToSchema
+instance ToSchema Integer where schema = genericToSchema
+
instance ToSchema Word where schema = genericToSchema
instance ToSchema Word8 where schema = genericToSchema
diff --git a/libs/schema-profunctor/test/unit/Test/Data/Schema.hs b/libs/schema-profunctor/test/unit/Test/Data/Schema.hs
index 0df5a4d61dc..718aa1989d3 100644
--- a/libs/schema-profunctor/test/unit/Test/Data/Schema.hs
+++ b/libs/schema-profunctor/test/unit/Test/Data/Schema.hs
@@ -19,7 +19,7 @@ module Test.Data.Schema where
import Control.Applicative
import Control.Arrow ((&&&))
-import Control.Lens (Prism', at, ix, nullOf, prism', (?~), (^.), _1)
+import Control.Lens (Prism', at, ix, makePrisms, nullOf, prism', (?~), (^.), _1)
import Data.Aeson (FromJSON (..), Result (..), ToJSON (..), Value, decode, encode, fromJSON)
import Data.Aeson.QQ
import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
@@ -50,6 +50,7 @@ tests =
testUser1FromJSON,
testUser2ToJSON,
testUser2FromJSON,
+ testUserSchema,
testTaggedObjectToJSON,
testTaggedObjectFromJSON,
testTaggedObject2ToJSON,
@@ -96,6 +97,10 @@ testFooSchema =
"Description should match"
(Just "A Foo object")
(s ^. description)
+ assertEqual
+ "a, b and str should be required"
+ ["a", "b", "str"]
+ (s ^. S.required)
assertEqual
"Schema for \"a\" should be referenced"
(Just (S.Ref (S.Reference "A")))
@@ -186,6 +191,15 @@ testUser2FromJSON =
(Just exampleUser2)
(decode exampleUser2JSON)
+testUserSchema :: TestTree
+testUserSchema =
+ testCase "User schema" $ do
+ let s = S.toSchema (Proxy @User)
+ assertEqual
+ "only name should be required"
+ ["name"]
+ (s ^. S.required)
+
testTaggedObjectToJSON :: TestTree
testTaggedObjectToJSON =
testCase "toJSON TaggedObject" $
@@ -378,15 +392,15 @@ data User = User
userExpire :: Maybe Int
}
deriving (Eq, Show)
- deriving (ToJSON, FromJSON) via Schema User
+ deriving (ToJSON, FromJSON, S.ToSchema) via Schema User
instance ToSchema User where
schema =
object "User" $
User
- <$> userName .= field "name" (unnamed schema)
- <*> userHandle .= opt (field "handle" (unnamed schema))
- <*> userExpire .= opt (field "expire" (unnamed schema))
+ <$> userName .= field "name" schema
+ <*> userHandle .= opt (field "handle" schema)
+ <*> userExpire .= opt (field "expire" schema)
exampleUser1 :: User
exampleUser1 = User "Alice" (Just "alice") Nothing
@@ -473,3 +487,58 @@ instance ToSchema Named where
instance S.ToSchema Named where
declareNamedSchema = schemaToSwagger
+
+-- examples from documentation (only type-checked)
+
+data Detail
+ = Name Text
+ | Age Int
+
+makePrisms ''Detail
+
+data DetailTag = NameTag | AgeTag
+ deriving (Eq, Enum, Bounded)
+
+tagSchema :: ValueSchema NamedSwaggerDoc DetailTag
+tagSchema =
+ enum @Text "Detail Tag" $
+ mconcat [element "name" NameTag, element "age" AgeTag]
+
+detailSchema :: ValueSchema NamedSwaggerDoc Detail
+detailSchema =
+ object "Detail" $
+ fromTagged <$> toTagged
+ .= bind
+ (fst .= field "tag" tagSchema)
+ (snd .= fieldOver _1 "value" untaggedSchema)
+ where
+ toTagged :: Detail -> (DetailTag, Detail)
+ toTagged d@(Name _) = (NameTag, d)
+ toTagged d@(Age _) = (AgeTag, d)
+
+ fromTagged :: (DetailTag, Detail) -> Detail
+ fromTagged = snd
+
+ untaggedSchema = dispatch $ \case
+ NameTag -> tag _Name (unnamed schema)
+ AgeTag -> tag _Age (unnamed schema)
+
+userSchemaWithDefaultName' :: ValueSchema NamedSwaggerDoc User
+userSchemaWithDefaultName' =
+ object "User" $
+ User
+ <$> (getOptText . userName) .= (fromMaybe "" <$> opt (field "name" schema))
+ <*> userHandle .= opt (field "handle" schema)
+ <*> userExpire .= opt (field "expire" schema)
+ where
+ getOptText :: Text -> Maybe Text
+ getOptText "" = Nothing
+ getOptText t = Just t
+
+userSchemaWithDefaultName :: ValueSchema NamedSwaggerDoc User
+userSchemaWithDefaultName =
+ object "User" $
+ User
+ <$> userName .= (field "name" schema <|> pure "")
+ <*> userHandle .= opt (field "handle" schema)
+ <*> userExpire .= opt (field "expire" schema)
diff --git a/libs/types-common/src/Data/LegalHold.hs b/libs/types-common/src/Data/LegalHold.hs
index de705b939fc..0e8bd7c9e56 100644
--- a/libs/types-common/src/Data/LegalHold.hs
+++ b/libs/types-common/src/Data/LegalHold.hs
@@ -39,9 +39,9 @@ instance ToSchema UserLegalHoldStatus where
(S.schema . description ?~ desc) $
enum @Text "UserLegalHoldStatus" $
element "enabled" UserLegalHoldEnabled
- <|> element "pending" UserLegalHoldPending
- <|> element "disabled" UserLegalHoldDisabled
- <|> element "no_consent" UserLegalHoldNoConsent
+ <> element "pending" UserLegalHoldPending
+ <> element "disabled" UserLegalHoldDisabled
+ <> element "no_consent" UserLegalHoldNoConsent
where
desc =
"states whether a user is under legal hold, "
diff --git a/libs/types-common/src/Data/Misc.hs b/libs/types-common/src/Data/Misc.hs
index 525f9692970..c6bda616225 100644
--- a/libs/types-common/src/Data/Misc.hs
+++ b/libs/types-common/src/Data/Misc.hs
@@ -54,6 +54,9 @@ module Data.Misc
-- * Swagger
modelLocation,
+
+ -- * Typesafe FUTUREWORKS
+ FutureWork (..),
)
where
@@ -348,3 +351,12 @@ instance FromJSON PlainTextPassword where
instance Arbitrary PlainTextPassword where
-- TODO: why 6..1024? For tests we might want invalid passwords as well, e.g. 3 chars
arbitrary = PlainTextPassword . fromRange <$> genRangeText @6 @1024 arbitrary
+
+-- | Usage:
+-- 1. Use this type in patterns to mark FUTUREWORKS.
+-- 2. Remove the label constructor -> all futureworks become compiler errors
+--
+-- Example:
+-- >>> let (FutureWork @'LegalholdPlusFederationNotImplemented -> _remoteUsers, localUsers)
+-- >>> = partitionRemoteOrLocalIds domain qualifiedUids
+newtype FutureWork label payload = FutureWork payload
diff --git a/libs/wai-utilities/src/Network/Wai/Utilities/Error.hs b/libs/wai-utilities/src/Network/Wai/Utilities/Error.hs
index 799e8cefceb..661b6618646 100644
--- a/libs/wai-utilities/src/Network/Wai/Utilities/Error.hs
+++ b/libs/wai-utilities/src/Network/Wai/Utilities/Error.hs
@@ -18,10 +18,19 @@
-- You should have received a copy of the GNU Affero General Public License along
-- with this program. If not, see .
-module Network.Wai.Utilities.Error where
+module Network.Wai.Utilities.Error
+ ( Error (..),
+ ErrorData (..),
+ mkError,
+ (!>>),
+ byteStringError,
+ )
+where
import Control.Error
import Data.Aeson hiding (Error)
+import Data.Aeson.Types (Pair)
+import Data.Domain
import Data.Text.Lazy.Encoding (decodeUtf8)
import Imports
import Network.HTTP.Types
@@ -29,29 +38,58 @@ import Network.HTTP.Types
data Error = Error
{ code :: !Status,
label :: !LText,
- message :: !LText
+ message :: !LText,
+ errorData :: Maybe ErrorData
}
deriving (Show, Typeable)
+mkError :: Status -> LText -> LText -> Error
+mkError c l m = Error c l m Nothing
+
instance Exception Error
+data ErrorData = FederationErrorData
+ { federrDomain :: !Domain,
+ federrPath :: !Text
+ }
+ deriving (Show, Typeable)
+
+instance ToJSON ErrorData where
+ toJSON (FederationErrorData d p) =
+ object
+ [ "type" .= ("federation" :: Text),
+ "domain" .= d,
+ "path" .= p
+ ]
+
+instance FromJSON ErrorData where
+ parseJSON = withObject "ErrorData" $ \o ->
+ FederationErrorData
+ <$> o .: "domain"
+ <*> o .: "path"
+
-- | Assumes UTF-8 encoding.
byteStringError :: Status -> LByteString -> LByteString -> Error
-byteStringError s l m = Error s (decodeUtf8 l) (decodeUtf8 m)
+byteStringError s l m = Error s (decodeUtf8 l) (decodeUtf8 m) Nothing
instance ToJSON Error where
- toJSON (Error c l m) =
- object
+ toJSON (Error c l m md) =
+ object $
[ "code" .= statusCode c,
"label" .= l,
"message" .= m
]
+ ++ fromMaybe [] (fmap dataFields md)
+ where
+ dataFields :: ErrorData -> [Pair]
+ dataFields d = ["data" .= d]
instance FromJSON Error where
parseJSON = withObject "Error" $ \o ->
Error <$> (toEnum <$> o .: "code")
<*> o .: "label"
<*> o .: "message"
+ <*> o .:? "data"
-- FIXME: This should not live here.
infixl 5 !>>
diff --git a/libs/wai-utilities/src/Network/Wai/Utilities/Request.hs b/libs/wai-utilities/src/Network/Wai/Utilities/Request.hs
index c454af0fd72..1f969d3447c 100644
--- a/libs/wai-utilities/src/Network/Wai/Utilities/Request.hs
+++ b/libs/wai-utilities/src/Network/Wai/Utilities/Request.hs
@@ -56,7 +56,7 @@ parseBody r = readBody r >>= hoistEither . fmapL Text.pack . eitherDecode'
parseBody' :: (FromJSON a, MonadIO m, MonadThrow m) => JsonRequest a -> m a
parseBody' r = either thrw pure =<< runExceptT (parseBody r)
where
- thrw msg = throwM $ Wai.Error status400 "bad-request" msg
+ thrw msg = throwM $ Wai.mkError status400 "bad-request" msg
parseOptionalBody ::
(MonadIO m, FromJSON a) =>
diff --git a/libs/wai-utilities/src/Network/Wai/Utilities/Response.hs b/libs/wai-utilities/src/Network/Wai/Utilities/Response.hs
index c1e4037c8c1..d3e2e9b2e7a 100644
--- a/libs/wai-utilities/src/Network/Wai/Utilities/Response.hs
+++ b/libs/wai-utilities/src/Network/Wai/Utilities/Response.hs
@@ -46,7 +46,7 @@ jsonContent :: Header
jsonContent = (hContentType, "application/json")
errorRs :: Status -> LText -> LText -> Response
-errorRs s l m = errorRs' (Error s l m)
+errorRs s l m = errorRs' (mkError s l m)
errorRs' :: Error -> Response
errorRs' e = setStatus (code e) (json e)
diff --git a/libs/wai-utilities/src/Network/Wai/Utilities/Server.hs b/libs/wai-utilities/src/Network/Wai/Utilities/Server.hs
index 3724a4e4ef9..a3200292a25 100644
--- a/libs/wai-utilities/src/Network/Wai/Utilities/Server.hs
+++ b/libs/wai-utilities/src/Network/Wai/Utilities/Server.hs
@@ -47,13 +47,15 @@ module Network.Wai.Utilities.Server
where
import Control.Concurrent.Async
+import Control.Error.Util ((?:))
import Control.Exception (throw, throwIO)
import Control.Monad.Catch hiding (onError, onException)
-import Data.Aeson (encode)
+import Data.Aeson (decode, encode)
import qualified Data.ByteString as BS
import Data.ByteString.Builder
import qualified Data.ByteString.Char8 as C
import qualified Data.ByteString.Lazy as LBS
+import Data.Domain (domainText)
import Data.Metrics.GC (spawnGCMetricsCollector)
import Data.Metrics.Middleware
import Data.Streaming.Zlib (ZlibException (..))
@@ -145,7 +147,7 @@ runSettingsWithShutdown s app secs = do
compile :: Monad m => Routes a m b -> Tree (App m)
compile routes = Route.prepare (Route.renderer predicateError >> routes)
where
- predicateError e = return (encode $ Wai.Error (P.status e) "client-error" (format e), [jsonContent])
+ predicateError e = return (encode $ Wai.mkError (P.status e) "client-error" (format e), [jsonContent])
-- [label] 'source' reason: message
format e =
let l = labelStr $ labels e
@@ -171,7 +173,7 @@ compile routes = Route.prepare (Route.renderer predicateError >> routes)
route :: (MonadCatch m, MonadIO m) => Tree (App m) -> Request -> Continue IO -> m ResponseReceived
route rt rq k = Route.routeWith (Route.Config $ errorRs' noEndpoint) rt rq (liftIO . k)
where
- noEndpoint = Wai.Error status404 "no-endpoint" "The requested endpoint does not exist"
+ noEndpoint = Wai.mkError status404 "no-endpoint" "The requested endpoint does not exist"
{-# INLINEABLE route #-}
--------------------------------------------------------------------------------
@@ -201,12 +203,12 @@ catchErrors l m app req k =
errorHandlers :: Applicative m => [Handler m Wai.Error]
errorHandlers =
[ Handler $ \(x :: Wai.Error) -> pure x,
- Handler $ \(_ :: InvalidRequest) -> pure $ Wai.Error status400 "client-error" "Invalid Request",
- Handler $ \(_ :: TimeoutThread) -> pure $ Wai.Error status408 "client-error" "Request Timeout",
+ Handler $ \(_ :: InvalidRequest) -> pure $ Wai.mkError status400 "client-error" "Invalid Request",
+ Handler $ \(_ :: TimeoutThread) -> pure $ Wai.mkError status408 "client-error" "Request Timeout",
Handler $ \case
- ZlibException (-3) -> pure $ Wai.Error status400 "client-error" "Invalid request body compression"
- ZlibException _ -> pure $ Wai.Error status500 "server-error" "Server Error",
- Handler $ \(_ :: SomeException) -> pure $ Wai.Error status500 "server-error" "Server Error"
+ ZlibException (-3) -> pure $ Wai.mkError status400 "client-error" "Invalid request body compression"
+ ZlibException _ -> pure $ Wai.mkError status500 "server-error" "Server Error",
+ Handler $ \(_ :: SomeException) -> pure $ Wai.mkError status500 "server-error" "Server Error"
]
{-# INLINE errorHandlers #-}
@@ -298,7 +300,16 @@ rethrow5xx logger app req k = app req k'
then k resp
else do
rsbody :: LText <- liftIO $ cs <$> lazyResponseBody resp
- throwM $ Wai.Error st "server-error" rsbody
+ throwM $ wrapError st rsbody
+
+-- | Wrap the body of an HTTP error into a Wai.Error structure.
+--
+-- If the error is already a JSON serialisation of a Wai.Error, avoid creating
+-- an unnecessary wrapper.
+wrapError :: Status -> LText -> Wai.Error
+wrapError st body =
+ decode (LT.encodeUtf8 body)
+ ?: Wai.mkError st "server-error" body
-- | This flushes the response! If you want to keep using the response, you need to construct
-- a new one with a fresh body stream.
@@ -342,14 +353,20 @@ logError :: (MonadIO m, HasRequest r) => Logger -> Maybe r -> Wai.Error -> m ()
logError g mr = logError' g (lookupRequestId =<< mr)
logError' :: (MonadIO m) => Logger -> Maybe ByteString -> Wai.Error -> m ()
-logError' g mr (Wai.Error c l m) = liftIO $ Log.debug g logMsg
+logError' g mr (Wai.Error c l m md) = liftIO $ Log.debug g logMsg
where
logMsg =
field "code" (statusCode c)
. field "label" l
. field "request" (fromMaybe "N/A" mr)
+ . fromMaybe id (fmap logErrorData md)
. msg (val "\"" +++ m +++ val "\"")
+ -- TODO: actually log error data fields
+ logErrorData (Wai.FederationErrorData d p) =
+ field "domain" (domainText d)
+ . field "path" p
+
logIO :: (ToBytes msg, HasRequest r) => Logger -> Level -> Maybe r -> msg -> IO ()
logIO lg lv r a =
let reqId = field "request" . fromMaybe "N/A" . lookupRequestId <$> r
diff --git a/libs/wire-api-federation/package.yaml b/libs/wire-api-federation/package.yaml
index 6cacef496dc..0dcd55edab6 100644
--- a/libs/wire-api-federation/package.yaml
+++ b/libs/wire-api-federation/package.yaml
@@ -21,6 +21,7 @@ dependencies:
- bytestring
- either
- errors
+- exceptions
- http-types
- http2-client-grpc
- imports
@@ -39,6 +40,7 @@ dependencies:
- time >=1.8
- types-common
- warp
+- wai-utilities
- wire-api
library:
source-dirs: src
diff --git a/libs/wire-api-federation/proto/router.proto b/libs/wire-api-federation/proto/router.proto
index c2c482ecc08..53e8dd522dc 100644
--- a/libs/wire-api-federation/proto/router.proto
+++ b/libs/wire-api-federation/proto/router.proto
@@ -13,6 +13,7 @@ package wire.federator;
enum Component {
Brig = 0;
+ Galley = 1;
}
message InwardResponse {
diff --git a/libs/wire-api-federation/src/Wire/API/Federation/API/Brig.hs b/libs/wire-api-federation/src/Wire/API/Federation/API/Brig.hs
index 83b1c290f6b..fd501b0db22 100644
--- a/libs/wire-api-federation/src/Wire/API/Federation/API/Brig.hs
+++ b/libs/wire-api-federation/src/Wire/API/Federation/API/Brig.hs
@@ -27,7 +27,7 @@ import Servant.API.Generic
import Servant.Client.Generic (AsClientT, genericClient)
import Test.QuickCheck (Arbitrary)
import Wire.API.Arbitrary (GenericUniform (..))
-import Wire.API.Federation.Client (FederationClientError, FederatorClient)
+import Wire.API.Federation.Client (FederationClientFailure, FederatorClient)
import qualified Wire.API.Federation.GRPC.Types as Proto
import Wire.API.Message (UserClients)
import Wire.API.User (UserProfile)
@@ -86,5 +86,5 @@ data Api routes = Api
}
deriving (Generic)
-clientRoutes :: (MonadError FederationClientError m, MonadIO m) => Api (AsClientT (FederatorClient 'Proto.Brig m))
+clientRoutes :: (MonadError FederationClientFailure m, MonadIO m) => Api (AsClientT (FederatorClient 'Proto.Brig m))
clientRoutes = genericClient
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 10cb7b38dcf..2b7a04fb3d4 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
@@ -17,14 +17,20 @@
module Wire.API.Federation.API.Galley where
+import Control.Monad.Except (MonadError (..))
import Data.Aeson (FromJSON, ToJSON)
import Data.Id (ConvId, UserId)
import Data.Qualified (Qualified)
+import Data.Time.Clock (UTCTime)
import Imports
import Servant.API (JSON, Post, ReqBody, (:>))
import Servant.API.Generic ((:-))
+import Servant.Client.Generic (AsClientT, genericClient)
import Wire.API.Arbitrary (Arbitrary, GenericUniform (..))
import Wire.API.Conversation (Conversation)
+import Wire.API.Conversation.Role (RoleName)
+import Wire.API.Federation.Client (FederationClientFailure, FederatorClient)
+import qualified Wire.API.Federation.GRPC.Types as Proto
import Wire.API.Federation.Util.Aeson (CustomEncoded (CustomEncoded))
-- FUTUREWORK: data types, json instances, more endpoints. See
@@ -65,10 +71,16 @@ newtype GetConversationsResponse = GetConversationsResponse
deriving (ToJSON, FromJSON) via (CustomEncoded GetConversationsResponse)
data ConversationMemberUpdate = ConversationMemberUpdate
- { cmuConvId :: Qualified ConvId,
- cmuUsersAdd :: [UserId],
+ { cmuTime :: UTCTime,
+ cmuOrigUserId :: Qualified UserId,
+ cmuConvId :: Qualified ConvId,
+ cmuAlreadyPresentUsers :: [UserId], -- pre-existing users in the conversation from the receiving domain
+ cmuUsersAdd :: [(Qualified UserId, RoleName)],
cmuUsersRemove :: [UserId]
}
deriving stock (Eq, Show, Generic)
deriving (Arbitrary) via (GenericUniform ConversationMemberUpdate)
deriving (ToJSON, FromJSON) via (CustomEncoded ConversationMemberUpdate)
+
+clientRoutes :: (MonadError FederationClientFailure m, MonadIO m) => Api (AsClientT (FederatorClient 'Proto.Galley m))
+clientRoutes = genericClient
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 7ad67420ed4..983e042e057 100644
--- a/libs/wire-api-federation/src/Wire/API/Federation/Client.hs
+++ b/libs/wire-api-federation/src/Wire/API/Federation/Client.hs
@@ -21,6 +21,7 @@
module Wire.API.Federation.Client where
import Control.Monad.Except (ExceptT, MonadError (..), withExceptT)
+import Control.Monad.State (MonadState (..), StateT, evalStateT, gets)
import Data.ByteString.Builder (toLazyByteString)
import qualified Data.ByteString.Lazy as LBS
import Data.Domain (Domain, domainText)
@@ -41,11 +42,12 @@ data FederatorClientEnv = FederatorClientEnv
originDomain :: Domain
}
-newtype FederatorClient (component :: Proto.Component) m a = FederatorClient {runFederatorClient :: ReaderT FederatorClientEnv m a}
- deriving newtype (Functor, Applicative, Monad, MonadReader FederatorClientEnv, MonadIO)
+-- the state monad is used to store the request path in case of servant errors
+newtype FederatorClient (component :: Proto.Component) m a = FederatorClient {runFederatorClient :: ReaderT FederatorClientEnv (StateT (Maybe ByteString) m) a}
+ deriving newtype (Functor, Applicative, Monad, MonadReader FederatorClientEnv, MonadState (Maybe ByteString), MonadIO)
-runFederatorClientWith :: GrpcClient -> Domain -> Domain -> FederatorClient component m a -> m a
-runFederatorClientWith client targetDomain originDomain = flip runReaderT (FederatorClientEnv client targetDomain originDomain) . runFederatorClient
+runFederatorClientWith :: Monad m => GrpcClient -> Domain -> Domain -> FederatorClient component m a -> m a
+runFederatorClientWith client targetDomain originDomain = flip evalStateT Nothing . flip runReaderT (FederatorClientEnv client targetDomain originDomain) . runFederatorClient
class KnownComponent (c :: Proto.Component) where
componentVal :: Proto.Component
@@ -53,28 +55,44 @@ class KnownComponent (c :: Proto.Component) where
instance KnownComponent 'Proto.Brig where
componentVal = Proto.Brig
+instance KnownComponent 'Proto.Galley where
+ componentVal = Proto.Galley
+
-- | expectedStatuses is ignored as we don't get any status from the federator,
-- all responses have '200 OK' as their status.
-instance (Monad m, MonadError FederationClientError m, MonadIO m, KnownComponent component) => RunClient (FederatorClient component m) where
+instance (Monad m, MonadIO m, MonadError FederationClientFailure m, KnownComponent component) => RunClient (FederatorClient component m) where
runRequestAcceptStatus _expectedStatuses req = do
env <- ask
+ let path = LBS.toStrict . toLazyByteString $ requestPath req
+ domain = targetDomain env
+ mkFailure = FederationClientFailure domain path
+ failure :: MonadError FederationClientFailure n => FederationClientError -> n x
+ failure = throwError . mkFailure
+ rpcFailure = failure . FederationClientRPCError
+ readBody = \case
+ RequestBodyLBS lbs -> pure $ LBS.toStrict lbs
+ RequestBodyBS bs -> pure bs
+ RequestBodySource _ -> failure FederationClientStreamingUnsupported
+ -- save path in the state, so that we can access it from throwClientError
+ -- if necessary
+ put (Just path)
body <- readBody . maybe (RequestBodyBS "") fst $ requestBody req
let call =
Proto.ValidatedFederatedRequest
- (targetDomain env)
+ domain
( Proto.Request
(componentVal @component)
- (LBS.toStrict . toLazyByteString $ requestPath req)
+ path
body
(domainText (originDomain env))
)
grpcResponse <- callRemote (grpcClient env) call
case grpcResponse of
- GRpcTooMuchConcurrency _tmc -> rpcErr "too much concurrency"
- GRpcErrorCode code -> rpcErr $ "grpc error code: " <> T.pack (show code)
- GRpcErrorString msg -> rpcErr $ "grpc error: " <> T.pack msg
- GRpcClientError msg -> rpcErr $ "grpc client error: " <> T.pack (show msg)
- GRpcOk (Proto.OutwardResponseError err) -> throwError (FederationClientOutwardError err)
+ GRpcTooMuchConcurrency _tmc -> rpcFailure "too much concurrency"
+ GRpcErrorCode code -> rpcFailure $ "grpc error code: " <> T.pack (show code)
+ GRpcErrorString msg -> rpcFailure $ "grpc error: " <> T.pack msg
+ GRpcClientError msg -> rpcFailure $ "grpc client error: " <> T.pack (show msg)
+ GRpcOk (Proto.OutwardResponseError err) -> failure (FederationClientOutwardError err)
GRpcOk (Proto.OutwardResponseBody res) -> do
pure $
Response
@@ -86,15 +104,13 @@ instance (Monad m, MonadError FederationClientError m, MonadIO m, KnownComponent
responseHttpVersion = HTTP.http11,
responseBody = LBS.fromStrict res
}
- where
- rpcErr = throwError . FederationClientRPCError
- readBody = \case
- RequestBodyLBS lbs -> pure $ LBS.toStrict lbs
- RequestBodyBS bs -> pure bs
- RequestBodySource _ -> throwError FederationClientStreamingUnsupported
- throwClientError = throwError . FederationClientServantError
-
-instance (Monad m, MonadError FederationClientError m) => MonadError FederationClientError (FederatorClient c m) where
+
+ throwClientError err = do
+ dom <- asks targetDomain
+ path <- gets (fromMaybe "")
+ throwError (FederationClientFailure dom path (FederationClientServantError err))
+
+instance (Monad m, MonadError FederationClientFailure m) => MonadError FederationClientFailure (FederatorClient c m) where
throwError = FederatorClient . throwError
catchError (FederatorClient action) f = FederatorClient $ catchError action (runFederatorClient . f)
@@ -102,7 +118,14 @@ data FederationError
= FederationUnavailable Text
| FederationNotImplemented
| FederationNotConfigured
- | FederationCallFailure FederationClientError
+ | FederationCallFailure FederationClientFailure
+
+data FederationClientFailure = FederationClientFailure
+ { fedFailDomain :: Domain,
+ fedFailPath :: ByteString,
+ fedFailError :: FederationClientError
+ }
+ deriving (Show, Eq)
data FederationClientError
= FederationClientInvalidMethod HTTP.Method
@@ -138,7 +161,7 @@ mkFederatorClient = do
executeFederated ::
(MonadIO m, HasFederatorConfig m) =>
Domain ->
- FederatorClient component (ExceptT FederationClientError m) a ->
+ FederatorClient component (ExceptT FederationClientFailure m) a ->
ExceptT FederationError m a
executeFederated targetDomain action = do
federatorClient <- mkFederatorClient
diff --git a/libs/wire-api-federation/src/Wire/API/Federation/Error.hs b/libs/wire-api-federation/src/Wire/API/Federation/Error.hs
new file mode 100644
index 00000000000..f56b6426957
--- /dev/null
+++ b/libs/wire-api-federation/src/Wire/API/Federation/Error.hs
@@ -0,0 +1,155 @@
+-- This file is part of the Wire Server implementation.
+--
+-- Copyright (C) 2021 Wire Swiss GmbH
+--
+-- This program is free software: you can redistribute it and/or modify it under
+-- the terms of the GNU Affero General Public License as published by the Free
+-- Software Foundation, either version 3 of the License, or (at your option) any
+-- later version.
+--
+-- This program is distributed in the hope that it will be useful, but WITHOUT
+-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+-- details.
+--
+-- You should have received a copy of the GNU Affero General Public License along
+-- with this program. If not, see .
+
+module Wire.API.Federation.Error where
+
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as T
+import qualified Data.Text.Lazy as LT
+import Imports
+import Network.HTTP.Types.Status
+import qualified Network.HTTP.Types.Status as HTTP
+import qualified Network.Wai.Utilities.Error as Wai
+import qualified Servant.Client as Servant
+import Wire.API.Federation.Client
+ ( FederationClientError (..),
+ FederationClientFailure (..),
+ FederationError (..),
+ )
+import qualified Wire.API.Federation.GRPC.Types as Proto
+
+federationErrorToWai :: FederationError -> Wai.Error
+federationErrorToWai (FederationUnavailable err) = federationUnavailable err
+federationErrorToWai FederationNotImplemented = federationNotImplemented
+federationErrorToWai FederationNotConfigured = federationNotConfigured
+federationErrorToWai (FederationCallFailure failure) = addErrorData $
+ case fedFailError failure of
+ FederationClientRPCError msg -> federationRpcError msg
+ FederationClientInvalidMethod mth ->
+ federationInvalidCall
+ ("Unexpected method: " <> LT.fromStrict (T.decodeUtf8 mth))
+ FederationClientStreamingUnsupported -> federationInvalidCall "Streaming unsupported"
+ FederationClientOutwardError outwardErr -> federationRemoteError outwardErr
+ FederationClientServantError (Servant.DecodeFailure msg _) -> federationInvalidBody msg
+ FederationClientServantError (Servant.FailureResponse _ _) ->
+ Wai.mkError unexpectedFederationResponseStatus "unknown-federation-error" "Unknown federation error"
+ FederationClientServantError (Servant.InvalidContentTypeHeader res) ->
+ Wai.mkError
+ unexpectedFederationResponseStatus
+ "federation-invalid-content-type-header"
+ ("Content-type: " <> contentType res)
+ FederationClientServantError (Servant.UnsupportedContentType mediaType res) ->
+ Wai.mkError
+ unexpectedFederationResponseStatus
+ "federation-unsupported-content-type"
+ ("Content-type: " <> contentType res <> ", Media-Type: " <> LT.pack (show mediaType))
+ FederationClientServantError (Servant.ConnectionError exception) ->
+ federationUnavailable . T.pack . show $ exception
+ where
+ contentType = LT.fromStrict . T.decodeUtf8 . maybe "" snd . find (\(name, _) -> name == "Content-Type") . Servant.responseHeaders
+ addErrorData :: Wai.Error -> Wai.Error
+ addErrorData err =
+ err
+ { Wai.errorData =
+ Just
+ Wai.FederationErrorData
+ { Wai.federrDomain = fedFailDomain failure,
+ Wai.federrPath = T.decodeUtf8 (fedFailPath failure)
+ }
+ }
+
+noFederationStatus :: Status
+noFederationStatus = status403
+
+unexpectedFederationResponseStatus :: Status
+unexpectedFederationResponseStatus = HTTP.Status 533 "Unexpected Federation Response"
+
+federatorConnectionRefusedStatus :: Status
+federatorConnectionRefusedStatus = HTTP.Status 521 "Remote Federator Connection Refused"
+
+federationNotImplemented :: Wai.Error
+federationNotImplemented =
+ Wai.mkError
+ noFederationStatus
+ "federation-not-implemented"
+ "Federation is not yet implemented for this endpoint"
+
+federationInvalidCode :: Word32 -> Wai.Error
+federationInvalidCode code =
+ Wai.mkError
+ unexpectedFederationResponseStatus
+ "federation-invalid-code"
+ ("Invalid response code from remote federator: " <> LT.pack (show code))
+
+federationInvalidBody :: Text -> Wai.Error
+federationInvalidBody msg =
+ Wai.mkError
+ unexpectedFederationResponseStatus
+ "federation-invalid-body"
+ ("Could not parse remote federator response: " <> LT.fromStrict msg)
+
+federationUnexpectedBody :: Text -> Wai.Error
+federationUnexpectedBody msg =
+ Wai.mkError
+ unexpectedFederationResponseStatus
+ "federation-unexpected-body"
+ ("Could parse body, but response was not expected: " <> LT.fromStrict msg)
+
+federationNotConfigured :: Wai.Error
+federationNotConfigured =
+ Wai.mkError
+ HTTP.status400
+ "federation-not-enabled"
+ "no federator configured"
+
+federationRpcError :: Text -> Wai.Error
+federationRpcError msg =
+ Wai.mkError
+ HTTP.status500
+ "federation-rpc-error"
+ (LT.fromStrict msg)
+
+federationUnavailable :: Text -> Wai.Error
+federationUnavailable err =
+ Wai.mkError
+ HTTP.status500
+ "federation-not-available"
+ ("Local federator not available: " <> LT.fromStrict err)
+
+federationRemoteError :: Proto.OutwardError -> Wai.Error
+federationRemoteError err = Wai.mkError status (LT.fromStrict label) (LT.fromStrict msg)
+ where
+ decodeError :: Maybe Proto.ErrorPayload -> (Text, Text)
+ decodeError Nothing = ("unknown-federation-error", "Unknown federation error")
+ decodeError (Just (Proto.ErrorPayload label' msg')) = (label', msg')
+
+ (label, msg) = decodeError (Proto.outwardErrorPayload err)
+
+ status = case Proto.outwardErrorType err of
+ Proto.RemoteNotFound -> HTTP.status422
+ Proto.DiscoveryFailed -> HTTP.status500
+ Proto.ConnectionRefused -> HTTP.Status 521 "Web Server Is Down"
+ Proto.TLSFailure -> HTTP.Status 525 "SSL Handshake Failure"
+ Proto.InvalidCertificate -> HTTP.Status 526 "Invalid SSL Certificate"
+ Proto.VersionMismatch -> HTTP.Status 531 "Version Mismatch"
+ Proto.FederationDeniedByRemote -> HTTP.Status 532 "Federation Denied"
+ Proto.FederationDeniedLocally -> HTTP.status400
+ Proto.RemoteFederatorError -> unexpectedFederationResponseStatus
+ Proto.InvalidRequest -> HTTP.status500
+
+federationInvalidCall :: LText -> Wai.Error
+federationInvalidCall = Wai.mkError HTTP.status500 "federation-invalid-call"
diff --git a/libs/wire-api-federation/src/Wire/API/Federation/GRPC/Types.hs b/libs/wire-api-federation/src/Wire/API/Federation/GRPC/Types.hs
index f891797db24..4891859ead8 100644
--- a/libs/wire-api-federation/src/Wire/API/Federation/GRPC/Types.hs
+++ b/libs/wire-api-federation/src/Wire/API/Federation/GRPC/Types.hs
@@ -46,6 +46,7 @@ grpc "Router" id routerProtoFile
data Component
= Brig
+ | Galley
deriving (Typeable, Show, Eq, Generic, ToSchema Router "Component", FromSchema Router "Component")
deriving (Arbitrary) via (GenericUniform Component)
diff --git a/libs/wire-api-federation/src/Wire/API/Federation/Mock.hs b/libs/wire-api-federation/src/Wire/API/Federation/Mock.hs
index 8582b1a8aad..54c02573299 100644
--- a/libs/wire-api-federation/src/Wire/API/Federation/Mock.hs
+++ b/libs/wire-api-federation/src/Wire/API/Federation/Mock.hs
@@ -22,9 +22,10 @@
module Wire.API.Federation.Mock where
import qualified Control.Concurrent.Async as Async
-import Control.Exception.Lifted (finally)
+import Control.Monad.Catch (MonadMask)
+import qualified Control.Monad.Catch as Catch
import Control.Monad.Except (ExceptT (..), MonadError (..), runExceptT)
-import Control.Monad.State (MonadState (..), modify)
+import Control.Monad.State (MonadState (..), gets, modify)
import qualified Data.Aeson as Aeson
import qualified Data.ByteString.Lazy as LBS
import Data.Domain (Domain)
@@ -50,7 +51,8 @@ outwardService = Mu.singleService (Mu.method @"call" callOutward)
callOutward :: FederatedRequest -> MockT ServerErrorIO OutwardResponse
callOutward req = do
modify (\s -> s {receivedRequests = receivedRequests s <> [req]})
- MockT . lift . effectfulResponse =<< get
+ resp <- gets effectfulResponse
+ MockT . lift $ resp req
mkSuccessResponse :: Aeson.ToJSON a => a -> ServerErrorIO OutwardResponse
mkSuccessResponse = pure . OutwardResponseBody . LBS.toStrict . Aeson.encode
@@ -58,8 +60,8 @@ mkSuccessResponse = pure . OutwardResponseBody . LBS.toStrict . Aeson.encode
mkErrorResponse :: OutwardError -> ServerErrorIO OutwardResponse
mkErrorResponse = pure . OutwardResponseError
-startMockFederator :: IORef MockState -> ExceptT String IO ()
-startMockFederator ref = ExceptT $ do
+startMockFederator :: MonadIO m => IORef MockState -> ExceptT String m ()
+startMockFederator ref = ExceptT . liftIO $ do
(port, sock) <- Warp.openFreePort
serverStarted <- newEmptyMVar
let settings =
@@ -78,21 +80,30 @@ startMockFederator ref = ExceptT $ do
liftIO . modifyIORef ref $ \s -> s {serverThread = federatorThread, serverPort = toInteger port}
pure (Right ())
-stopMockFederator :: IORef MockState -> IO ()
-stopMockFederator ref = do
- Async.cancel . serverThread <=< readIORef $ ref
+stopMockFederator :: MonadIO m => IORef MockState -> m ()
+stopMockFederator ref =
+ liftIO $ Async.cancel . serverThread <=< readIORef $ ref
flushState :: IORef MockState -> IO ()
flushState = flip modifyIORef $ \s -> s {receivedRequests = [], effectfulResponse = error "No mock response provided"}
initState :: Domain -> Domain -> MockState
-initState targetDomain originDomain = MockState [] (error "No mock response provided") (error "server not started") (error "No port selected yet") targetDomain originDomain
+initState = MockState [] (error "No mock response provided") (error "server not started") (error "No port selected yet")
+-- | Run an action with access to a mock federator.
+--
+-- The function argument `resp :: FederatedRequest -> ServerErrorIO
+-- OutwardResponse` can be used to provide a fake federator response for each
+-- possible request it is expected to receive.
+--
+-- More explicitly, any request `req` to the federator within the provided
+-- action will return `resp req` as its response.
withMockFederator ::
+ (MonadIO m, MonadMask m) =>
IORef MockState ->
- ServerErrorIO OutwardResponse ->
- (MockState -> ExceptT String IO a) ->
- ExceptT String IO (a, ReceivedRequests)
+ (FederatedRequest -> ServerErrorIO OutwardResponse) ->
+ (MockState -> ExceptT String m a) ->
+ ExceptT String m (a, ReceivedRequests)
withMockFederator ref resp action = do
liftIO . modifyIORef ref $ \s -> s {effectfulResponse = resp}
st <- liftIO $ readIORef ref
@@ -101,10 +112,11 @@ withMockFederator ref resp action = do
pure (actualResponse, receivedRequests st')
withMockFederatorClient ::
+ (MonadIO m, MonadMask m) =>
IORef MockState ->
- ServerErrorIO OutwardResponse ->
- FederatorClient component (ExceptT e IO) a ->
- ExceptT String IO (Either e a, ReceivedRequests)
+ (FederatedRequest -> ServerErrorIO OutwardResponse) ->
+ FederatorClient component (ExceptT e m) a ->
+ ExceptT String m (Either e a, ReceivedRequests)
withMockFederatorClient ref resp action = withMockFederator ref resp $ \st -> do
let cfg = grpcClientConfigSimple "127.0.0.1" (fromInteger (serverPort st)) False
client <- fmapLT (Text.unpack . reason) (ExceptT (createGrpcClient cfg))
@@ -114,16 +126,16 @@ withMockFederatorClient ref resp action = withMockFederator ref resp $ \st -> do
-- | Like 'withMockFederator', but spawn a new instance of the mock federator
-- just for this action.
withTempMockFederator ::
- forall a.
+ (MonadIO m, MonadMask m) =>
MockState ->
- ServerErrorIO OutwardResponse ->
- (MockState -> ExceptT String IO a) ->
- ExceptT String IO (a, ReceivedRequests)
+ (FederatedRequest -> ServerErrorIO OutwardResponse) ->
+ (MockState -> ExceptT String m a) ->
+ ExceptT String m (a, ReceivedRequests)
withTempMockFederator st resp action = do
- ref <- liftIO $ newIORef st
+ ref <- newIORef st
startMockFederator ref
withMockFederator ref resp action
- `finally` lift (stopMockFederator ref)
+ `Catch.finally` stopMockFederator ref
newtype MockT m a = MockT {unMock :: ReaderT (IORef MockState) m a}
deriving newtype (Functor, Applicative, Monad, MonadReader (IORef MockState), MonadIO)
@@ -142,7 +154,7 @@ instance MonadIO m => MonadState MockState (MockT m) where
data MockState = MockState
{ receivedRequests :: ReceivedRequests,
- effectfulResponse :: ServerErrorIO OutwardResponse,
+ effectfulResponse :: FederatedRequest -> ServerErrorIO OutwardResponse,
serverThread :: Async.Async (),
serverPort :: Integer,
stateTarget :: Domain,
diff --git a/libs/wire-api-federation/test/Test/Wire/API/Federation/ClientSpec.hs b/libs/wire-api-federation/test/Test/Wire/API/Federation/ClientSpec.hs
index f8cd42ca97a..4dd1e784abb 100644
--- a/libs/wire-api-federation/test/Test/Wire/API/Federation/ClientSpec.hs
+++ b/libs/wire-api-federation/test/Test/Wire/API/Federation/ClientSpec.hs
@@ -22,6 +22,7 @@ module Test.Wire.API.Federation.ClientSpec where
import Control.Monad.Except (ExceptT, MonadError (..), runExceptT)
import qualified Data.Aeson as Aeson
+import Data.Bifunctor (first)
import qualified Data.ByteString.Lazy as LBS
import Data.Domain (Domain (Domain))
import qualified Data.Text as Text
@@ -30,7 +31,7 @@ import qualified Mu.Server as Mu
import Test.Hspec
import Test.QuickCheck (arbitrary, generate)
import qualified Wire.API.Federation.API.Brig as Brig
-import Wire.API.Federation.Client (FederationClientError (FederationClientOutwardError, FederationClientRPCError))
+import Wire.API.Federation.Client (FederationClientError (FederationClientOutwardError, FederationClientRPCError), FederationClientFailure (..))
import Wire.API.Federation.GRPC.Types (Component (Brig), FederatedRequest (FederatedRequest), Request (..))
import Wire.API.Federation.Mock
import Wire.API.User (UserProfile)
@@ -49,7 +50,7 @@ spec = do
expectedResponse :: Maybe UserProfile <- generate arbitrary
(actualResponse, sentRequests) <-
- assertRightT . withMockFederatorClient stateRef (mkSuccessResponse expectedResponse) $
+ assertRightT . withMockFederatorClient stateRef (const (mkSuccessResponse expectedResponse)) $
Brig.getUserByHandle Brig.clientRoutes handle
sentRequests `shouldBe` [FederatedRequest "target.example.com" (Just $ Request Brig "/federation/get-user-by-handle" (LBS.toStrict (Aeson.encode handle)) "origin.example.com")]
@@ -60,10 +61,11 @@ spec = do
someErr <- generate arbitrary
(actualResponse, _) <-
- assertRightT . withMockFederatorClient stateRef (mkErrorResponse someErr) $
+ assertRightT . withMockFederatorClient stateRef (const (mkErrorResponse someErr)) $
Brig.getUserByHandle Brig.clientRoutes handle
- actualResponse `shouldBe` Left (FederationClientOutwardError someErr)
+ first fedFailError actualResponse
+ `shouldBe` Left (FederationClientOutwardError someErr)
it "should report federator failures correctly" $ do
handle <- generate arbitrary
@@ -75,19 +77,20 @@ spec = do
case actualResponse of
Right res ->
expectationFailure $ "Expected response to be failure, got: \n" <> show res
- Left (FederationClientRPCError errText) ->
+ Left (FederationClientFailure _ _ (FederationClientRPCError errText)) ->
Text.unpack errText `shouldStartWith` "grpc error: GRPC status indicates failure: status-code=INTERNAL, status-message=\"some IO error!"
Left err ->
- expectationFailure $ "Expected FedeartionClientRPCError, got different error: \n" <> show err
+ expectationFailure $ "Expected FederationClientRPCError, got different error: \n" <> show err
it "should report GRPC errors correctly" $ do
handle <- generate arbitrary
(actualResponse, _) <-
- assertRightT . withMockFederatorClient stateRef (throwError $ Mu.ServerError Mu.NotFound "Just testing") $
+ assertRightT . withMockFederatorClient stateRef (const (throwError $ Mu.ServerError Mu.NotFound "Just testing")) $
Brig.getUserByHandle Brig.clientRoutes handle
- actualResponse `shouldBe` Left (FederationClientRPCError "grpc error: GRPC status indicates failure: status-code=NOT_FOUND, status-message=\"Just testing\"")
+ first fedFailError actualResponse
+ `shouldBe` Left (FederationClientRPCError "grpc error: GRPC status indicates failure: status-code=NOT_FOUND, status-message=\"Just testing\"")
assertRight :: Either String b -> IO b
assertRight = \case
diff --git a/libs/wire-api-federation/wire-api-federation.cabal b/libs/wire-api-federation/wire-api-federation.cabal
index 5c52eff809b..d161eedc3f6 100644
--- a/libs/wire-api-federation/wire-api-federation.cabal
+++ b/libs/wire-api-federation/wire-api-federation.cabal
@@ -4,7 +4,7 @@ cabal-version: 1.12
--
-- see: https://github.com/sol/hpack
--
--- hash: 1b0ad324729b16c2eabcf924d9771984cf8dc4a62ae497b8b94f63708e7c50c9
+-- hash: cfa04ed47037ada887f549b3346d2d8d4feadc654f375227c86559b0a5e2ea0f
name: wire-api-federation
version: 0.1.0
@@ -25,6 +25,7 @@ library
Wire.API.Federation.API.Brig
Wire.API.Federation.API.Galley
Wire.API.Federation.Client
+ Wire.API.Federation.Error
Wire.API.Federation.Event
Wire.API.Federation.GRPC.Client
Wire.API.Federation.GRPC.Helper
@@ -46,6 +47,7 @@ library
, bytestring-conversion
, either
, errors
+ , exceptions
, http-types
, http2-client-grpc
, imports
@@ -63,6 +65,7 @@ library
, text >=0.11
, time >=1.8
, types-common
+ , wai-utilities
, warp
, wire-api
default-language: Haskell2010
@@ -90,6 +93,7 @@ test-suite spec
, bytestring-conversion
, either
, errors
+ , exceptions
, hspec
, http-types
, http2-client-grpc
@@ -111,6 +115,7 @@ test-suite spec
, text >=0.11
, time >=1.8
, types-common
+ , wai-utilities
, warp
, wire-api
, wire-api-federation
diff --git a/libs/wire-api/src/Wire/API/Connection.hs b/libs/wire-api/src/Wire/API/Connection.hs
index 9eefcd75dd3..46e51e67d05 100644
--- a/libs/wire-api/src/Wire/API/Connection.hs
+++ b/libs/wire-api/src/Wire/API/Connection.hs
@@ -28,6 +28,8 @@ module Wire.API.Connection
UserConnectionList (..),
Message (..),
Relation (..),
+ RelationWithHistory (..),
+ relationDropHistory,
-- * Requests
ConnectionRequest (..),
@@ -165,6 +167,41 @@ data Relation
deriving (Arbitrary) via (GenericUniform Relation)
deriving (ToSchema) via (CustomSwagger '[ConstructorTagModifier CamelToKebab] Relation)
+-- | 'updateConnectionInternal', requires knowledge of the previous state (before
+-- 'MissingLegalholdConsent'), but the clients don't need that information. To avoid having
+-- to change the API, we introduce an internal variant of 'Relation' with surjective mapping
+-- 'relationDropHistory'.
+data RelationWithHistory
+ = AcceptedWithHistory
+ | BlockedWithHistory
+ | PendingWithHistory
+ | IgnoredWithHistory
+ | SentWithHistory
+ | CancelledWithHistory
+ | MissingLegalholdConsentFromAccepted
+ | MissingLegalholdConsentFromBlocked
+ | MissingLegalholdConsentFromPending
+ | MissingLegalholdConsentFromIgnored
+ | MissingLegalholdConsentFromSent
+ | MissingLegalholdConsentFromCancelled
+ deriving stock (Eq, Ord, Show, Generic)
+ deriving (Arbitrary) via (GenericUniform RelationWithHistory)
+
+relationDropHistory :: RelationWithHistory -> Relation
+relationDropHistory = \case
+ AcceptedWithHistory -> Accepted
+ BlockedWithHistory -> Blocked
+ PendingWithHistory -> Pending
+ IgnoredWithHistory -> Ignored
+ SentWithHistory -> Sent
+ CancelledWithHistory -> Cancelled
+ MissingLegalholdConsentFromAccepted -> MissingLegalholdConsent
+ MissingLegalholdConsentFromBlocked -> MissingLegalholdConsent
+ MissingLegalholdConsentFromPending -> MissingLegalholdConsent
+ MissingLegalholdConsentFromIgnored -> MissingLegalholdConsent
+ MissingLegalholdConsentFromSent -> MissingLegalholdConsent
+ MissingLegalholdConsentFromCancelled -> MissingLegalholdConsent
+
typeRelation :: Doc.DataType
typeRelation =
Doc.string $
diff --git a/libs/wire-api/src/Wire/API/Conversation.hs b/libs/wire-api/src/Wire/API/Conversation.hs
index b8df6d434c1..fa9fc57bae4 100644
--- a/libs/wire-api/src/Wire/API/Conversation.hs
+++ b/libs/wire-api/src/Wire/API/Conversation.hs
@@ -294,7 +294,7 @@ data ConvType
instance ToSchema ConvType where
schema =
enum @Integer "ConvType" $
- asum
+ mconcat
[ element 0 RegularConv,
element 1 SelfConv,
element 2 One2OneConv,
@@ -384,6 +384,8 @@ modelNewConversation = Doc.defineModel "NewConversation" $ do
Doc.description "JSON object to create a new conversation"
Doc.property "users" (Doc.unique $ Doc.array Doc.bytes') $
Doc.description "List of user IDs (excluding the requestor) to be part of this conversation"
+ Doc.property "qualified_users" (Doc.unique . Doc.array $ Doc.bytes') $
+ Doc.description "List of qualified user IDs to be part of this conversation"
Doc.property "name" Doc.string' $ do
Doc.description "The conversation name"
Doc.optional
@@ -414,6 +416,9 @@ instance Arbitrary NewConvUnmanaged where
data NewConv = NewConv
{ newConvUsers :: [UserId],
+ -- | A list of qualified users, which can include some local qualified users
+ -- too.
+ newConvQualifiedUsers :: [Qualified UserId],
newConvName :: Maybe Text,
newConvAccess :: Set Access,
newConvAccessRole :: Maybe AccessRole,
@@ -437,6 +442,13 @@ newConvSchema =
"users"
(description ?~ usersDesc)
(array schema)
+ <*> newConvQualifiedUsers
+ .= ( fieldWithDocModifier
+ "qualified_users"
+ (description ?~ qualifiedUsersDesc)
+ (array schema)
+ <|> pure []
+ )
<*> newConvName .= opt (field "name" schema)
<*> (Set.toList . newConvAccess)
.= ( field "access" (Set.fromList <$> array schema)
@@ -465,7 +477,10 @@ newConvSchema =
where
usersDesc =
"List of user IDs (excluding the requestor) to be \
- \part of this conversation"
+ \part of this conversation (deprecated)"
+ qualifiedUsersDesc =
+ "List of qualified user IDs (excluding the requestor) \
+ \to be part of this conversation"
newConvIsManaged :: NewConv -> Bool
newConvIsManaged = maybe False cnvManaged . newConvTeam
diff --git a/libs/wire-api/src/Wire/API/Conversation/Typing.hs b/libs/wire-api/src/Wire/API/Conversation/Typing.hs
index c41ebe0f63a..c603de7e071 100644
--- a/libs/wire-api/src/Wire/API/Conversation/Typing.hs
+++ b/libs/wire-api/src/Wire/API/Conversation/Typing.hs
@@ -64,7 +64,7 @@ instance ToSchema TypingStatus where
schema =
enum @Text "TypingStatus" $
element "started" StartedTyping
- <|> element "stopped" StoppedTyping
+ <> element "stopped" StoppedTyping
typeTypingStatus :: Doc.DataType
typeTypingStatus =
diff --git a/libs/wire-api/src/Wire/API/ErrorDescription.hs b/libs/wire-api/src/Wire/API/ErrorDescription.hs
new file mode 100644
index 00000000000..ae8bcd30bf6
--- /dev/null
+++ b/libs/wire-api/src/Wire/API/ErrorDescription.hs
@@ -0,0 +1,120 @@
+module Wire.API.ErrorDescription where
+
+import Control.Lens (at, over, (.~), (?~))
+import Control.Lens.Combinators (_Just)
+import qualified Data.Aeson as A
+import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
+import Data.Schema
+import Data.Swagger (PathItem (..), Swagger (..))
+import qualified Data.Swagger as Swagger
+import qualified Data.Text as Text
+import GHC.TypeLits (KnownNat, KnownSymbol, Symbol, natVal, symbolVal)
+import GHC.TypeNats (Nat)
+import Imports hiding (head)
+import Servant hiding (Handler, JSON, addHeader, contentType, respond)
+import Servant.API.Status (KnownStatus)
+import Servant.Swagger.Internal
+
+-- FUTUREWORK: Ponder about elevating label and messge to the type level. If all
+-- errors are static, there is probably no point in having them at value level.
+data ErrorDescription (statusCode :: Nat) (desc :: Symbol) = ErrorDescription
+ { label :: !Text,
+ message :: !Text
+ }
+ deriving stock (Show, Typeable)
+ deriving (A.ToJSON, A.FromJSON, Swagger.ToSchema) via Schema (ErrorDescription statusCode desc)
+
+instance (KnownNat statusCode, KnownSymbol desc) => ToSchema (ErrorDescription statusCode desc) where
+ schema =
+ object "ErrorDescription" $
+ ErrorDescription
+ <$> label .= field "label" schema
+ <*> message .= field "message" schema
+ <* const (natVal (Proxy @statusCode)) .= field "code" schema
+
+-- | This instance works with 'UVerb' only becaue of the following overlapping
+-- instance for 'UVerb method cs (ErrorDescription status desc ': rest))'
+instance (KnownNat statusCode, KnownSymbol desc, AllAccept cs, SwaggerMethod method) => HasSwagger (Verb method statusCode cs (ErrorDescription statusCode desc)) where
+ toSwagger _ = overrrideResponseDesc $ mkEndpoint "/" (Proxy @(Verb method statusCode cs (Headers '[] (ErrorDescription statusCode desc))))
+ where
+ overrrideResponseDesc :: Swagger -> Swagger
+ overrrideResponseDesc =
+ over (Swagger.paths . at "/" . _Just) overridePathItem
+ overridePathItem :: Swagger.PathItem -> Swagger.PathItem
+ overridePathItem =
+ over (Swagger.get . _Just) overrideOp
+ . over (Swagger.post . _Just) overrideOp
+ . over (Swagger.put . _Just) overrideOp
+ . over (Swagger.head_ . _Just) overrideOp
+ . over (Swagger.patch . _Just) overrideOp
+ . over (Swagger.delete . _Just) overrideOp
+ . over (Swagger.options . _Just) overrideOp
+ overrideOp :: Swagger.Operation -> Swagger.Operation
+ overrideOp =
+ Swagger.responses . Swagger.responses . at (fromInteger $ natVal (Proxy @statusCode))
+ ?~ Swagger.Inline
+ ( mempty
+ & Swagger.description .~ Text.pack (symbolVal (Proxy @desc))
+ & Swagger.schema ?~ Swagger.toSchemaRef (Proxy @(ErrorDescription statusCode desc))
+ )
+
+-- | This is a copy of instance for 'UVerb method cs (a:as)', but without this
+-- things don't work because the instance defined in the library is already
+-- compiled with the now overlapped version of `Verb method cs a` and won't
+-- pickup the above instance.
+instance
+ (KnownNat status, KnownSymbol desc, AllAccept cs, SwaggerMethod method, HasSwagger (UVerb method cs rest)) =>
+ HasSwagger (UVerb method cs (ErrorDescription status desc ': rest))
+ where
+ toSwagger _ =
+ toSwagger (Proxy @(Verb method (StatusOf (ErrorDescription status desc)) cs (ErrorDescription status desc)))
+ `combineSwagger` toSwagger (Proxy @(UVerb method cs rest))
+ where
+ -- workaround for https://github.com/GetShopTV/swagger2/issues/218
+ -- We'd like to juse use (<>) but the instances are wrong
+ combinePathItem :: PathItem -> PathItem -> PathItem
+ combinePathItem s t =
+ PathItem
+ { _pathItemGet = _pathItemGet s <> _pathItemGet t,
+ _pathItemPut = _pathItemPut s <> _pathItemPut t,
+ _pathItemPost = _pathItemPost s <> _pathItemPost t,
+ _pathItemDelete = _pathItemDelete s <> _pathItemDelete t,
+ _pathItemOptions = _pathItemOptions s <> _pathItemOptions t,
+ _pathItemHead = _pathItemHead s <> _pathItemHead t,
+ _pathItemPatch = _pathItemPatch s <> _pathItemPatch t,
+ _pathItemParameters = _pathItemParameters s <> _pathItemParameters t
+ }
+
+ combineSwagger :: Swagger -> Swagger -> Swagger
+ combineSwagger s t =
+ Swagger
+ { _swaggerInfo = _swaggerInfo s <> _swaggerInfo t,
+ _swaggerHost = _swaggerHost s <|> _swaggerHost t,
+ _swaggerBasePath = _swaggerBasePath s <|> _swaggerBasePath t,
+ _swaggerSchemes = _swaggerSchemes s <> _swaggerSchemes t,
+ _swaggerConsumes = _swaggerConsumes s <> _swaggerConsumes t,
+ _swaggerProduces = _swaggerProduces s <> _swaggerProduces t,
+ _swaggerPaths = InsOrdHashMap.unionWith combinePathItem (_swaggerPaths s) (_swaggerPaths t),
+ _swaggerDefinitions = _swaggerDefinitions s <> _swaggerDefinitions t,
+ _swaggerParameters = _swaggerParameters s <> _swaggerParameters t,
+ _swaggerResponses = _swaggerResponses s <> _swaggerResponses t,
+ _swaggerSecurityDefinitions = _swaggerSecurityDefinitions s <> _swaggerSecurityDefinitions t,
+ _swaggerSecurity = _swaggerSecurity s <> _swaggerSecurity t,
+ _swaggerTags = _swaggerTags s <> _swaggerTags t,
+ _swaggerExternalDocs = _swaggerExternalDocs s <|> _swaggerExternalDocs t
+ }
+
+instance (KnownNat status, KnownStatus status) => HasStatus (ErrorDescription status desc) where
+ type StatusOf (ErrorDescription status desc) = status
+
+-- * Errors
+
+type ConversationNotFound = ErrorDescription 404 "Conversation not found"
+
+convNotFound :: ConversationNotFound
+convNotFound = ErrorDescription "no-conversation" "conversation not found"
+
+type UnknownClient = ErrorDescription 403 "Unknown Client"
+
+unknownClient :: UnknownClient
+unknownClient = ErrorDescription "unknown-client" "Sending client not known"
diff --git a/libs/wire-api/src/Wire/API/Event/Conversation.hs b/libs/wire-api/src/Wire/API/Event/Conversation.hs
index 0f1a151462a..d9565b531de 100644
--- a/libs/wire-api/src/Wire/API/Event/Conversation.hs
+++ b/libs/wire-api/src/Wire/API/Event/Conversation.hs
@@ -26,6 +26,7 @@ module Wire.API.Event.Conversation
-- * Event data helpers
SimpleMember (..),
+ smId,
SimpleMembers (..),
Connect (..),
MemberUpdateData (..),
@@ -70,6 +71,7 @@ import qualified Data.Aeson as A
import qualified Data.HashMap.Strict as HashMap
import Data.Id
import Data.Json.Util (ToJSONObject (toJSONObject), UTCTimeMillis (fromUTCTimeMillis), toUTCTimeMillis)
+import Data.Qualified
import Data.Schema
import qualified Data.Swagger as S
import qualified Data.Swagger.Build.Api as Doc
@@ -89,8 +91,8 @@ import Wire.API.User (UserIdList (..))
data Event = Event
{ evtType :: EventType,
- evtConv :: ConvId,
- evtFrom :: UserId,
+ evtConv :: Qualified ConvId,
+ evtFrom :: Qualified UserId,
evtTime :: UTCTime,
evtData :: EventData
}
@@ -157,7 +159,7 @@ data EventType
instance ToSchema EventType where
schema =
enum @Text "EventType" $
- asum
+ mconcat
[ element "conversation.member-join" MemberJoin,
element "conversation.member-leave" MemberLeave,
element "conversation.member-update" MemberStateUpdate,
@@ -294,26 +296,17 @@ newtype SimpleMembers = SimpleMembers
deriving (FromJSON, ToJSON, S.ToSchema) via Schema SimpleMembers
instance ToSchema SimpleMembers where
- schema = object "Members" simpleMembersObjectSchema
-
-simpleMembersObjectSchema :: ObjectSchema SwaggerDoc SimpleMembers
-simpleMembersObjectSchema =
- (`withParser` either fail pure) $
- mk
- <$> mMembers .= optional (field "users" (array schema))
- <*> (fmap smId . mMembers)
- .= optional
- ( fieldWithDocModifier
- "user_ids"
- (description ?~ "deprecated")
- (array schema)
- )
- where
- -- This is to make migration easier and not dependent on deployment ordering
- mk :: Maybe [SimpleMember] -> Maybe [UserId] -> Either String SimpleMembers
- mk Nothing Nothing = Left "Either users or user_ids required"
- mk Nothing (Just ids) = pure (SimpleMembers (fmap (\u -> SimpleMember u roleNameWireAdmin) ids))
- mk (Just membs) _ = pure (SimpleMembers membs)
+ schema =
+ object "Members" $
+ SimpleMembers
+ <$> mMembers .= field "users" (array schema)
+ <* (fmap smId . mMembers)
+ .= optional
+ ( fieldWithDocModifier
+ "user_ids"
+ (description ?~ "deprecated")
+ (array schema)
+ )
-- | Used both for 'SimpleMembers' and 'UserIdList'.
modelMembers :: Doc.Model
@@ -323,32 +316,24 @@ modelMembers =
Doc.description "List of user IDs"
data SimpleMember = SimpleMember
- { smId :: UserId,
+ { smQualifiedId :: Qualified UserId,
smConvRoleName :: RoleName
}
deriving stock (Eq, Show, Generic)
deriving (Arbitrary) via (GenericUniform SimpleMember)
+ deriving (FromJSON, ToJSON) via Schema SimpleMember
+
+smId :: SimpleMember -> UserId
+smId = qUnqualified . smQualifiedId
instance ToSchema SimpleMember where
schema =
object "SimpleMember" $
SimpleMember
- <$> smId .= field "id" schema
+ <$> smQualifiedId .= field "qualified_id" schema
+ <* smId .= optional (field "id" schema)
<*> smConvRoleName
- .= field "conversation_role" schema
-
-instance ToJSON SimpleMember where
- toJSON m =
- A.object
- [ "id" A..= smId m,
- "conversation_role" A..= smConvRoleName m
- ]
-
-instance FromJSON SimpleMember where
- parseJSON = A.withObject "simple member object" $ \o ->
- SimpleMember
- <$> o A..: "id"
- <*> o A..:? "conversation_role" A..!= roleNameWireAdmin
+ .= (field "conversation_role" schema <|> pure roleNameWireAdmin)
data Connect = Connect
{ cRecipient :: UserId,
@@ -545,8 +530,10 @@ eventObjectSchema :: ObjectSchema SwaggerDoc Event
eventObjectSchema =
mk
<$> (evtType &&& evtData) .= taggedEventDataSchema
- <*> evtConv .= field "conversation" schema
- <*> evtFrom .= field "from" schema
+ <* (qUnqualified . evtConv) .= optional (field "conversation" schema)
+ <*> evtConv .= field "qualified_conversation" schema
+ <* (qUnqualified . evtFrom) .= optional (field "from" schema)
+ <*> evtFrom .= field "qualified_from" schema
<*> (toUTCTimeMillis . evtTime) .= field "time" (fromUTCTimeMillis <$> schema)
where
mk (ty, d) cid uid tm = Event ty cid uid tm d
diff --git a/libs/wire-api/src/Wire/API/Message.hs b/libs/wire-api/src/Wire/API/Message.hs
index 73b9b907da6..5e33b5c89cb 100644
--- a/libs/wire-api/src/Wire/API/Message.hs
+++ b/libs/wire-api/src/Wire/API/Message.hs
@@ -33,6 +33,8 @@ module Wire.API.Message
OtrFilterMissing (..),
ClientMismatch (..),
UserClients (..),
+ ReportMissing (..),
+ IgnoreMissing (..),
-- * Swagger
modelNewOtrMessage,
@@ -42,12 +44,17 @@ module Wire.API.Message
)
where
-import Data.Aeson
+import Control.Lens ((?~))
+import qualified Data.Aeson as A
+import Data.CommaSeparatedList (CommaSeparatedList (fromCommaSeparatedList))
import Data.Id
import Data.Json.Util
+import Data.Schema
+import qualified Data.Set as Set
+import qualified Data.Swagger as S
import qualified Data.Swagger.Build.Api as Doc
-import Data.Time
import Imports
+import Servant (FromHttpApiData (..))
import Wire.API.Arbitrary (Arbitrary (..), GenericUniform (..))
import Wire.API.User.Client (UserClientMap (..), UserClients (..), modelOtrClientMap, modelUserClients)
@@ -69,6 +76,7 @@ data NewOtrMessage = NewOtrMessage
}
deriving stock (Eq, Show, Generic)
deriving (Arbitrary) via (GenericUniform NewOtrMessage)
+ deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema NewOtrMessage)
modelNewOtrMessage :: Doc.Model
modelNewOtrMessage = Doc.defineModel "NewOtrMessage" $ do
@@ -95,28 +103,17 @@ modelNewOtrMessage = Doc.defineModel "NewOtrMessage" $ do
Doc.description "List of user IDs"
Doc.optional
-instance ToJSON NewOtrMessage where
- toJSON otr =
- object $
- "sender" .= newOtrSender otr
- # "recipients" .= newOtrRecipients otr
- # "native_push" .= newOtrNativePush otr
- # "transient" .= newOtrTransient otr
- # "native_priority" .= newOtrNativePriority otr
- # "data" .= newOtrData otr
- # "report_missing" .= newOtrReportMissing otr
- # []
-
-instance FromJSON NewOtrMessage where
- parseJSON = withObject "new-otr-message" $ \o ->
- NewOtrMessage
- <$> o .: "sender"
- <*> o .: "recipients"
- <*> o .:? "native_push" .!= True
- <*> o .:? "transient" .!= False
- <*> o .:? "native_priority"
- <*> o .:? "data"
- <*> o .:? "report_missing"
+instance ToSchema NewOtrMessage where
+ schema =
+ object "new-otr-message" $
+ NewOtrMessage
+ <$> newOtrSender .= field "sender" schema
+ <*> newOtrRecipients .= field "recipients" schema
+ <*> newOtrNativePush .= (field "native_push" schema <|> pure True)
+ <*> newOtrTransient .= (field "transient" schema <|> pure False)
+ <*> newOtrNativePriority .= opt (field "native_priority" schema)
+ <*> newOtrData .= opt (field "data" schema)
+ <*> newOtrReportMissing .= opt (field "report_missing" (array schema))
--------------------------------------------------------------------------------
-- Priority
@@ -134,6 +131,7 @@ instance FromJSON NewOtrMessage where
data Priority = LowPriority | HighPriority
deriving stock (Eq, Show, Ord, Enum, Generic)
deriving (Arbitrary) via (GenericUniform Priority)
+ deriving (A.ToJSON, A.FromJSON, S.ToSchema) via Schema Priority
typePriority :: Doc.DataType
typePriority =
@@ -143,15 +141,13 @@ typePriority =
"high"
]
-instance ToJSON Priority where
- toJSON LowPriority = String "low"
- toJSON HighPriority = String "high"
-
-instance FromJSON Priority where
- parseJSON = withText "Priority" $ \case
- "low" -> pure LowPriority
- "high" -> pure HighPriority
- x -> fail $ "Invalid push priority: " ++ show x
+instance ToSchema Priority where
+ schema =
+ enum @Text "Priority" $
+ mconcat
+ [ element "low" LowPriority,
+ element "high" HighPriority
+ ]
--------------------------------------------------------------------------------
-- Recipients
@@ -161,7 +157,7 @@ newtype OtrRecipients = OtrRecipients
{ otrRecipientsMap :: UserClientMap Text
}
deriving stock (Eq, Show)
- deriving newtype (ToJSON, FromJSON, Semigroup, Monoid, Arbitrary)
+ deriving newtype (ToSchema, A.ToJSON, A.FromJSON, Semigroup, Monoid, Arbitrary)
-- FUTUREWORK: Remove when 'NewOtrMessage' has ToSchema
modelOtrRecipients :: Doc.Model
@@ -190,7 +186,7 @@ data OtrFilterMissing
deriving (Arbitrary) via (GenericUniform OtrFilterMissing)
data ClientMismatch = ClientMismatch
- { cmismatchTime :: UTCTime,
+ { cmismatchTime :: UTCTimeMillis,
-- | Clients that the message /should/ have been encrypted for, but wasn't.
missingClients :: UserClients,
-- | Clients that the message /should not/ have been encrypted for, but was.
@@ -198,13 +194,12 @@ data ClientMismatch = ClientMismatch
deletedClients :: UserClients
}
deriving stock (Eq, Show, Generic)
+ deriving (A.ToJSON, A.FromJSON, S.ToSchema) via Schema ClientMismatch
instance Arbitrary ClientMismatch where
arbitrary =
ClientMismatch
- <$> (milli <$> arbitrary) <*> arbitrary <*> arbitrary <*> arbitrary
- where
- milli = fromUTCTimeMillis . toUTCTimeMillis
+ <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
modelClientMismatch :: Doc.Model
modelClientMismatch = Doc.defineModel "ClientMismatch" $ do
@@ -218,19 +213,40 @@ modelClientMismatch = Doc.defineModel "ClientMismatch" $ do
Doc.property "deleted" (Doc.ref modelUserClients) $
Doc.description "Map of deleted clients per user."
-instance ToJSON ClientMismatch where
- toJSON m =
- object
- [ "time" .= toUTCTimeMillis (cmismatchTime m),
- "missing" .= missingClients m,
- "redundant" .= redundantClients m,
- "deleted" .= deletedClients m
- ]
-
-instance FromJSON ClientMismatch where
- parseJSON = withObject "ClientMismatch" $ \o ->
- ClientMismatch
- <$> o .: "time"
- <*> o .: "missing"
- <*> o .: "redundant"
- <*> o .: "deleted"
+instance ToSchema ClientMismatch where
+ schema =
+ object "ClientMismatch" $
+ ClientMismatch
+ <$> cmismatchTime .= field "time" schema
+ <*> missingClients .= field "missing" schema
+ <*> redundantClients .= field "redundant" schema
+ <*> deletedClients .= field "deleted" schema
+
+-- QueryParams
+
+data IgnoreMissing
+ = IgnoreMissingAll
+ | IgnoreMissingList (Set UserId)
+ deriving (Show, Eq)
+
+instance S.ToParamSchema IgnoreMissing where
+ toParamSchema _ = mempty & S.type_ ?~ S.SwaggerString
+
+instance FromHttpApiData IgnoreMissing where
+ parseQueryParam = \case
+ "true" -> Right IgnoreMissingAll
+ "false" -> Right $ IgnoreMissingList mempty
+ list -> IgnoreMissingList . Set.fromList . fromCommaSeparatedList <$> parseQueryParam list
+
+data ReportMissing
+ = ReportMissingAll
+ | ReportMissingList (Set UserId)
+
+instance S.ToParamSchema ReportMissing where
+ toParamSchema _ = mempty & S.type_ ?~ S.SwaggerString
+
+instance FromHttpApiData ReportMissing where
+ parseQueryParam = \case
+ "true" -> Right ReportMissingAll
+ "false" -> Right $ ReportMissingList mempty
+ list -> ReportMissingList . Set.fromList . fromCommaSeparatedList <$> parseQueryParam list
diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs b/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs
index 4304e7a804f..7814ca31600 100644
--- a/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs
+++ b/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs
@@ -213,6 +213,7 @@ data Api routes = Api
getUsersPrekeysClientUnqualified ::
routes
:- Summary "(deprecated) Get a prekey for a specific client of a user."
+ :> ZUser
:> "users"
:> CaptureUserId "uid"
:> "prekeys"
@@ -221,6 +222,7 @@ data Api routes = Api
getUsersPrekeysClientQualified ::
routes
:- Summary "Get a prekey for a specific client of a user."
+ :> ZUser
:> "users"
:> Capture "domain" Domain
:> CaptureUserId "uid"
@@ -230,6 +232,7 @@ data Api routes = Api
getUsersPrekeyBundleUnqualified ::
routes
:- Summary "(deprecated) Get a prekey for each client of a user."
+ :> ZUser
:> "users"
:> CaptureUserId "uid"
:> "prekeys"
@@ -237,6 +240,7 @@ data Api routes = Api
getUsersPrekeyBundleQualified ::
routes
:- Summary "Get a prekey for each client of a user."
+ :> ZUser
:> "users"
:> Capture "domain" Domain
:> CaptureUserId "uid"
@@ -248,6 +252,7 @@ data Api routes = Api
"(deprecated) Given a map of user IDs to client IDs return a \
\prekey for each one. You can't request information for more users than \
\maximum conversation size."
+ :> ZUser
:> "users"
:> "prekeys"
:> ReqBody '[JSON] UserClients
@@ -258,6 +263,7 @@ data Api routes = Api
"Given a map of domain to (map of user IDs to client IDs) return a \
\prekey for each one. You can't request information for more users than \
\maximum conversation size."
+ :> ZUser
:> "users"
:> "list-prekeys"
:> ReqBody '[JSON] QualifiedUserClients
diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Galley.hs b/libs/wire-api/src/Wire/API/Routes/Public/Galley.hs
index 873efaf5807..11bce3dece4 100644
--- a/libs/wire-api/src/Wire/API/Routes/Public/Galley.hs
+++ b/libs/wire-api/src/Wire/API/Routes/Public/Galley.hs
@@ -21,9 +21,10 @@
module Wire.API.Routes.Public.Galley where
import Data.CommaSeparatedList
+import Data.Domain
import Data.Id (ConvId, TeamId)
import Data.Range
-import Data.Swagger
+import qualified Data.Swagger as Swagger
import Imports hiding (head)
import Servant hiding (Handler, JSON, addHeader, contentType, respond)
import qualified Servant
@@ -32,8 +33,9 @@ import Servant.Swagger.Internal
import Servant.Swagger.Internal.Orphans ()
import qualified Wire.API.Conversation as Public
import qualified Wire.API.Conversation.Role as Public
+import Wire.API.ErrorDescription (ConversationNotFound, UnknownClient)
import qualified Wire.API.Event.Conversation as Public
-import qualified Wire.API.Event.Team as Public ()
+import qualified Wire.API.Message as Public
import Wire.API.Routes.Public (EmptyResult, ZConn, ZUser)
import qualified Wire.API.Team.Conversation as Public
@@ -47,18 +49,33 @@ type UpdateResponses =
NoContent
]
+type PostOtrResponses =
+ '[ WithStatus 201 Public.ClientMismatch,
+ WithStatus 412 Public.ClientMismatch,
+ ConversationNotFound,
+ UnknownClient
+ ]
+
-- FUTUREWORK: Make a PR to the servant-swagger package with this instance
-instance ToSchema Servant.NoContent where
- declareNamedSchema _ = declareNamedSchema (Proxy @())
+instance Swagger.ToSchema Servant.NoContent where
+ declareNamedSchema _ = Swagger.declareNamedSchema (Proxy @())
data Api routes = Api
{ -- Conversations
+ getUnqualifiedConversation ::
+ routes
+ :- Summary "Get a conversation by ID"
+ :> ZUser
+ :> "conversations"
+ :> Capture "cnv" ConvId
+ :> Get '[Servant.JSON] Public.Conversation,
getConversation ::
routes
:- Summary "Get a conversation by ID"
:> ZUser
:> "conversations"
+ :> Capture "domain" Domain
:> Capture "cnv" ConvId
:> Get '[Servant.JSON] Public.Conversation,
getConversationRoles ::
@@ -153,10 +170,9 @@ data Api routes = Api
:> UVerb 'POST '[Servant.JSON] ConversationResponses,
addMembersToConversationV2 ::
routes
- :- Summary "Add qualified members to an existing conversation: WIP, inaccessible for clients until ready"
+ :- Summary "Add qualified members to an existing conversation: WIP, events not propagated yet."
:> ZUser
:> ZConn
- :> "i" -- FUTUREWORK: remove this /i/ once it's ready. See comment on 'Update.addMembers'
:> "conversations"
:> Capture "cnv" ConvId
:> "members"
@@ -205,11 +221,46 @@ data Api routes = Api
:> Capture "tid" TeamId
:> "conversations"
:> Capture "cid" ConvId
- :> Delete '[] (EmptyResult 200)
+ :> Delete '[] (EmptyResult 200),
+ postOtrMessage ::
+ routes
+ :- Summary "Post an encrypted message to a conversation (accepts JSON)"
+ :> Description PostOtrDescription
+ :> ZUser
+ :> ZConn
+ :> "conversations"
+ :> Capture "cnv" ConvId
+ :> QueryParam "ignore_missing" Public.IgnoreMissing
+ :> QueryParam "report_missing" Public.ReportMissing
+ :> "otr"
+ :> "messages"
+ :> ReqBody '[Servant.JSON] Public.NewOtrMessage
+ :> UVerb 'POST '[Servant.JSON] PostOtrResponses
}
deriving (Generic)
type ServantAPI = ToServantApi Api
-swaggerDoc :: Swagger
+type PostOtrDescription =
+ "This endpoint ensures that the list of clients is correct and only sends the message if the list is correct.\n\
+ \To override this, the endpoint accepts two query params:\n\
+ \- `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\
+ \\n\
+ \Apart 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\
+ \\n\
+ \All 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\
+ \\n\
+ \This endpoint can lead to OtrMessageAdd event being sent to the recipients."
+
+swaggerDoc :: Swagger.Swagger
swaggerDoc = toSwagger (Proxy @ServantAPI)
diff --git a/libs/wire-api/src/Wire/API/Team/LegalHold.hs b/libs/wire-api/src/Wire/API/Team/LegalHold.hs
index d3279198155..b03769225e7 100644
--- a/libs/wire-api/src/Wire/API/Team/LegalHold.hs
+++ b/libs/wire-api/src/Wire/API/Team/LegalHold.hs
@@ -27,6 +27,7 @@ module Wire.API.Team.LegalHold
RemoveLegalHoldSettingsRequest (..),
DisableLegalHoldForUserRequest (..),
ApproveLegalHoldForUserRequest (..),
+ LegalholdProtectee (..),
)
where
@@ -340,3 +341,24 @@ camelToUnderscore :: String -> String
camelToUnderscore = concatMap go . (ix 0 %~ toLower)
where
go x = if isUpper x then "_" <> [toLower x] else [x]
+
+-----------------------------------------------------------------------
+
+-- | Bots are not protected to be potentially recorded by legalhold devices.
+data LegalholdProtectee
+ = ProtectedUser UserId
+ | -- | add UserId here if you want to protect bots as well (or just remove and use
+ -- 'ProtectedUser', but then you'll loose the user type information).
+ UnprotectedBot
+ | -- | FUTUREWORK: protection against legalhold when looking up prekeys accross federated
+ -- instances.
+ LegalholdPlusFederationNotImplemented
+ deriving (Show, Eq, Ord, Generic)
+ deriving (Arbitrary) via (GenericUniform LegalholdProtectee)
+
+instance ToJSON LegalholdProtectee
+
+-- {"tag":"ProtectedUser","contents":"110a187a-be5b-11eb-8f47-370bc8e40f35"}
+-- {"tag":"UnprotectedBot"}
+-- {"tag":"LegalholdPlusFederationNotImplemented"}
+instance FromJSON LegalholdProtectee
diff --git a/libs/wire-api/src/Wire/API/User/Client.hs b/libs/wire-api/src/Wire/API/User/Client.hs
index 4a9cf90a5b8..c887ed8dd47 100644
--- a/libs/wire-api/src/Wire/API/User/Client.hs
+++ b/libs/wire-api/src/Wire/API/User/Client.hs
@@ -30,9 +30,13 @@ module Wire.API.User.Client
QualifiedUserClientMap (..),
QualifiedUserClientPrekeyMap (..),
mkQualifiedUserClientPrekeyMap,
+ UserClientsFull (..),
+ userClientsFullToUserClients,
UserClients (..),
+ mkUserClients,
QualifiedUserClients (..),
filterClients,
+ filterClientsFull,
-- * Client
Client (..),
@@ -72,6 +76,7 @@ import qualified Cassandra as Cql
import Control.Lens ((?~), (^.))
import Data.Aeson (FromJSON (..), ToJSON (..))
import qualified Data.Aeson as A
+import Data.Bifunctor (second)
import Data.Coerce
import Data.Domain (Domain)
import qualified Data.HashMap.Strict as HashMap
@@ -140,7 +145,7 @@ import Wire.API.User.Client.Prekey as Prekey
-- ancient capabilities.
data ClientCapability
= -- | Clients have minimum support for LH, but not for explicit consent. Implicit consent
- -- is granted via the galley server config (see '_setLegalHoldTeamsWhitelist').
+ -- is granted via the galley server config and cassandra table `galley.legalhold_whitelisted`.
ClientSupportsLegalholdImplicitConsent
deriving stock (Eq, Ord, Bounded, Enum, Show, Generic)
deriving (Arbitrary) via (GenericUniform ClientCapability)
@@ -297,21 +302,50 @@ mkQualifiedUserClientPrekeyMap = coerce
--------------------------------------------------------------------------------
-- UserClients
--- TODO: check if example generated by swagger look okay (probably not)
+newtype UserClientsFull = UserClientsFull
+ { userClientsFull :: Map UserId (Set Client)
+ }
+ deriving stock (Eq, Show, Generic)
+ deriving newtype (Semigroup, Monoid)
+
+instance ToJSON UserClientsFull where
+ toJSON =
+ toJSON . Map.foldrWithKey' fn Map.empty . userClientsFull
+ where
+ fn u c m =
+ let k = Text.E.decodeLatin1 (toASCIIBytes (toUUID u))
+ in Map.insert k c m
+
+instance FromJSON UserClientsFull where
+ parseJSON =
+ A.withObject "UserClientsFull" (fmap UserClientsFull . foldrM fn Map.empty . HashMap.toList)
+ where
+ fn (k, v) m = Map.insert <$> parseJSON (A.String k) <*> parseJSON v <*> pure m
+
+instance Arbitrary UserClientsFull where
+ arbitrary = UserClientsFull <$> mapOf' arbitrary (setOf' arbitrary)
+
+userClientsFullToUserClients :: UserClientsFull -> UserClients
+userClientsFullToUserClients (UserClientsFull mp) = UserClients $ Set.map clientId <$> mp
+
newtype UserClients = UserClients
{ userClients :: Map UserId (Set ClientId)
}
deriving stock (Eq, Show, Generic)
deriving newtype (Semigroup, Monoid)
+ deriving (ToJSON, FromJSON, Swagger.ToSchema) via Schema UserClients
-instance Swagger.ToSchema UserClients where
- declareNamedSchema _ = do
- mapSch <- Swagger.declareSchema (Proxy @(Map UserId (Set ClientId)))
- return $
- Swagger.NamedSchema (Just "UserClients") $
- mapSch
- & Swagger.description ?~ "Map of user id to list of client ids."
- & Swagger.example
+mkUserClients :: [(UserId, [ClientId])] -> UserClients
+mkUserClients xs = UserClients $ Map.fromList (xs <&> second Set.fromList)
+
+instance ToSchema UserClients where
+ schema =
+ addDoc . named "UserClients" $ UserClients <$> userClients .= map_ (set schema)
+ where
+ addDoc sch =
+ sch
+ & Swagger.schema . Swagger.description ?~ "Map of user id to list of client ids."
+ & Swagger.schema . Swagger.example
?~ toJSON
( Map.fromList
[ (generateExample @UserId, [newClientId 1684636986166846496, newClientId 4940483633899001999]),
@@ -319,33 +353,23 @@ instance Swagger.ToSchema UserClients where
]
)
--- FUTUREWORK: Remove when 'NewOtrMessage' has ToSchema
+-- FUTUREWORK: Remove when proto endpoint for sending messages is moved to
+-- servant
modelUserClients :: Doc.Model
modelUserClients =
Doc.defineModel "UserClients" $
Doc.property "" (Doc.unique $ Doc.array Doc.bytes') $
Doc.description "Map of user IDs to sets of client IDs ({ UserId: [ClientId] })."
-instance ToJSON UserClients where
- toJSON =
- toJSON . Map.foldrWithKey' fn Map.empty . userClients
- where
- fn u c m =
- let k = Text.E.decodeLatin1 (toASCIIBytes (toUUID u))
- in Map.insert k c m
-
-instance FromJSON UserClients where
- parseJSON =
- A.withObject "UserClients" (fmap UserClients . foldrM fn Map.empty . HashMap.toList)
- where
- fn (k, v) m = Map.insert <$> parseJSON (A.String k) <*> parseJSON v <*> pure m
-
instance Arbitrary UserClients where
arbitrary = UserClients <$> mapOf' arbitrary (setOf' arbitrary)
filterClients :: (Set ClientId -> Bool) -> UserClients -> UserClients
filterClients p (UserClients c) = UserClients $ Map.filter p c
+filterClientsFull :: (Set Client -> Bool) -> UserClientsFull -> UserClientsFull
+filterClientsFull p (UserClientsFull c) = UserClientsFull $ Map.filter p c
+
newtype QualifiedUserClients = QualifiedUserClients
{ qualifiedUserClients :: Map Domain UserClients
}
@@ -378,7 +402,8 @@ data Client = Client
clientLabel :: Maybe Text,
clientCookie :: Maybe CookieLabel,
clientLocation :: Maybe Location,
- clientModel :: Maybe Text
+ clientModel :: Maybe Text,
+ clientCapabilities :: ClientCapabilityList
}
deriving stock (Eq, Show, Generic, Ord)
deriving (Arbitrary) via (GenericUniform Client)
@@ -421,6 +446,7 @@ instance ToJSON Client where
# "cookie" A..= clientCookie c
# "location" A..= clientLocation c
# "model" A..= clientModel c
+ # "capabilities" A..= clientCapabilities c
# []
instance FromJSON Client where
@@ -434,6 +460,7 @@ instance FromJSON Client where
<*> o A..:? "cookie"
<*> o A..:? "location"
<*> o A..:? "model"
+ <*> (o A..:? "capabilities" A..!= ClientCapabilityList mempty)
--------------------------------------------------------------------------------
-- PubClient
@@ -564,7 +591,8 @@ data NewClient = NewClient
newClientClass :: Maybe ClientClass,
newClientCookie :: Maybe CookieLabel,
newClientPassword :: Maybe PlainTextPassword,
- newClientModel :: Maybe Text
+ newClientModel :: Maybe Text,
+ newClientCapabilities :: Maybe (Set ClientCapability)
}
deriving stock (Eq, Show, Generic)
deriving (Arbitrary) via (GenericUniform NewClient)
@@ -604,6 +632,9 @@ modelNewClient = Doc.defineModel "NewClient" $ do
Doc.property "model" Doc.string' $ do
Doc.description "Optional model information of this client"
Doc.optional
+ Doc.property "capabilities" typeClientCapability $ do
+ Doc.description "Hints for the backend so it can behave in a backwards-compatible way."
+ Doc.optional
newClient :: ClientType -> LastPrekey -> NewClient
newClient t k =
@@ -615,7 +646,8 @@ newClient t k =
newClientClass = if t == LegalHoldClientType then Just LegalHoldClient else Nothing,
newClientCookie = Nothing,
newClientPassword = Nothing,
- newClientModel = Nothing
+ newClientModel = Nothing,
+ newClientCapabilities = Nothing
}
instance ToJSON NewClient where
@@ -629,6 +661,7 @@ instance ToJSON NewClient where
# "cookie" A..= newClientCookie c
# "password" A..= newClientPassword c
# "model" A..= newClientModel c
+ # "capabilities" A..= newClientCapabilities c
# []
instance FromJSON NewClient where
@@ -642,6 +675,7 @@ instance FromJSON NewClient where
<*> o A..:? "cookie"
<*> o A..:? "password"
<*> o A..:? "model"
+ <*> o A..:? "capabilities"
--------------------------------------------------------------------------------
-- UpdateClient
diff --git a/libs/wire-api/src/Wire/API/User/Orphans.hs b/libs/wire-api/src/Wire/API/User/Orphans.hs
index 963d24b79ff..a61cdc515d1 100644
--- a/libs/wire-api/src/Wire/API/User/Orphans.hs
+++ b/libs/wire-api/src/Wire/API/User/Orphans.hs
@@ -48,8 +48,6 @@ instance ToSchema CountryCode
-- TODO: steal from https://github.com/haskell-servant/servant-swagger/blob/master/example/src/Todo.hs
--- FUTUREWORK: push orphans upstream to saml2-web-sso, servant-multipart
-
-- | The options to use for schema generation. Must match the options used
-- for 'ToJSON' instances elsewhere.
samlSchemaOptions :: SchemaOptions
diff --git a/libs/wire-api/src/Wire/API/User/Profile.hs b/libs/wire-api/src/Wire/API/User/Profile.hs
index 5cd5d66d418..7bea3c03def 100644
--- a/libs/wire-api/src/Wire/API/User/Profile.hs
+++ b/libs/wire-api/src/Wire/API/User/Profile.hs
@@ -95,7 +95,6 @@ modelUserDisplayName = Doc.defineModel "UserDisplayName" $ do
Doc.property "name" Doc.string' $
Doc.description "User name"
--- FUTUREWORK: use @Range 1 128 Text@ and deriving this instance.
instance ToSchema Name where
schema = Name <$> fromName .= untypedRangedSchema 1 128 schema
@@ -168,7 +167,7 @@ typeAssetSize =
instance ToSchema AssetSize where
schema =
enum @Text "AssetSize" $
- asum
+ mconcat
[ element "preview" AssetPreview,
element "complete" AssetComplete
]
diff --git a/libs/wire-api/test/golden/fromJSON/testObject_NewConvUnmanaged_user_1.json b/libs/wire-api/test/golden/fromJSON/testObject_NewConvUnmanaged_user_1.json
new file mode 100644
index 00000000000..8139eab3cc6
--- /dev/null
+++ b/libs/wire-api/test/golden/fromJSON/testObject_NewConvUnmanaged_user_1.json
@@ -0,0 +1,18 @@
+{
+ "access": [
+ "private",
+ "invite"
+ ],
+ "access_role": "activated",
+ "users": [
+ "00000001-0000-0000-0000-000000000001",
+ "00000000-0000-0000-0000-000000000000"
+ ],
+ "conversation_role": "8tp2gs7b6",
+ "team": {
+ "managed": false,
+ "teamid": "00000000-0000-0001-0000-000000000000"
+ },
+ "receipt_mode": 1,
+ "message_timer": 3320987366258987
+}
diff --git a/libs/wire-api/test/golden/fromJSON/testObject_SimpleMember_user_1.json b/libs/wire-api/test/golden/fromJSON/testObject_SimpleMember_user_1.json
new file mode 100644
index 00000000000..dbbb83a9c18
--- /dev/null
+++ b/libs/wire-api/test/golden/fromJSON/testObject_SimpleMember_user_1.json
@@ -0,0 +1,6 @@
+{
+ "qualified_id": {
+ "id": "0000003a-0000-0042-0000-007500000037",
+ "domain": "faraway.example.com"
+ }
+}
diff --git a/libs/wire-api/test/golden/testObject_AddBotResponse_user_1.json b/libs/wire-api/test/golden/testObject_AddBotResponse_user_1.json
index 932b0eb2454..ca039b97f36 100644
--- a/libs/wire-api/test/golden/testObject_AddBotResponse_user_1.json
+++ b/libs/wire-api/test/golden/testObject_AddBotResponse_user_1.json
@@ -1,11 +1,19 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00000001-0000-0000-0000-000200000003"
+ },
"conversation": "00000001-0000-0000-0000-000200000003",
"time": "1864-05-12T19:20:22.286Z",
"data": {
"name": "6"
},
"from": "00000004-0000-0004-0000-000400000004",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00000004-0000-0004-0000-000400000004"
+ },
"type": "conversation.rename"
},
"accent_id": -3,
diff --git a/libs/wire-api/test/golden/testObject_AddBotResponse_user_10.json b/libs/wire-api/test/golden/testObject_AddBotResponse_user_10.json
index 0f26a3ffa98..55eb948dd15 100644
--- a/libs/wire-api/test/golden/testObject_AddBotResponse_user_10.json
+++ b/libs/wire-api/test/golden/testObject_AddBotResponse_user_10.json
@@ -1,11 +1,19 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00000002-0000-0004-0000-000400000001"
+ },
"conversation": "00000002-0000-0004-0000-000400000001",
"time": "1864-05-04T10:22:33.842Z",
"data": {
"user_ids": []
},
"from": "00000002-0000-0001-0000-000200000000",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00000002-0000-0001-0000-000200000000"
+ },
"type": "conversation.member-leave"
},
"accent_id": 3,
diff --git a/libs/wire-api/test/golden/testObject_AddBotResponse_user_11.json b/libs/wire-api/test/golden/testObject_AddBotResponse_user_11.json
index 4521bc810bf..379f4faf645 100644
--- a/libs/wire-api/test/golden/testObject_AddBotResponse_user_11.json
+++ b/libs/wire-api/test/golden/testObject_AddBotResponse_user_11.json
@@ -1,5 +1,9 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00000000-0000-0003-0000-000300000004"
+ },
"conversation": "00000000-0000-0003-0000-000300000004",
"time": "1864-05-04T14:10:34.032Z",
"data": {
@@ -9,6 +13,10 @@
"access_role": "activated"
},
"from": "00000003-0000-0003-0000-000100000000",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00000003-0000-0003-0000-000100000000"
+ },
"type": "conversation.access-update"
},
"accent_id": 0,
diff --git a/libs/wire-api/test/golden/testObject_AddBotResponse_user_12.json b/libs/wire-api/test/golden/testObject_AddBotResponse_user_12.json
index aafb5d123b5..4c66fd54cdf 100644
--- a/libs/wire-api/test/golden/testObject_AddBotResponse_user_12.json
+++ b/libs/wire-api/test/golden/testObject_AddBotResponse_user_12.json
@@ -1,5 +1,9 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00000003-0000-0000-0000-000200000000"
+ },
"conversation": "00000003-0000-0000-0000-000200000000",
"time": "1864-05-05T01:06:47.245Z",
"data": {
@@ -9,6 +13,10 @@
"recipient": "00000000-0000-0000-0000-000000000000"
},
"from": "00000001-0000-0001-0000-000100000002",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00000001-0000-0001-0000-000100000002"
+ },
"type": "conversation.connect-request"
},
"accent_id": -4,
diff --git a/libs/wire-api/test/golden/testObject_AddBotResponse_user_13.json b/libs/wire-api/test/golden/testObject_AddBotResponse_user_13.json
index e053f1497fb..ee3437a0ad4 100644
--- a/libs/wire-api/test/golden/testObject_AddBotResponse_user_13.json
+++ b/libs/wire-api/test/golden/testObject_AddBotResponse_user_13.json
@@ -1,11 +1,19 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00000000-0000-0003-0000-000100000001"
+ },
"conversation": "00000000-0000-0003-0000-000100000001",
"time": "1864-05-13T05:09:37.371Z",
"data": {
"receipt_mode": 3
},
"from": "00000004-0000-0001-0000-000400000002",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00000004-0000-0001-0000-000400000002"
+ },
"type": "conversation.receipt-mode-update"
},
"accent_id": -1,
diff --git a/libs/wire-api/test/golden/testObject_AddBotResponse_user_14.json b/libs/wire-api/test/golden/testObject_AddBotResponse_user_14.json
index 168fa1d4d70..fc44e3ac0f9 100644
--- a/libs/wire-api/test/golden/testObject_AddBotResponse_user_14.json
+++ b/libs/wire-api/test/golden/testObject_AddBotResponse_user_14.json
@@ -1,5 +1,9 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00000001-0000-0004-0000-000000000004"
+ },
"conversation": "00000001-0000-0004-0000-000000000004",
"time": "1864-05-13T06:48:06.601Z",
"data": {
@@ -12,6 +16,10 @@
"otr_archived_ref": ""
},
"from": "00000000-0000-0000-0000-000200000001",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00000000-0000-0000-0000-000200000001"
+ },
"type": "conversation.member-update"
},
"accent_id": 4,
diff --git a/libs/wire-api/test/golden/testObject_AddBotResponse_user_15.json b/libs/wire-api/test/golden/testObject_AddBotResponse_user_15.json
index cd697cceb0c..d78f7d2ab6c 100644
--- a/libs/wire-api/test/golden/testObject_AddBotResponse_user_15.json
+++ b/libs/wire-api/test/golden/testObject_AddBotResponse_user_15.json
@@ -1,23 +1,43 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00000002-0000-0001-0000-000100000000"
+ },
"conversation": "00000002-0000-0001-0000-000100000000",
"time": "1864-05-11T04:21:51.377Z",
"data": {
"users": [
{
"conversation_role": "uk7gwif0crc3wgiak6qae948ny57lwbwbtgbhran16vnewvp10eqialhaq9m38bqbczm_17nl46lhxs3h2cf448_7zcazh1f4ao8gnrzutbhd29j_lvsz",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000002-0000-0002-0000-000200000001"
+ },
"id": "00000002-0000-0002-0000-000200000001"
},
{
"conversation_role": "il2dfqczvqqvs3vcob7t6t7zi61y4hxgxmmpp19ueznkasq5q1cssn72l5df92b64yuqsizc6up2p1270hu18t97oifzl",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000000-0000-0000-0000-000000000001"
+ },
"id": "00000000-0000-0000-0000-000000000001"
},
{
"conversation_role": "jf7f75hkum6_zxqiabxu8zix2_1kutsjijedcjckapwmymcxx11",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000001-0000-0000-0000-000000000002"
+ },
"id": "00000001-0000-0000-0000-000000000002"
},
{
"conversation_role": "i700417q9qqygs5k5a0zvvnpkvg2jimgi_stuyzfxgokyvy05n3_vgikqr0t5ldsb5fvltb8pylb",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000001-0000-0001-0000-000000000002"
+ },
"id": "00000001-0000-0001-0000-000000000002"
}
],
@@ -29,6 +49,10 @@
]
},
"from": "00000003-0000-0001-0000-000200000003",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00000003-0000-0001-0000-000200000003"
+ },
"type": "conversation.member-join"
},
"accent_id": -4,
diff --git a/libs/wire-api/test/golden/testObject_AddBotResponse_user_16.json b/libs/wire-api/test/golden/testObject_AddBotResponse_user_16.json
index da1ef774ba6..65f6171e9b4 100644
--- a/libs/wire-api/test/golden/testObject_AddBotResponse_user_16.json
+++ b/libs/wire-api/test/golden/testObject_AddBotResponse_user_16.json
@@ -1,11 +1,19 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00000004-0000-0003-0000-000200000000"
+ },
"conversation": "00000004-0000-0003-0000-000200000000",
"time": "1864-05-07T11:54:38.133Z",
"data": {
"name": "𑩣)@䉉"
},
"from": "00000000-0000-0000-0000-000200000001",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00000000-0000-0000-0000-000200000001"
+ },
"type": "conversation.rename"
},
"accent_id": 1,
diff --git a/libs/wire-api/test/golden/testObject_AddBotResponse_user_17.json b/libs/wire-api/test/golden/testObject_AddBotResponse_user_17.json
index 6e3e34438f5..50744a3e747 100644
--- a/libs/wire-api/test/golden/testObject_AddBotResponse_user_17.json
+++ b/libs/wire-api/test/golden/testObject_AddBotResponse_user_17.json
@@ -1,5 +1,9 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00000003-0000-0002-0000-000100000002"
+ },
"conversation": "00000003-0000-0002-0000-000100000002",
"time": "1864-05-09T16:18:32.395Z",
"data": {
@@ -12,6 +16,10 @@
"access_role": "non_activated"
},
"from": "00000003-0000-0000-0000-000400000001",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00000003-0000-0000-0000-000400000001"
+ },
"type": "conversation.access-update"
},
"accent_id": -2,
diff --git a/libs/wire-api/test/golden/testObject_AddBotResponse_user_18.json b/libs/wire-api/test/golden/testObject_AddBotResponse_user_18.json
index 01879b257ef..d369613588e 100644
--- a/libs/wire-api/test/golden/testObject_AddBotResponse_user_18.json
+++ b/libs/wire-api/test/golden/testObject_AddBotResponse_user_18.json
@@ -1,11 +1,19 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00000000-0000-0001-0000-000100000001"
+ },
"conversation": "00000000-0000-0001-0000-000100000001",
"time": "1864-05-13T04:14:10.186Z",
"data": {
"name": "+Sx1"
},
"from": "00000001-0000-0002-0000-000000000003",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00000001-0000-0002-0000-000000000003"
+ },
"type": "conversation.rename"
},
"accent_id": 1,
diff --git a/libs/wire-api/test/golden/testObject_AddBotResponse_user_19.json b/libs/wire-api/test/golden/testObject_AddBotResponse_user_19.json
index 699ad3e400c..da627fc162a 100644
--- a/libs/wire-api/test/golden/testObject_AddBotResponse_user_19.json
+++ b/libs/wire-api/test/golden/testObject_AddBotResponse_user_19.json
@@ -1,9 +1,17 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00000003-0000-0003-0000-000000000000"
+ },
"conversation": "00000003-0000-0003-0000-000000000000",
"time": "1864-05-14T03:03:50.569Z",
"data": null,
"from": "00000004-0000-0003-0000-000400000002",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00000004-0000-0003-0000-000400000002"
+ },
"type": "conversation.code-delete"
},
"accent_id": 3,
diff --git a/libs/wire-api/test/golden/testObject_AddBotResponse_user_2.json b/libs/wire-api/test/golden/testObject_AddBotResponse_user_2.json
index ec3ca8d4d53..558e4aee975 100644
--- a/libs/wire-api/test/golden/testObject_AddBotResponse_user_2.json
+++ b/libs/wire-api/test/golden/testObject_AddBotResponse_user_2.json
@@ -1,11 +1,19 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00000000-0000-0002-0000-000300000001"
+ },
"conversation": "00000000-0000-0002-0000-000300000001",
"time": "1864-05-08T19:02:58.600Z",
"data": {
"status": "started"
},
"from": "00000004-0000-0000-0000-000300000001",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00000004-0000-0000-0000-000300000001"
+ },
"type": "conversation.typing"
},
"accent_id": 3,
diff --git a/libs/wire-api/test/golden/testObject_AddBotResponse_user_20.json b/libs/wire-api/test/golden/testObject_AddBotResponse_user_20.json
index 27540c65c94..7f87a8c75e7 100644
--- a/libs/wire-api/test/golden/testObject_AddBotResponse_user_20.json
+++ b/libs/wire-api/test/golden/testObject_AddBotResponse_user_20.json
@@ -1,11 +1,19 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00000003-0000-0002-0000-000200000004"
+ },
"conversation": "00000003-0000-0002-0000-000200000004",
"time": "1864-05-08T05:48:34.348Z",
"data": {
"message_timer": 3346692440762670
},
"from": "00000002-0000-0003-0000-000200000004",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00000002-0000-0003-0000-000200000004"
+ },
"type": "conversation.message-timer-update"
},
"accent_id": -1,
diff --git a/libs/wire-api/test/golden/testObject_AddBotResponse_user_3.json b/libs/wire-api/test/golden/testObject_AddBotResponse_user_3.json
index a762fe0c95c..813d5595d0d 100644
--- a/libs/wire-api/test/golden/testObject_AddBotResponse_user_3.json
+++ b/libs/wire-api/test/golden/testObject_AddBotResponse_user_3.json
@@ -1,5 +1,9 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00000000-0000-0001-0000-000000000003"
+ },
"conversation": "00000000-0000-0001-0000-000000000003",
"time": "1864-05-10T11:22:13.523Z",
"data": {
@@ -34,6 +38,10 @@
"last_event": "0.0"
},
"from": "00000000-0000-0004-0000-000400000004",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00000000-0000-0004-0000-000400000004"
+ },
"type": "conversation.create"
},
"accent_id": 0,
diff --git a/libs/wire-api/test/golden/testObject_AddBotResponse_user_4.json b/libs/wire-api/test/golden/testObject_AddBotResponse_user_4.json
index 33f0a2865d2..865ba77d74b 100644
--- a/libs/wire-api/test/golden/testObject_AddBotResponse_user_4.json
+++ b/libs/wire-api/test/golden/testObject_AddBotResponse_user_4.json
@@ -1,9 +1,17 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00000003-0000-0002-0000-000300000003"
+ },
"conversation": "00000003-0000-0002-0000-000300000003",
"time": "1864-05-06T03:03:10.788Z",
"data": null,
"from": "00000001-0000-0002-0000-000300000001",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00000001-0000-0002-0000-000300000001"
+ },
"type": "conversation.delete"
},
"accent_id": -4,
diff --git a/libs/wire-api/test/golden/testObject_AddBotResponse_user_5.json b/libs/wire-api/test/golden/testObject_AddBotResponse_user_5.json
index f928a78ba0a..7b2e73becc6 100644
--- a/libs/wire-api/test/golden/testObject_AddBotResponse_user_5.json
+++ b/libs/wire-api/test/golden/testObject_AddBotResponse_user_5.json
@@ -1,5 +1,9 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00000000-0000-0001-0000-000000000001"
+ },
"conversation": "00000000-0000-0001-0000-000000000001",
"time": "1864-05-13T21:19:26.488Z",
"data": {
@@ -37,6 +41,10 @@
"last_event": "0.0"
},
"from": "00000001-0000-0004-0000-000000000002",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00000001-0000-0004-0000-000000000002"
+ },
"type": "conversation.create"
},
"accent_id": -4,
diff --git a/libs/wire-api/test/golden/testObject_AddBotResponse_user_6.json b/libs/wire-api/test/golden/testObject_AddBotResponse_user_6.json
index 9224f1a5c58..2e3cb265b6d 100644
--- a/libs/wire-api/test/golden/testObject_AddBotResponse_user_6.json
+++ b/libs/wire-api/test/golden/testObject_AddBotResponse_user_6.json
@@ -1,5 +1,9 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00000003-0000-0001-0000-000000000003"
+ },
"conversation": "00000003-0000-0001-0000-000000000003",
"time": "1864-05-14T23:40:44.551Z",
"data": {
@@ -8,6 +12,10 @@
"code": "3A16c3OyeYqqLqhHKwZ"
},
"from": "00000002-0000-0004-0000-000300000001",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00000002-0000-0004-0000-000300000001"
+ },
"type": "conversation.code-update"
},
"accent_id": -1,
diff --git a/libs/wire-api/test/golden/testObject_AddBotResponse_user_7.json b/libs/wire-api/test/golden/testObject_AddBotResponse_user_7.json
index 0161ef13439..6968428bbfd 100644
--- a/libs/wire-api/test/golden/testObject_AddBotResponse_user_7.json
+++ b/libs/wire-api/test/golden/testObject_AddBotResponse_user_7.json
@@ -1,11 +1,19 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00000000-0000-0004-0000-000300000004"
+ },
"conversation": "00000000-0000-0004-0000-000300000004",
"time": "1864-05-07T22:30:05.775Z",
"data": {
"receipt_mode": -4
},
"from": "00000003-0000-0001-0000-000400000001",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00000003-0000-0001-0000-000400000001"
+ },
"type": "conversation.receipt-mode-update"
},
"accent_id": -4,
diff --git a/libs/wire-api/test/golden/testObject_AddBotResponse_user_8.json b/libs/wire-api/test/golden/testObject_AddBotResponse_user_8.json
index 28df97ef97e..290302c008f 100644
--- a/libs/wire-api/test/golden/testObject_AddBotResponse_user_8.json
+++ b/libs/wire-api/test/golden/testObject_AddBotResponse_user_8.json
@@ -1,5 +1,9 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00000001-0000-0000-0000-000200000003"
+ },
"conversation": "00000001-0000-0000-0000-000200000003",
"time": "1864-05-05T09:04:05.078Z",
"data": {
@@ -10,6 +14,10 @@
"access_role": "activated"
},
"from": "00000004-0000-0000-0000-000400000000",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00000004-0000-0000-0000-000400000000"
+ },
"type": "conversation.access-update"
},
"accent_id": 0,
diff --git a/libs/wire-api/test/golden/testObject_AddBotResponse_user_9.json b/libs/wire-api/test/golden/testObject_AddBotResponse_user_9.json
index e1f9fcc94bd..62435ec6e26 100644
--- a/libs/wire-api/test/golden/testObject_AddBotResponse_user_9.json
+++ b/libs/wire-api/test/golden/testObject_AddBotResponse_user_9.json
@@ -1,5 +1,9 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00000002-0000-0004-0000-000000000004"
+ },
"conversation": "00000002-0000-0004-0000-000000000004",
"time": "1864-05-07T17:13:06.966Z",
"data": {
@@ -14,6 +18,10 @@
"target": "00000001-0000-0000-0000-000000000001"
},
"from": "00000002-0000-0002-0000-000100000002",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00000002-0000-0002-0000-000100000002"
+ },
"type": "conversation.member-update"
},
"accent_id": 2,
diff --git a/libs/wire-api/test/golden/testObject_Client_user_1.json b/libs/wire-api/test/golden/testObject_Client_user_1.json
index 16b0263f25e..485fd29d67b 100644
--- a/libs/wire-api/test/golden/testObject_Client_user_1.json
+++ b/libs/wire-api/test/golden/testObject_Client_user_1.json
@@ -1,8 +1 @@
-{
- "time": "1864-05-06T19:39:12.770Z",
- "model": ";",
- "id": "2",
- "type": "permanent",
- "class": "desktop",
- "label": "%*"
-}
\ No newline at end of file
+{"time":"1864-05-06T19:39:12.770Z","model":";","id":"2","type":"permanent","class":"desktop","capabilities":{"capabilities":[]},"label":"%*"}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Client_user_10.json b/libs/wire-api/test/golden/testObject_Client_user_10.json
index aae68e5c0a0..8d4e645b039 100644
--- a/libs/wire-api/test/golden/testObject_Client_user_10.json
+++ b/libs/wire-api/test/golden/testObject_Client_user_10.json
@@ -1,11 +1 @@
-{
- "cookie": "L",
- "time": "1864-05-10T18:42:04.137Z",
- "location": {
- "lat": -2.6734377548386075,
- "lon": -1.40544074714727
- },
- "model": "\u0018",
- "id": "0",
- "type": "permanent"
-}
\ No newline at end of file
+{"cookie":"L","time":"1864-05-10T18:42:04.137Z","location":{"lat":-2.6734377548386075,"lon":-1.40544074714727},"model":"\u0018","id":"0","type":"permanent","capabilities":{"capabilities":[]}}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Client_user_11.json b/libs/wire-api/test/golden/testObject_Client_user_11.json
index c090ff7b90a..aa134735b51 100644
--- a/libs/wire-api/test/golden/testObject_Client_user_11.json
+++ b/libs/wire-api/test/golden/testObject_Client_user_11.json
@@ -1,13 +1 @@
-{
- "cookie": "5",
- "time": "1864-05-08T11:57:08.087Z",
- "location": {
- "lat": 0.44311730892815937,
- "lon": 0.6936233843789369
- },
- "model": "ML",
- "id": "3",
- "type": "temporary",
- "class": "legalhold",
- "label": "\u001fb"
-}
\ No newline at end of file
+{"cookie":"5","time":"1864-05-08T11:57:08.087Z","location":{"lat":0.44311730892815937,"lon":0.6936233843789369},"model":"ML","id":"3","type":"temporary","class":"legalhold","capabilities":{"capabilities":[]},"label":"\u001fb"}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Client_user_12.json b/libs/wire-api/test/golden/testObject_Client_user_12.json
index 367119c753d..eef9fb95c46 100644
--- a/libs/wire-api/test/golden/testObject_Client_user_12.json
+++ b/libs/wire-api/test/golden/testObject_Client_user_12.json
@@ -1,12 +1 @@
-{
- "cookie": "0",
- "time": "1864-05-08T18:44:00.378Z",
- "location": {
- "lat": -2.502416826395783,
- "lon": 1.4712334862249388
- },
- "model": "",
- "id": "2",
- "type": "permanent",
- "label": ""
-}
\ No newline at end of file
+{"cookie":"0","time":"1864-05-08T18:44:00.378Z","location":{"lat":-2.502416826395783,"lon":1.4712334862249388},"model":"","id":"2","type":"permanent","capabilities":{"capabilities":[]},"label":""}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Client_user_13.json b/libs/wire-api/test/golden/testObject_Client_user_13.json
index e2c838166c9..62669f2484f 100644
--- a/libs/wire-api/test/golden/testObject_Client_user_13.json
+++ b/libs/wire-api/test/golden/testObject_Client_user_13.json
@@ -1,13 +1 @@
-{
- "cookie": "\u000c^",
- "time": "1864-05-07T01:09:04.597Z",
- "location": {
- "lat": -2.3798205243177692,
- "lon": -2.619240132398651
- },
- "model": "\u0017𐲤",
- "id": "2",
- "type": "permanent",
- "class": "phone",
- "label": ""
-}
\ No newline at end of file
+{"cookie":"\u000c^","time":"1864-05-07T01:09:04.597Z","location":{"lat":-2.3798205243177692,"lon":-2.619240132398651},"model":"\u0017𐲤","id":"2","type":"permanent","class":"phone","capabilities":{"capabilities":[]},"label":""}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Client_user_14.json b/libs/wire-api/test/golden/testObject_Client_user_14.json
index f16102040dc..3ac52f48e57 100644
--- a/libs/wire-api/test/golden/testObject_Client_user_14.json
+++ b/libs/wire-api/test/golden/testObject_Client_user_14.json
@@ -1,12 +1 @@
-{
- "time": "1864-05-12T11:00:10.449Z",
- "location": {
- "lat": 2.459582010332432,
- "lon": -1.2286910026214775
- },
- "model": "\r",
- "id": "2",
- "type": "temporary",
- "class": "tablet",
- "label": "x\u000e"
-}
\ No newline at end of file
+{"time":"1864-05-12T11:00:10.449Z","location":{"lat":2.459582010332432,"lon":-1.2286910026214775},"model":"\r","id":"2","type":"temporary","class":"tablet","capabilities":{"capabilities":[]},"label":"x\u000e"}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Client_user_15.json b/libs/wire-api/test/golden/testObject_Client_user_15.json
index cbf1ba8d8b1..ab5748c5915 100644
--- a/libs/wire-api/test/golden/testObject_Client_user_15.json
+++ b/libs/wire-api/test/golden/testObject_Client_user_15.json
@@ -1,8 +1 @@
-{
- "cookie": "N",
- "time": "1864-05-08T11:28:27.778Z",
- "model": "zAI",
- "id": "3",
- "type": "temporary",
- "label": "\u0004G"
-}
\ No newline at end of file
+{"cookie":"N","time":"1864-05-08T11:28:27.778Z","model":"zAI","id":"3","type":"temporary","capabilities":{"capabilities":[]},"label":"\u0004G"}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Client_user_16.json b/libs/wire-api/test/golden/testObject_Client_user_16.json
index a0f8235ee13..e216def07fc 100644
--- a/libs/wire-api/test/golden/testObject_Client_user_16.json
+++ b/libs/wire-api/test/golden/testObject_Client_user_16.json
@@ -1,9 +1 @@
-{
- "cookie": "U",
- "time": "1864-05-12T11:31:10.072Z",
- "model": "",
- "id": "2",
- "type": "temporary",
- "class": "legalhold",
- "label": "=E"
-}
\ No newline at end of file
+{"cookie":"U","time":"1864-05-12T11:31:10.072Z","model":"","id":"2","type":"temporary","class":"legalhold","capabilities":{"capabilities":[]},"label":"=E"}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Client_user_17.json b/libs/wire-api/test/golden/testObject_Client_user_17.json
index 6d29d3e3653..034aaa838f4 100644
--- a/libs/wire-api/test/golden/testObject_Client_user_17.json
+++ b/libs/wire-api/test/golden/testObject_Client_user_17.json
@@ -1,12 +1 @@
-{
- "cookie": "",
- "time": "1864-05-12T02:25:34.770Z",
- "location": {
- "lat": -1.6915872714820337,
- "lon": 2.1128949838723656
- },
- "model": "",
- "id": "4",
- "type": "temporary",
- "class": "desktop"
-}
\ No newline at end of file
+{"cookie":"","time":"1864-05-12T02:25:34.770Z","location":{"lat":-1.6915872714820337,"lon":2.1128949838723656},"model":"","id":"4","type":"temporary","class":"desktop","capabilities":{"capabilities":[]}}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Client_user_18.json b/libs/wire-api/test/golden/testObject_Client_user_18.json
index bb200a411de..6c7f06f5df6 100644
--- a/libs/wire-api/test/golden/testObject_Client_user_18.json
+++ b/libs/wire-api/test/golden/testObject_Client_user_18.json
@@ -1,13 +1 @@
-{
- "cookie": "PG:",
- "time": "1864-05-07T17:21:05.930Z",
- "location": {
- "lat": -1.2949675488134762,
- "lon": 0.43717421775412324
- },
- "model": "",
- "id": "1",
- "type": "temporary",
- "class": "legalhold",
- "label": ""
-}
\ No newline at end of file
+{"cookie":"PG:","time":"1864-05-07T17:21:05.930Z","location":{"lat":-1.2949675488134762,"lon":0.43717421775412324},"model":"","id":"1","type":"temporary","class":"legalhold","capabilities":{"capabilities":[]},"label":""}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Client_user_19.json b/libs/wire-api/test/golden/testObject_Client_user_19.json
index 583538dc2dd..e4c3ba8fe1b 100644
--- a/libs/wire-api/test/golden/testObject_Client_user_19.json
+++ b/libs/wire-api/test/golden/testObject_Client_user_19.json
@@ -1,12 +1 @@
-{
- "time": "1864-05-12T07:49:27.999Z",
- "location": {
- "lat": -1.4630309786758076,
- "lon": -0.5295690632216867
- },
- "model": "",
- "id": "2",
- "type": "permanent",
- "class": "desktop",
- "label": "l"
-}
\ No newline at end of file
+{"time":"1864-05-12T07:49:27.999Z","location":{"lat":-1.4630309786758076,"lon":-0.5295690632216867},"model":"","id":"2","type":"permanent","class":"desktop","capabilities":{"capabilities":[]},"label":"l"}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Client_user_2.json b/libs/wire-api/test/golden/testObject_Client_user_2.json
index b6af6718d74..d118861a2e1 100644
--- a/libs/wire-api/test/golden/testObject_Client_user_2.json
+++ b/libs/wire-api/test/golden/testObject_Client_user_2.json
@@ -1,10 +1 @@
-{
- "cookie": "c",
- "time": "1864-05-07T08:48:22.537Z",
- "location": {
- "lat": 0.6919026326441752,
- "lon": 1.18215529547942
- },
- "id": "1",
- "type": "legalhold"
-}
\ No newline at end of file
+{"cookie":"c","time":"1864-05-07T08:48:22.537Z","location":{"lat":0.6919026326441752,"lon":1.18215529547942},"id":"1","type":"legalhold","capabilities":{"capabilities":[]}}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Client_user_20.json b/libs/wire-api/test/golden/testObject_Client_user_20.json
index 7379687d06c..4217467994e 100644
--- a/libs/wire-api/test/golden/testObject_Client_user_20.json
+++ b/libs/wire-api/test/golden/testObject_Client_user_20.json
@@ -8,5 +8,10 @@
"id": "1",
"type": "legalhold",
"class": "phone",
+ "capabilities": {
+ "capabilities": [
+ "legalhold-implicit-consent"
+ ]
+ },
"label": "-v"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Client_user_3.json b/libs/wire-api/test/golden/testObject_Client_user_3.json
index fc530a2fb59..7ea430fa50e 100644
--- a/libs/wire-api/test/golden/testObject_Client_user_3.json
+++ b/libs/wire-api/test/golden/testObject_Client_user_3.json
@@ -1,12 +1 @@
-{
- "cookie": "",
- "time": "1864-05-07T00:38:22.384Z",
- "location": {
- "lat": -0.31865405026910076,
- "lon": 6.859482454480745e-2
- },
- "id": "1",
- "type": "temporary",
- "class": "legalhold",
- "label": "pi"
-}
\ No newline at end of file
+{"cookie":"","time":"1864-05-07T00:38:22.384Z","location":{"lat":-0.31865405026910076,"lon":6.859482454480745e-2},"id":"1","type":"temporary","class":"legalhold","capabilities":{"capabilities":[]},"label":"pi"}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Client_user_4.json b/libs/wire-api/test/golden/testObject_Client_user_4.json
index c18c5253002..19dab4b121f 100644
--- a/libs/wire-api/test/golden/testObject_Client_user_4.json
+++ b/libs/wire-api/test/golden/testObject_Client_user_4.json
@@ -1,12 +1 @@
-{
- "cookie": "j",
- "time": "1864-05-06T09:13:45.902Z",
- "location": {
- "lat": 0.43019316470477537,
- "lon": -2.1994844230432533
- },
- "model": "",
- "id": "3",
- "type": "permanent",
- "class": "legalhold"
-}
\ No newline at end of file
+{"cookie":"j","time":"1864-05-06T09:13:45.902Z","location":{"lat":0.43019316470477537,"lon":-2.1994844230432533},"model":"","id":"3","type":"permanent","class":"legalhold","capabilities":{"capabilities":[]}}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Client_user_5.json b/libs/wire-api/test/golden/testObject_Client_user_5.json
index d6408f63b16..63593b7b014 100644
--- a/libs/wire-api/test/golden/testObject_Client_user_5.json
+++ b/libs/wire-api/test/golden/testObject_Client_user_5.json
@@ -1,12 +1 @@
-{
- "cookie": "",
- "time": "1864-05-07T09:07:14.559Z",
- "location": {
- "lat": -1.505966289957799,
- "lon": -2.516893825541776
- },
- "model": "⌷o",
- "id": "0",
- "type": "temporary",
- "class": "desktop"
-}
\ No newline at end of file
+{"cookie":"","time":"1864-05-07T09:07:14.559Z","location":{"lat":-1.505966289957799,"lon":-2.516893825541776},"model":"⌷o","id":"0","type":"temporary","class":"desktop","capabilities":{"capabilities":[]}}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Client_user_6.json b/libs/wire-api/test/golden/testObject_Client_user_6.json
index 471c55caf4f..5dbaea9e7b8 100644
--- a/libs/wire-api/test/golden/testObject_Client_user_6.json
+++ b/libs/wire-api/test/golden/testObject_Client_user_6.json
@@ -1,12 +1 @@
-{
- "cookie": "l\u0002",
- "time": "1864-05-08T22:37:53.030Z",
- "location": {
- "lat": 0.3764380360505919,
- "lon": 1.3619562593325738
- },
- "model": "",
- "id": "4",
- "type": "permanent",
- "class": "tablet"
-}
\ No newline at end of file
+{"cookie":"l\u0002","time":"1864-05-08T22:37:53.030Z","location":{"lat":0.3764380360505919,"lon":1.3619562593325738},"model":"","id":"4","type":"permanent","class":"tablet","capabilities":{"capabilities":[]}}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Client_user_7.json b/libs/wire-api/test/golden/testObject_Client_user_7.json
index 4079b9c3938..be3515d318c 100644
--- a/libs/wire-api/test/golden/testObject_Client_user_7.json
+++ b/libs/wire-api/test/golden/testObject_Client_user_7.json
@@ -1,8 +1 @@
-{
- "time": "1864-05-07T04:35:34.201Z",
- "model": "",
- "id": "4",
- "type": "permanent",
- "class": "phone",
- "label": ""
-}
\ No newline at end of file
+{"time":"1864-05-07T04:35:34.201Z","model":"","id":"4","type":"permanent","class":"phone","capabilities":{"capabilities":[]},"label":""}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Client_user_8.json b/libs/wire-api/test/golden/testObject_Client_user_8.json
index 7f0128c8978..5ff8579f3e4 100644
--- a/libs/wire-api/test/golden/testObject_Client_user_8.json
+++ b/libs/wire-api/test/golden/testObject_Client_user_8.json
@@ -1,13 +1 @@
-{
- "cookie": "\u0015p`",
- "time": "1864-05-11T06:32:01.921Z",
- "location": {
- "lat": 0.8626148594727595,
- "lon": -1.971023301844283
- },
- "model": "",
- "id": "4",
- "type": "legalhold",
- "class": "phone",
- "label": ""
-}
\ No newline at end of file
+{"cookie":"\u0015p`","time":"1864-05-11T06:32:01.921Z","location":{"lat":0.8626148594727595,"lon":-1.971023301844283},"model":"","id":"4","type":"legalhold","class":"phone","capabilities":{"capabilities":[]},"label":""}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Client_user_9.json b/libs/wire-api/test/golden/testObject_Client_user_9.json
index 905bf6b5b4e..d636494db93 100644
--- a/libs/wire-api/test/golden/testObject_Client_user_9.json
+++ b/libs/wire-api/test/golden/testObject_Client_user_9.json
@@ -1,13 +1 @@
-{
- "cookie": "G",
- "time": "1864-05-08T03:54:56.526Z",
- "location": {
- "lat": -0.3086524641730466,
- "lon": 1.72690152811777
- },
- "model": "㌀m",
- "id": "1",
- "type": "legalhold",
- "class": "legalhold",
- "label": "v"
-}
\ No newline at end of file
+{"cookie":"G","time":"1864-05-08T03:54:56.526Z","location":{"lat":-0.3086524641730466,"lon":1.72690152811777},"model":"㌀m","id":"1","type":"legalhold","class":"legalhold","capabilities":{"capabilities":[]},"label":"v"}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Event_user_1.json b/libs/wire-api/test/golden/testObject_Event_user_1.json
index 42f2c8c7b5e..3f0251e28bc 100644
--- a/libs/wire-api/test/golden/testObject_Event_user_1.json
+++ b/libs/wire-api/test/golden/testObject_Event_user_1.json
@@ -1,7 +1,15 @@
{
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00005d81-0000-0d71-0000-1d8f00007d32"
+ },
"conversation": "00005d81-0000-0d71-0000-1d8f00007d32",
"time": "1864-05-22T09:51:07.104Z",
"data": null,
"from": "00003b8b-0000-3395-0000-076a00007830",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00003b8b-0000-3395-0000-076a00007830"
+ },
"type": "conversation.delete"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Event_user_10.json b/libs/wire-api/test/golden/testObject_Event_user_10.json
index 28740cdb73d..f99cd3585fe 100644
--- a/libs/wire-api/test/golden/testObject_Event_user_10.json
+++ b/libs/wire-api/test/golden/testObject_Event_user_10.json
@@ -1,4 +1,8 @@
{
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "000019e1-0000-1dc6-0000-68de0000246d"
+ },
"conversation": "000019e1-0000-1dc6-0000-68de0000246d",
"time": "1864-05-29T19:31:31.226Z",
"data": {
@@ -93,5 +97,9 @@
"last_event": "0.0"
},
"from": "00000457-0000-0689-0000-77a00000021c",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00000457-0000-0689-0000-77a00000021c"
+ },
"type": "conversation.create"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Event_user_11.json b/libs/wire-api/test/golden/testObject_Event_user_11.json
index c29eb5c9017..76ec1445449 100644
--- a/libs/wire-api/test/golden/testObject_Event_user_11.json
+++ b/libs/wire-api/test/golden/testObject_Event_user_11.json
@@ -1,4 +1,8 @@
{
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "000031c2-0000-108c-0000-10a500000882"
+ },
"conversation": "000031c2-0000-108c-0000-10a500000882",
"time": "1864-05-03T06:49:41.178Z",
"data": {
@@ -12,5 +16,9 @@
"target": "00000001-0000-0002-0000-000100000000"
},
"from": "00005335-0000-2983-0000-46460000082f",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00005335-0000-2983-0000-46460000082f"
+ },
"type": "conversation.member-update"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Event_user_12.json b/libs/wire-api/test/golden/testObject_Event_user_12.json
index 00e8ed095c6..67289f83ca6 100644
--- a/libs/wire-api/test/golden/testObject_Event_user_12.json
+++ b/libs/wire-api/test/golden/testObject_Event_user_12.json
@@ -1,7 +1,15 @@
{
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00007474-0000-2a7b-0000-125900006ac9"
+ },
"conversation": "00007474-0000-2a7b-0000-125900006ac9",
"time": "1864-05-23T17:16:29.326Z",
"data": null,
"from": "00000795-0000-709d-0000-11270000007a",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00000795-0000-709d-0000-11270000007a"
+ },
"type": "conversation.delete"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Event_user_13.json b/libs/wire-api/test/golden/testObject_Event_user_13.json
index b16b1049f15..aa4f5553bfe 100644
--- a/libs/wire-api/test/golden/testObject_Event_user_13.json
+++ b/libs/wire-api/test/golden/testObject_Event_user_13.json
@@ -1,4 +1,8 @@
{
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00006355-0000-5f6e-0000-592c0000680c"
+ },
"conversation": "00006355-0000-5f6e-0000-592c0000680c",
"time": "1864-05-21T03:22:42.926Z",
"data": {
@@ -8,5 +12,9 @@
"recipient": "4"
},
"from": "000029eb-0000-06f8-0000-514100000a84",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "000029eb-0000-06f8-0000-514100000a84"
+ },
"type": "conversation.otr-message-add"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Event_user_14.json b/libs/wire-api/test/golden/testObject_Event_user_14.json
index d8166bc72b6..4130d1084d8 100644
--- a/libs/wire-api/test/golden/testObject_Event_user_14.json
+++ b/libs/wire-api/test/golden/testObject_Event_user_14.json
@@ -1,9 +1,17 @@
{
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00000b98-0000-618d-0000-19e200004651"
+ },
"conversation": "00000b98-0000-618d-0000-19e200004651",
"time": "1864-05-01T11:57:35.123Z",
"data": {
"receipt_mode": -10505
},
"from": "00004bee-0000-45a0-0000-2c0300005726",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00004bee-0000-45a0-0000-2c0300005726"
+ },
"type": "conversation.receipt-mode-update"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Event_user_15.json b/libs/wire-api/test/golden/testObject_Event_user_15.json
index ab24a5ffb22..dec62fe4423 100644
--- a/libs/wire-api/test/golden/testObject_Event_user_15.json
+++ b/libs/wire-api/test/golden/testObject_Event_user_15.json
@@ -1,4 +1,8 @@
{
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00005e43-0000-3b56-0000-7c270000538c"
+ },
"conversation": "00005e43-0000-3b56-0000-7c270000538c",
"time": "1864-05-25T01:31:49.802Z",
"data": {
@@ -8,5 +12,9 @@
"recipient": "00000008-0000-0000-0000-000600000001"
},
"from": "00007f28-0000-40b1-0000-56ab0000748d",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00007f28-0000-40b1-0000-56ab0000748d"
+ },
"type": "conversation.connect-request"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Event_user_16.json b/libs/wire-api/test/golden/testObject_Event_user_16.json
index 5cda4b24a3a..e334d264c8d 100644
--- a/libs/wire-api/test/golden/testObject_Event_user_16.json
+++ b/libs/wire-api/test/golden/testObject_Event_user_16.json
@@ -1,4 +1,8 @@
{
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00004b59-0000-55d6-0000-5aad00007373"
+ },
"conversation": "00004b59-0000-55d6-0000-5aad00007373",
"time": "1864-05-24T00:49:37.413Z",
"data": {
@@ -6,5 +10,9 @@
"access_role": "activated"
},
"from": "0000211e-0000-0b37-0000-563100003a5d",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "0000211e-0000-0b37-0000-563100003a5d"
+ },
"type": "conversation.access-update"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Event_user_17.json b/libs/wire-api/test/golden/testObject_Event_user_17.json
index e3a6b34447f..cc17cded498 100644
--- a/libs/wire-api/test/golden/testObject_Event_user_17.json
+++ b/libs/wire-api/test/golden/testObject_Event_user_17.json
@@ -1,9 +1,17 @@
{
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00006ac8-0000-1342-0000-76880000021d"
+ },
"conversation": "00006ac8-0000-1342-0000-76880000021d",
"time": "1864-04-17T07:39:54.846Z",
"data": {
"status": "stopped"
},
"from": "0000145f-0000-2ce0-0000-4ca800006c72",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "0000145f-0000-2ce0-0000-4ca800006c72"
+ },
"type": "conversation.typing"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Event_user_18.json b/libs/wire-api/test/golden/testObject_Event_user_18.json
index 1507da9087f..b0a9058eb0c 100644
--- a/libs/wire-api/test/golden/testObject_Event_user_18.json
+++ b/libs/wire-api/test/golden/testObject_Event_user_18.json
@@ -1,4 +1,8 @@
{
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "0000303b-0000-23a9-0000-25de00002f80"
+ },
"conversation": "0000303b-0000-23a9-0000-25de00002f80",
"time": "1864-04-12T01:28:25.705Z",
"data": {
@@ -31,5 +35,9 @@
]
},
"from": "000043a6-0000-1627-0000-490300002017",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "000043a6-0000-1627-0000-490300002017"
+ },
"type": "conversation.member-leave"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Event_user_19.json b/libs/wire-api/test/golden/testObject_Event_user_19.json
index a7a890f5335..4cee704eddf 100644
--- a/libs/wire-api/test/golden/testObject_Event_user_19.json
+++ b/libs/wire-api/test/golden/testObject_Event_user_19.json
@@ -1,66 +1,130 @@
{
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00000838-0000-1bc6-0000-686d00003565"
+ },
"conversation": "00000838-0000-1bc6-0000-686d00003565",
"time": "1864-05-12T20:29:47.483Z",
"data": {
"users": [
{
"conversation_role": "dlkagbmicz0f95d",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000055-0000-004d-0000-005100000037"
+ },
"id": "00000055-0000-004d-0000-005100000037"
},
{
"conversation_role": "1me2in15nttjib_zx_qqx_c_mw4rw9bys2w4y78e6qhziu_85wj8vbnk6igkzld9unfvnl0oosp25i4btj6yehlq7q9em_mxsxodvq7nj_f5hqx",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000004c-0000-0051-0000-00220000005c"
+ },
"id": "0000004c-0000-0051-0000-00220000005c"
},
{
"conversation_role": "31664ffg5sx2690yu2059f7hij_m5vmb80kig21u4h3fe8uwfbshhgkdydiv_nwjm3mo4fprgxkizazcvax0vvxwcvdax",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000014-0000-0027-0000-003400000023"
+ },
"id": "00000014-0000-0027-0000-003400000023"
},
{
"conversation_role": "2e",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000001-0000-001f-0000-001500000009"
+ },
"id": "00000001-0000-001f-0000-001500000009"
},
{
"conversation_role": "f3nxp18px4kup3nrarx5wsp1o_eh69",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000005d-0000-0064-0000-00590000007d"
+ },
"id": "0000005d-0000-0064-0000-00590000007d"
},
{
"conversation_role": "fixso00nq4580z4ax9zs0sk3rej11c09rcj2ikbvnrg_io84n0eamqvwlz2icdo2u5jzzovta5j64kp0vg7e_21vs4r0hzv9",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000068-0000-007a-0000-005a0000006c"
+ },
"id": "00000068-0000-007a-0000-005a0000006c"
},
{
"conversation_role": "f9i5d2wd01ijp53en5bq8lch__jlnu8_v2xsgkctpin98byh1009f_v63",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000074-0000-0036-0000-00780000007d"
+ },
"id": "00000074-0000-0036-0000-00780000007d"
},
{
"conversation_role": "o_oqigzovv9oc2uxckvk5eofmc",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000001c-0000-000a-0000-004800000063"
+ },
"id": "0000001c-0000-000a-0000-004800000063"
},
{
"conversation_role": "5snj8s5t7nicihwspcp4sg4ny1pa1yb2s6601vjyxhksbciotoi_rvivybk1iviuz8buw",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000056-0000-0028-0000-004f00000079"
+ },
"id": "00000056-0000-0028-0000-004f00000079"
},
{
"conversation_role": "73e9u2hpffjb5ids29tbtcceg0i9v2",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000001e-0000-0054-0000-002300000053"
+ },
"id": "0000001e-0000-0054-0000-002300000053"
},
{
"conversation_role": "d2s4mc_qt1cc2rox8c9gak_qivlha7q259lsz7y5bz6dxsv8igx9r",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000004d-0000-0027-0000-007500000042"
+ },
"id": "0000004d-0000-0027-0000-007500000042"
},
{
"conversation_role": "7d84htzo4bc9250rer4r8p47ykbesgatuz8wwkoe1m2xnfljpwoi01025ti548frbvdmtykqq4pn1qsoc3s",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000050-0000-006e-0000-007000000057"
+ },
"id": "00000050-0000-006e-0000-007000000057"
},
{
"conversation_role": "v7ldb8mov4an62t6",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000007a-0000-003c-0000-005300000013"
+ },
"id": "0000007a-0000-003c-0000-005300000013"
},
{
"conversation_role": "k7uigpk1wwfc0mffoafjqf3dejctneh21zilaup19435zntvwu8kqd3l0k7s938ex2hf_n7_7dld5z604_if5z88f3u2w28qarfdcw5rkczk4jb4n",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000001-0000-0064-0000-005e00000072"
+ },
"id": "00000001-0000-0064-0000-005e00000072"
},
{
"conversation_role": "s6creybsl300lqkhu0wv_ikgattm3bd1r",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000020-0000-0012-0000-000500000036"
+ },
"id": "00000020-0000-0012-0000-000500000036"
}
],
@@ -83,5 +147,9 @@
]
},
"from": "0000114a-0000-7da8-0000-40cb00007fcf",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "0000114a-0000-7da8-0000-40cb00007fcf"
+ },
"type": "conversation.member-join"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Event_user_2.json b/libs/wire-api/test/golden/testObject_Event_user_2.json
index 705f67f17e1..7232d9989ff 100644
--- a/libs/wire-api/test/golden/testObject_Event_user_2.json
+++ b/libs/wire-api/test/golden/testObject_Event_user_2.json
@@ -1,4 +1,8 @@
{
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "0000064d-0000-7a7f-0000-5749000029e1"
+ },
"conversation": "0000064d-0000-7a7f-0000-5749000029e1",
"time": "1864-06-05T23:01:18.769Z",
"data": {
@@ -12,5 +16,9 @@
"access_role": "activated"
},
"from": "00006a88-0000-2acb-0000-6aa0000061b2",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00006a88-0000-2acb-0000-6aa0000061b2"
+ },
"type": "conversation.access-update"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Event_user_20.json b/libs/wire-api/test/golden/testObject_Event_user_20.json
index 01bba9ac96a..f44e65af7a9 100644
--- a/libs/wire-api/test/golden/testObject_Event_user_20.json
+++ b/libs/wire-api/test/golden/testObject_Event_user_20.json
@@ -1,4 +1,8 @@
{
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00000c88-0000-433f-0000-669100006374"
+ },
"conversation": "00000c88-0000-433f-0000-669100006374",
"time": "1864-04-21T23:40:54.462Z",
"data": {
@@ -32,5 +36,9 @@
]
},
"from": "00007547-0000-26d8-0000-52280000157c",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00007547-0000-26d8-0000-52280000157c"
+ },
"type": "conversation.member-leave"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Event_user_3.json b/libs/wire-api/test/golden/testObject_Event_user_3.json
index 7e33d430317..477819a2684 100644
--- a/libs/wire-api/test/golden/testObject_Event_user_3.json
+++ b/libs/wire-api/test/golden/testObject_Event_user_3.json
@@ -1,4 +1,8 @@
{
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00006f8c-0000-00d6-0000-1568000001e9"
+ },
"conversation": "00006f8c-0000-00d6-0000-1568000001e9",
"time": "1864-04-27T15:44:23.844Z",
"data": {
@@ -8,5 +12,9 @@
"recipient": "f"
},
"from": "00004b11-0000-5504-0000-55d800002188",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00004b11-0000-5504-0000-55d800002188"
+ },
"type": "conversation.otr-message-add"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Event_user_4.json b/libs/wire-api/test/golden/testObject_Event_user_4.json
index 2c396b8258f..7aea91abb15 100644
--- a/libs/wire-api/test/golden/testObject_Event_user_4.json
+++ b/libs/wire-api/test/golden/testObject_Event_user_4.json
@@ -1,7 +1,15 @@
{
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00004f04-0000-3939-0000-472d0000316b"
+ },
"conversation": "00004f04-0000-3939-0000-472d0000316b",
"time": "1864-05-12T00:59:09.200Z",
"data": null,
"from": "00007c90-0000-766a-0000-01b700002ab7",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00007c90-0000-766a-0000-01b700002ab7"
+ },
"type": "conversation.code-delete"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Event_user_5.json b/libs/wire-api/test/golden/testObject_Event_user_5.json
index d95d29ea4bd..383635ba15a 100644
--- a/libs/wire-api/test/golden/testObject_Event_user_5.json
+++ b/libs/wire-api/test/golden/testObject_Event_user_5.json
@@ -1,4 +1,8 @@
{
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00003c8c-0000-6394-0000-294b0000098b"
+ },
"conversation": "00003c8c-0000-6394-0000-294b0000098b",
"time": "1864-04-12T03:04:00.298Z",
"data": {
@@ -10,5 +14,9 @@
"otr_archived_ref": "\u0001J"
},
"from": "00002a12-0000-73e1-0000-71f700002ec9",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00002a12-0000-73e1-0000-71f700002ec9"
+ },
"type": "conversation.member-update"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Event_user_6.json b/libs/wire-api/test/golden/testObject_Event_user_6.json
index 270580352a3..aff5c1fa549 100644
--- a/libs/wire-api/test/golden/testObject_Event_user_6.json
+++ b/libs/wire-api/test/golden/testObject_Event_user_6.json
@@ -1,9 +1,17 @@
{
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00001fdb-0000-3127-0000-23ef00007183"
+ },
"conversation": "00001fdb-0000-3127-0000-23ef00007183",
"time": "1864-05-09T05:44:41.382Z",
"data": {
"message_timer": 5029817038083912
},
"from": "0000705a-0000-0b62-0000-425c000049c8",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "0000705a-0000-0b62-0000-425c000049c8"
+ },
"type": "conversation.message-timer-update"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Event_user_7.json b/libs/wire-api/test/golden/testObject_Event_user_7.json
index d5f1ea4a749..a22dea6898d 100644
--- a/libs/wire-api/test/golden/testObject_Event_user_7.json
+++ b/libs/wire-api/test/golden/testObject_Event_user_7.json
@@ -1,9 +1,17 @@
{
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00006ac1-0000-543e-0000-7c8f00000be7"
+ },
"conversation": "00006ac1-0000-543e-0000-7c8f00000be7",
"time": "1864-04-18T05:01:13.761Z",
"data": {
"status": "stopped"
},
"from": "0000355a-0000-2979-0000-083000002d5e",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "0000355a-0000-2979-0000-083000002d5e"
+ },
"type": "conversation.typing"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Event_user_8.json b/libs/wire-api/test/golden/testObject_Event_user_8.json
index 2999c3b8542..6ef386c6d8a 100644
--- a/libs/wire-api/test/golden/testObject_Event_user_8.json
+++ b/libs/wire-api/test/golden/testObject_Event_user_8.json
@@ -1,7 +1,15 @@
{
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00000892-0000-53c7-0000-0c870000027a"
+ },
"conversation": "00000892-0000-53c7-0000-0c870000027a",
"time": "1864-06-08T15:19:01.916Z",
"data": null,
"from": "000008e8-0000-43fa-0000-4dd1000034cc",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "000008e8-0000-43fa-0000-4dd1000034cc"
+ },
"type": "conversation.code-delete"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_Event_user_9.json b/libs/wire-api/test/golden/testObject_Event_user_9.json
index c417c2d4df4..b3ce97a8c3a 100644
--- a/libs/wire-api/test/golden/testObject_Event_user_9.json
+++ b/libs/wire-api/test/golden/testObject_Event_user_9.json
@@ -1,4 +1,8 @@
{
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00004847-0000-1eb9-0000-2973000039ca"
+ },
"conversation": "00004847-0000-1eb9-0000-2973000039ca",
"time": "1864-05-21T16:22:14.886Z",
"data": {
@@ -14,5 +18,9 @@
"access_role": "non_activated"
},
"from": "000044e3-0000-1c36-0000-42fd00006e01",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "000044e3-0000-1c36-0000-42fd00006e01"
+ },
"type": "conversation.access-update"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewClient_user_2.json b/libs/wire-api/test/golden/testObject_NewClient_user_2.json
index 5d1c7d86712..3554082e697 100644
--- a/libs/wire-api/test/golden/testObject_NewClient_user_2.json
+++ b/libs/wire-api/test/golden/testObject_NewClient_user_2.json
@@ -7,5 +7,8 @@
"password": "IoIC\u0008y1㕛&w>S~z豿\u0007{2Dj\u000b|Z\"\u000c*[mVkS𣚇A(2\u0010뱍\u000ei\u000f(𩌬f?q\u000e5𣱽d^nI_?H𝄻\u001af\nx\u0006dZ^N\u000ca\u0016\u001ab=SP😄aTd\u0019𭜏\u0013\u0006\u0017!𢬯o{uoN\u0018qL\u0015\u001bc=\u000b@o2\u0004𢲖\u001f5v\u0002\u001d_k,\u0013mAV>$\u001e\n⌔-ea}G`r? \u0001\u001f𠚕9\u0008rl𥶽}u𝢇@M6M𥷣𘃸𨺤|E5UdtLjQᩎ\u001e\u000b\u0011jE\u0006'~f\u000fR\u0015d}}q\u0011𤺆9𧋕\u001e𘣰\u0003𭦜\r\u001c\u001f迌㟍\u0015/\u001d掶\u0000(:n#m9x 𬲸}퓖\u001d\u0008`G#T\u0012-8\u0015䞆𠷿\tp/!\u00024C\u001a'DP'.\u00078<9\u0016\u0015Eq𩁒Ep]\u0007jZ%၊O製>\u0018\u0006w*f<\u000ejzpjY\u001f\u001a䪎\u0011\u0011\u0006|\u000e;𡇑F!f七b%t9\u0012\u000c𝤒X! 𠡿C\u001e𧨐C!冻H(/\u001dV)e\u00162\u0000#H$BAJy\u0017𧭞X𡜶\u001c\u001a~\u000c\u001b;\n<\u001df~{\u0008_",
"model": "om",
"type": "permanent",
- "prekeys": []
+ "prekeys": [],
+ "capabilities": [
+ "legalhold-implicit-consent"
+ ]
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvManaged_user_1.json b/libs/wire-api/test/golden/testObject_NewConvManaged_user_1.json
index d0d5e76b057..6236cbb2ef1 100644
--- a/libs/wire-api/test/golden/testObject_NewConvManaged_user_1.json
+++ b/libs/wire-api/test/golden/testObject_NewConvManaged_user_1.json
@@ -8,5 +8,6 @@
"teamid": "00000001-0000-0001-0000-000200000000"
},
"receipt_mode": 4,
- "message_timer": 193643728192048
+ "message_timer": 193643728192048,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvManaged_user_10.json b/libs/wire-api/test/golden/testObject_NewConvManaged_user_10.json
index 0223225ebb3..c2daf4e3a7c 100644
--- a/libs/wire-api/test/golden/testObject_NewConvManaged_user_10.json
+++ b/libs/wire-api/test/golden/testObject_NewConvManaged_user_10.json
@@ -12,5 +12,6 @@
"teamid": "00000001-0000-0001-0000-000000000001"
},
"receipt_mode": 1,
- "message_timer": 8061252799624904
+ "message_timer": 8061252799624904,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvManaged_user_11.json b/libs/wire-api/test/golden/testObject_NewConvManaged_user_11.json
index 142a6d0d1f4..6c529a22ee9 100644
--- a/libs/wire-api/test/golden/testObject_NewConvManaged_user_11.json
+++ b/libs/wire-api/test/golden/testObject_NewConvManaged_user_11.json
@@ -11,5 +11,6 @@
"teamid": "00000001-0000-0000-0000-000200000001"
},
"receipt_mode": -2,
- "message_timer": 6292627004994884
+ "message_timer": 6292627004994884,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvManaged_user_12.json b/libs/wire-api/test/golden/testObject_NewConvManaged_user_12.json
index d2504f314a5..4fcadb414c4 100644
--- a/libs/wire-api/test/golden/testObject_NewConvManaged_user_12.json
+++ b/libs/wire-api/test/golden/testObject_NewConvManaged_user_12.json
@@ -14,5 +14,6 @@
"managed": true,
"teamid": "00000000-0000-0001-0000-000100000000"
},
- "message_timer": 7043412511612101
+ "message_timer": 7043412511612101,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvManaged_user_13.json b/libs/wire-api/test/golden/testObject_NewConvManaged_user_13.json
index ae4e7f50d05..c9ef6cba2b8 100644
--- a/libs/wire-api/test/golden/testObject_NewConvManaged_user_13.json
+++ b/libs/wire-api/test/golden/testObject_NewConvManaged_user_13.json
@@ -12,5 +12,6 @@
"managed": true,
"teamid": "00000000-0000-0001-0000-000100000001"
},
- "receipt_mode": 0
+ "receipt_mode": 0,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvManaged_user_14.json b/libs/wire-api/test/golden/testObject_NewConvManaged_user_14.json
index c4f0faee5f7..7ffb20118c5 100644
--- a/libs/wire-api/test/golden/testObject_NewConvManaged_user_14.json
+++ b/libs/wire-api/test/golden/testObject_NewConvManaged_user_14.json
@@ -20,5 +20,6 @@
"managed": true,
"teamid": "00000001-0000-0001-0000-000000000000"
},
- "message_timer": 8669416711689656
+ "message_timer": 8669416711689656,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvManaged_user_15.json b/libs/wire-api/test/golden/testObject_NewConvManaged_user_15.json
index a92993fe12b..4897835141b 100644
--- a/libs/wire-api/test/golden/testObject_NewConvManaged_user_15.json
+++ b/libs/wire-api/test/golden/testObject_NewConvManaged_user_15.json
@@ -17,5 +17,6 @@
"teamid": "00000001-0000-0001-0000-000000000000"
},
"receipt_mode": 2,
- "message_timer": 1166285470102499
+ "message_timer": 1166285470102499,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvManaged_user_16.json b/libs/wire-api/test/golden/testObject_NewConvManaged_user_16.json
index 1934d39888e..01aeef6c744 100644
--- a/libs/wire-api/test/golden/testObject_NewConvManaged_user_16.json
+++ b/libs/wire-api/test/golden/testObject_NewConvManaged_user_16.json
@@ -10,5 +10,6 @@
"managed": true,
"teamid": "00000002-0000-0001-0000-000000000001"
},
- "message_timer": 4425819976591162
+ "message_timer": 4425819976591162,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvManaged_user_17.json b/libs/wire-api/test/golden/testObject_NewConvManaged_user_17.json
index d73ce51661a..b8d1bb81cf9 100644
--- a/libs/wire-api/test/golden/testObject_NewConvManaged_user_17.json
+++ b/libs/wire-api/test/golden/testObject_NewConvManaged_user_17.json
@@ -17,5 +17,6 @@
"managed": true,
"teamid": "00000000-0000-0000-0000-000000000000"
},
- "message_timer": 5065871950676797
+ "message_timer": 5065871950676797,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvManaged_user_18.json b/libs/wire-api/test/golden/testObject_NewConvManaged_user_18.json
index 1120207cbce..862a990ee99 100644
--- a/libs/wire-api/test/golden/testObject_NewConvManaged_user_18.json
+++ b/libs/wire-api/test/golden/testObject_NewConvManaged_user_18.json
@@ -8,5 +8,6 @@
"managed": true,
"teamid": "00000001-0000-0000-0000-000100000001"
},
- "receipt_mode": -1
+ "receipt_mode": -1,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvManaged_user_19.json b/libs/wire-api/test/golden/testObject_NewConvManaged_user_19.json
index 46a4e152ffc..1f29a6a2939 100644
--- a/libs/wire-api/test/golden/testObject_NewConvManaged_user_19.json
+++ b/libs/wire-api/test/golden/testObject_NewConvManaged_user_19.json
@@ -12,5 +12,6 @@
"managed": true,
"teamid": "00000001-0000-0000-0000-000100000001"
},
- "message_timer": 8428756728484885
+ "message_timer": 8428756728484885,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvManaged_user_2.json b/libs/wire-api/test/golden/testObject_NewConvManaged_user_2.json
index d186ff4b00f..3944598bbc6 100644
--- a/libs/wire-api/test/golden/testObject_NewConvManaged_user_2.json
+++ b/libs/wire-api/test/golden/testObject_NewConvManaged_user_2.json
@@ -14,5 +14,11 @@
"managed": true,
"teamid": "00000002-0000-0002-0000-000200000002"
},
- "message_timer": 5509522199847054
+ "message_timer": 5509522199847054,
+ "qualified_users": [
+ {
+ "domain": "test.example.com",
+ "id": "00000000-0000-0000-0000-000100000001"
+ }
+ ]
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvManaged_user_20.json b/libs/wire-api/test/golden/testObject_NewConvManaged_user_20.json
index 7a67aa24955..7634af079d0 100644
--- a/libs/wire-api/test/golden/testObject_NewConvManaged_user_20.json
+++ b/libs/wire-api/test/golden/testObject_NewConvManaged_user_20.json
@@ -15,5 +15,6 @@
"managed": true,
"teamid": "00000000-0000-0000-0000-000000000001"
},
- "message_timer": 6168723896440273
+ "message_timer": 6168723896440273,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvManaged_user_3.json b/libs/wire-api/test/golden/testObject_NewConvManaged_user_3.json
index 4dea92bc2ea..07019eaaeab 100644
--- a/libs/wire-api/test/golden/testObject_NewConvManaged_user_3.json
+++ b/libs/wire-api/test/golden/testObject_NewConvManaged_user_3.json
@@ -14,5 +14,11 @@
"teamid": "00000001-0000-0001-0000-000000000000"
},
"receipt_mode": 2,
- "message_timer": 582808797322573
+ "message_timer": 582808797322573,
+ "qualified_users": [
+ {
+ "domain": "test.example.com",
+ "id": "00000000-0000-0000-0000-000100000001"
+ }
+ ]
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvManaged_user_4.json b/libs/wire-api/test/golden/testObject_NewConvManaged_user_4.json
index 1b1f6259c82..70d83c053d2 100644
--- a/libs/wire-api/test/golden/testObject_NewConvManaged_user_4.json
+++ b/libs/wire-api/test/golden/testObject_NewConvManaged_user_4.json
@@ -14,5 +14,6 @@
"team": {
"managed": true,
"teamid": "00000001-0000-0001-0000-000100000002"
- }
+ },
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvManaged_user_5.json b/libs/wire-api/test/golden/testObject_NewConvManaged_user_5.json
index 0bb397139b5..c54f6a917f6 100644
--- a/libs/wire-api/test/golden/testObject_NewConvManaged_user_5.json
+++ b/libs/wire-api/test/golden/testObject_NewConvManaged_user_5.json
@@ -13,5 +13,6 @@
"teamid": "00000001-0000-0000-0000-000100000000"
},
"receipt_mode": -1,
- "message_timer": 1570858821505994
+ "message_timer": 1570858821505994,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvManaged_user_6.json b/libs/wire-api/test/golden/testObject_NewConvManaged_user_6.json
index 1e64c83bd3b..9663fd804a0 100644
--- a/libs/wire-api/test/golden/testObject_NewConvManaged_user_6.json
+++ b/libs/wire-api/test/golden/testObject_NewConvManaged_user_6.json
@@ -15,5 +15,6 @@
"managed": true,
"teamid": "00000000-0000-0000-0000-000000000001"
},
- "message_timer": 6614365418177275
+ "message_timer": 6614365418177275,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvManaged_user_7.json b/libs/wire-api/test/golden/testObject_NewConvManaged_user_7.json
index 085917605a6..39582d207b7 100644
--- a/libs/wire-api/test/golden/testObject_NewConvManaged_user_7.json
+++ b/libs/wire-api/test/golden/testObject_NewConvManaged_user_7.json
@@ -12,5 +12,6 @@
"teamid": "00000001-0000-0001-0000-000100000000"
},
"receipt_mode": 0,
- "message_timer": 7417375067718994
+ "message_timer": 7417375067718994,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvManaged_user_8.json b/libs/wire-api/test/golden/testObject_NewConvManaged_user_8.json
index 5f7c0d496cc..ae8c6078685 100644
--- a/libs/wire-api/test/golden/testObject_NewConvManaged_user_8.json
+++ b/libs/wire-api/test/golden/testObject_NewConvManaged_user_8.json
@@ -11,5 +11,6 @@
"managed": true,
"teamid": "00000001-0000-0000-0000-000100000001"
},
- "receipt_mode": 2
+ "receipt_mode": 2,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvManaged_user_9.json b/libs/wire-api/test/golden/testObject_NewConvManaged_user_9.json
index cc918ca9b6c..9edd2c8c3d3 100644
--- a/libs/wire-api/test/golden/testObject_NewConvManaged_user_9.json
+++ b/libs/wire-api/test/golden/testObject_NewConvManaged_user_9.json
@@ -12,5 +12,6 @@
"teamid": "00000001-0000-0000-0000-000000000000"
},
"receipt_mode": 1,
- "message_timer": 2550845209410146
+ "message_timer": 2550845209410146,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_1.json b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_1.json
index bb91c95bdc9..edf0282f535 100644
--- a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_1.json
+++ b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_1.json
@@ -14,5 +14,6 @@
"teamid": "00000000-0000-0001-0000-000000000000"
},
"receipt_mode": 1,
- "message_timer": 3320987366258987
+ "message_timer": 3320987366258987,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_10.json b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_10.json
index 053072f59a0..e35128c1deb 100644
--- a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_10.json
+++ b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_10.json
@@ -11,5 +11,6 @@
"conversation_role": "30mnzwj79jo9ear300qs4k_x2262nyaqxt9qga1_zaqmto43q2935t4dzaan_qnlstgjix7efmqfljkpww2lz",
"name": "",
"receipt_mode": 2,
- "message_timer": 5041503034744095
+ "message_timer": 5041503034744095,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_11.json b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_11.json
index d8208e917eb..69680bdfa2e 100644
--- a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_11.json
+++ b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_11.json
@@ -10,5 +10,6 @@
"teamid": "00000000-0000-0000-0000-000000000000"
},
"receipt_mode": 1,
- "message_timer": 6019134025424754
+ "message_timer": 6019134025424754,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_12.json b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_12.json
index f4b4ae0d413..844cd30e634 100644
--- a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_12.json
+++ b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_12.json
@@ -3,5 +3,6 @@
"users": [],
"conversation_role": "wqhaeljk9zpp5nmspwl",
"name": ">+",
- "receipt_mode": 1
+ "receipt_mode": 1,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_13.json b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_13.json
index cb760bbf05d..04278d46724 100644
--- a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_13.json
+++ b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_13.json
@@ -13,5 +13,6 @@
"teamid": "00000000-0000-0000-0000-000000000001"
},
"receipt_mode": -2,
- "message_timer": 211460552735402
+ "message_timer": 211460552735402,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_14.json b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_14.json
index cbf228dbd47..6690d68083a 100644
--- a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_14.json
+++ b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_14.json
@@ -10,5 +10,6 @@
"conversation_role": "fga_hfm9uzn_5z883y6r_kumb",
"name": "",
"receipt_mode": -2,
- "message_timer": 854777662274030
+ "message_timer": 854777662274030,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_15.json b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_15.json
index 181966845c5..6fda451288c 100644
--- a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_15.json
+++ b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_15.json
@@ -14,5 +14,6 @@
"teamid": "00000001-0000-0001-0000-000200000002"
},
"receipt_mode": 1,
- "message_timer": 4005602882980532
+ "message_timer": 4005602882980532,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_16.json b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_16.json
index 1d351204ce8..4b11912e073 100644
--- a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_16.json
+++ b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_16.json
@@ -11,5 +11,6 @@
"managed": false,
"teamid": "00000000-0000-0001-0000-000100000000"
},
- "receipt_mode": 1
+ "receipt_mode": 1,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_17.json b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_17.json
index 45a12afa84f..cfab3503e9b 100644
--- a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_17.json
+++ b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_17.json
@@ -22,5 +22,6 @@
"teamid": "00000000-0000-0001-0000-000100000001"
},
"receipt_mode": 0,
- "message_timer": 880163555151907
+ "message_timer": 880163555151907,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_18.json b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_18.json
index c245ea066da..43f14dfd5af 100644
--- a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_18.json
+++ b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_18.json
@@ -12,5 +12,6 @@
"conversation_role": "ugehcdyu_ob9_woawlths95ez8cgtb6wjqypp7vbjaooiczerb5zpc6srxszgkrdu8l24ygz_",
"name": "sd\u0006",
"receipt_mode": -2,
- "message_timer": 3120553871655858
+ "message_timer": 3120553871655858,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_19.json b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_19.json
index f3d6cf50c57..d9086140b52 100644
--- a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_19.json
+++ b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_19.json
@@ -9,5 +9,6 @@
"conversation_role": "xik7vc3wp82gw4r934rad_bhmf2orany3qgu_tx9huwfrlxy8m0id71x20uddebps30zdahe_ffcxxhc",
"name": "Cu\u0011",
"receipt_mode": -1,
- "message_timer": 864918593306344
+ "message_timer": 864918593306344,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_2.json b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_2.json
index b5fe627bd56..eefa9c41791 100644
--- a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_2.json
+++ b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_2.json
@@ -9,5 +9,11 @@
"teamid": "00000000-0000-0001-0000-000000000001"
},
"receipt_mode": -1,
- "message_timer": 2406292360203739
+ "message_timer": 2406292360203739,
+ "qualified_users": [
+ {
+ "domain": "testdomain.example.com",
+ "id": "00000000-0000-0000-0000-000100000001"
+ }
+ ]
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_20.json b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_20.json
index 7974cdee0a4..f6acbbccc1d 100644
--- a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_20.json
+++ b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_20.json
@@ -6,5 +6,6 @@
"conversation_role": "udhi2sbf7tzyshrh",
"name": "\u000f",
"receipt_mode": -1,
- "message_timer": 3641984282941906
+ "message_timer": 3641984282941906,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_3.json b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_3.json
index 0448c8d6ae4..b695ab354d0 100644
--- a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_3.json
+++ b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_3.json
@@ -13,5 +13,6 @@
"teamid": "00000000-0000-0001-0000-000000000001"
},
"receipt_mode": 0,
- "message_timer": 6764297310186120
+ "message_timer": 6764297310186120,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_4.json b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_4.json
index 25f2a9d5f99..4eb17cc8734 100644
--- a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_4.json
+++ b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_4.json
@@ -9,5 +9,6 @@
"managed": false,
"teamid": "00000000-0000-0001-0000-000000000000"
},
- "receipt_mode": -2
+ "receipt_mode": -2,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_5.json b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_5.json
index 333b08ad340..8c3300d8268 100644
--- a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_5.json
+++ b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_5.json
@@ -13,5 +13,6 @@
"team": {
"managed": false,
"teamid": "00000001-0000-0001-0000-000100000000"
- }
+ },
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_6.json b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_6.json
index f416efa308e..9ad3210f5e9 100644
--- a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_6.json
+++ b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_6.json
@@ -7,5 +7,6 @@
"conversation_role": "5zlsxm_95e5j1lk04d6rka_1svnnk65pov7tqs",
"name": "`3",
"receipt_mode": 2,
- "message_timer": 3993332602038581
+ "message_timer": 3993332602038581,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_7.json b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_7.json
index ac18846f376..7864d68fd0e 100644
--- a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_7.json
+++ b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_7.json
@@ -11,5 +11,6 @@
"teamid": "00000001-0000-0002-0000-000200000000"
},
"receipt_mode": -3,
- "message_timer": 5300164242243961
+ "message_timer": 5300164242243961,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_8.json b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_8.json
index 1767aace79f..adcf1b3067a 100644
--- a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_8.json
+++ b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_8.json
@@ -13,5 +13,6 @@
"teamid": "00000000-0000-0001-0000-000100000001"
},
"receipt_mode": -1,
- "message_timer": 5317293791913533
+ "message_timer": 5317293791913533,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_9.json b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_9.json
index 0f13c872607..52bf3a61ab4 100644
--- a/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_9.json
+++ b/libs/wire-api/test/golden/testObject_NewConvUnmanaged_user_9.json
@@ -11,5 +11,6 @@
],
"conversation_role": "n8cjajmyhnw3hqv8sohb8674nwnpsv7g57i2hjhexg9tww",
"name": "L",
- "message_timer": 7179840365742041
+ "message_timer": 7179840365742041,
+ "qualified_users": []
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_1.json b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_1.json
index ac41269ce91..cd55aa0263c 100644
--- a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_1.json
+++ b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_1.json
@@ -1,5 +1,9 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00003ab8-0000-0cff-0000-427f000000df"
+ },
"conversation": "00003ab8-0000-0cff-0000-427f000000df",
"time": "1864-05-07T01:13:35.741Z",
"data": {
@@ -37,6 +41,10 @@
]
},
"from": "00004166-0000-1e32-0000-52cb0000428d",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00004166-0000-1e32-0000-52cb0000428d"
+ },
"type": "conversation.member-leave"
}
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_10.json b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_10.json
index c6d25b53d83..f36ebe29fd3 100644
--- a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_10.json
+++ b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_10.json
@@ -1,11 +1,19 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00005788-0000-327b-0000-7ef80000017e"
+ },
"conversation": "00005788-0000-327b-0000-7ef80000017e",
"time": "1864-04-11T02:49:27.442Z",
"data": {
"status": "started"
},
"from": "0000588d-0000-6704-0000-153f00001692",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "0000588d-0000-6704-0000-153f00001692"
+ },
"type": "conversation.typing"
}
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_11.json b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_11.json
index a9afe81529b..02837917937 100644
--- a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_11.json
+++ b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_11.json
@@ -1,11 +1,19 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00001db4-0000-575c-0000-5b9200002c33"
+ },
"conversation": "00001db4-0000-575c-0000-5b9200002c33",
"time": "1864-05-25T16:08:53.052Z",
"data": {
"name": "\u0017𦙤𧉁>P2L𫐫xT𥄢0)y\u0016"
},
"from": "000009b3-0000-04dc-0000-310100002b5f",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "000009b3-0000-04dc-0000-310100002b5f"
+ },
"type": "conversation.rename"
}
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_12.json b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_12.json
index 07cb6e13e91..cd6295e633f 100644
--- a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_12.json
+++ b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_12.json
@@ -1,5 +1,9 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00004c29-0000-0214-0000-1d7300001cdc"
+ },
"conversation": "00004c29-0000-0214-0000-1d7300001cdc",
"time": "1864-04-23T00:31:51.842Z",
"data": {
@@ -9,6 +13,10 @@
"recipient": "00000002-0000-0005-0000-000100000007"
},
"from": "00003ba8-0000-448c-0000-769e00004cdf",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00003ba8-0000-448c-0000-769e00004cdf"
+ },
"type": "conversation.connect-request"
}
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_13.json b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_13.json
index fa698079b81..4305fa47343 100644
--- a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_13.json
+++ b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_13.json
@@ -1,11 +1,19 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "000062a2-0000-46ad-0000-0f8100005bbe"
+ },
"conversation": "000062a2-0000-46ad-0000-0f8100005bbe",
"time": "1864-05-06T22:47:56.147Z",
"data": {
"message_timer": null
},
"from": "000065a2-0000-1aaa-0000-311000003d69",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "000065a2-0000-1aaa-0000-311000003d69"
+ },
"type": "conversation.message-timer-update"
}
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_14.json b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_14.json
index 6de36e5b6ee..6d1d6aa5dec 100644
--- a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_14.json
+++ b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_14.json
@@ -1,5 +1,9 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "0000060f-0000-6d7d-0000-33a800005d07"
+ },
"conversation": "0000060f-0000-6d7d-0000-33a800005d07",
"time": "1864-04-21T02:44:02.145Z",
"data": {
@@ -7,6 +11,10 @@
"code": "z3MqXFfMRMlMeTim7025"
},
"from": "00005c4c-0000-226a-0000-04b70000100a",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00005c4c-0000-226a-0000-04b70000100a"
+ },
"type": "conversation.code-update"
}
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_15.json b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_15.json
index 6698a2b1ed3..c4c6da8c5a1 100644
--- a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_15.json
+++ b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_15.json
@@ -1,11 +1,19 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00006421-0000-0363-0000-192100003398"
+ },
"conversation": "00006421-0000-0363-0000-192100003398",
"time": "1864-04-30T23:29:02.240Z",
"data": {
"message_timer": 8977358108702637
},
"from": "000005cd-0000-7897-0000-1fc700002d35",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "000005cd-0000-7897-0000-1fc700002d35"
+ },
"type": "conversation.message-timer-update"
}
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_16.json b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_16.json
index 765395a35ec..4e8b454c83d 100644
--- a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_16.json
+++ b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_16.json
@@ -1,5 +1,9 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "0000067f-0000-0d9b-0000-039f0000033f"
+ },
"conversation": "0000067f-0000-0d9b-0000-039f0000033f",
"time": "1864-04-27T19:16:49.866Z",
"data": {
@@ -8,6 +12,10 @@
"code": "=X8_OGM09"
},
"from": "0000030b-0000-5943-0000-6cd900006eae",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "0000030b-0000-5943-0000-6cd900006eae"
+ },
"type": "conversation.code-update"
}
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_17.json b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_17.json
index 16b87085bf6..ec8523d2c21 100644
--- a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_17.json
+++ b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_17.json
@@ -1,11 +1,19 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00005994-0000-5c94-0000-519300002727"
+ },
"conversation": "00005994-0000-5c94-0000-519300002727",
"time": "1864-04-24T18:38:55.053Z",
"data": {
"message_timer": 3685837512701220
},
"from": "00003ddd-0000-21a2-0000-6a54000023c3",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00003ddd-0000-21a2-0000-6a54000023c3"
+ },
"type": "conversation.message-timer-update"
}
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_18.json b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_18.json
index 980f6239ea5..eee78a94ad0 100644
--- a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_18.json
+++ b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_18.json
@@ -1,5 +1,9 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "000005bf-0000-3fdd-0000-089a0000544e"
+ },
"conversation": "000005bf-0000-3fdd-0000-089a0000544e",
"time": "1864-05-05T05:34:43.386Z",
"data": {
@@ -9,6 +13,10 @@
"recipient": "00000007-0000-0005-0000-000400000002"
},
"from": "00003c0a-0000-3d64-0000-7f74000011e9",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00003c0a-0000-3d64-0000-7f74000011e9"
+ },
"type": "conversation.connect-request"
}
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_19.json b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_19.json
index c7afb1917d1..386359bd8e7 100644
--- a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_19.json
+++ b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_19.json
@@ -1,9 +1,17 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00000c59-0000-51c7-0000-1b6500001384"
+ },
"conversation": "00000c59-0000-51c7-0000-1b6500001384",
"time": "1864-04-19T14:51:39.037Z",
"data": null,
"from": "00003046-0000-14df-0000-5a5900005ef2",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00003046-0000-14df-0000-5a5900005ef2"
+ },
"type": "conversation.code-delete"
}
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_2.json b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_2.json
index 1d322f07400..1907be2fdb9 100644
--- a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_2.json
+++ b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_2.json
@@ -1,9 +1,17 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00005a06-0000-10ab-0000-4999000058de"
+ },
"conversation": "00005a06-0000-10ab-0000-4999000058de",
"time": "1864-04-23T16:56:18.982Z",
"data": null,
"from": "00004247-0000-0560-0000-07df00005850",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00004247-0000-0560-0000-07df00005850"
+ },
"type": "conversation.delete"
}
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_20.json b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_20.json
index b2f9ff67abe..1cca0da5286 100644
--- a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_20.json
+++ b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_20.json
@@ -1,11 +1,19 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00004e98-0000-2ec5-0000-31870000098c"
+ },
"conversation": "00004e98-0000-2ec5-0000-31870000098c",
"time": "1864-05-18T03:54:11.412Z",
"data": {
"message_timer": 5776200192005000
},
"from": "00006cb0-0000-6547-0000-1fe500000270",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00006cb0-0000-6547-0000-1fe500000270"
+ },
"type": "conversation.message-timer-update"
}
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_3.json b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_3.json
index 509b27457b1..3b3ac3350ea 100644
--- a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_3.json
+++ b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_3.json
@@ -1,87 +1,171 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "000031b6-0000-7f2c-0000-22ca000012a0"
+ },
"conversation": "000031b6-0000-7f2c-0000-22ca000012a0",
"time": "1864-04-23T02:07:23.620Z",
"data": {
"users": [
{
"conversation_role": "3jqe4rv30oxjs05p0vjx_gv",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000042-0000-0046-0000-005e0000001f"
+ },
"id": "00000042-0000-0046-0000-005e0000001f"
},
{
"conversation_role": "gv66owx6jn8",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000073-0000-003c-0000-005800000069"
+ },
"id": "00000073-0000-003c-0000-005800000069"
},
{
"conversation_role": "zx5yjj62r6x5vzvdekehjc6syfkollz3j5ztxjsu1ffrjvolkynevvykqe6dyyntx3t4p7ph_axwmb_9puw2h2i5qrnvkuwx1a7d23ln9q30h_vulfs1x8iiya",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000003a-0000-0056-0000-000e00000038"
+ },
"id": "0000003a-0000-0056-0000-000e00000038"
},
{
"conversation_role": "_6hbn84l_4xly84ic0hrz_m4unx_i2_5sfotmu2xjmylyly_qilavdw54n1reep",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000007c-0000-0075-0000-006500000036"
+ },
"id": "0000007c-0000-0075-0000-006500000036"
},
{
"conversation_role": "u8r_c9n84lvf4v9i8c6tzre_e3jhp327b2vvubky8_25tf6x6cszt770uuuikdpofyu5oa7lyd",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000031-0000-0004-0000-005e00000060"
+ },
"id": "00000031-0000-0004-0000-005e00000060"
},
{
"conversation_role": "4agujelz62r_o96qfxja1h60hqmsbuowdhmqb1zvrlhtru6b66vl1lu5oc1",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000007b-0000-0019-0000-002a00000000"
+ },
"id": "0000007b-0000-0019-0000-002a00000000"
},
{
"conversation_role": "6o_85q3e0hn13mkqzstg29b3r29ezb52cl6a_1hhzpx1wtdkav8z8nhc8uk5jj3wsp16rn0wx0dbj9rqt",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000000c-0000-0069-0000-006600000032"
+ },
"id": "0000000c-0000-0069-0000-006600000032"
},
{
"conversation_role": "ii7eljki45zqe819xzx16tkvbgb85",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000001d-0000-0006-0000-00540000005a"
+ },
"id": "0000001d-0000-0006-0000-00540000005a"
},
{
"conversation_role": "8fg3lg3rtnjamcshonl6ailheepmslbc_c3vgdhofs2hwbr84duunkatfkotiq246euejqre_sa4ygly",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000007f-0000-005d-0000-000700000035"
+ },
"id": "0000007f-0000-005d-0000-000700000035"
},
{
"conversation_role": "5moz9hri8wj07ilkxfcsubwzelf8bkv0vpyssxthz7nnwbthym1ux33bn682ddcbv91aq7oquc9osjow75iu75kjp0prd2zam_o_zixgv3",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000006b-0000-005e-0000-003d0000007c"
+ },
"id": "0000006b-0000-005e-0000-003d0000007c"
},
{
"conversation_role": "_xq9rxj1fopahja5o9av3g18y4ko17fzdjunr84k0_txycx3sd1sqn2k5_usv0l_007wdzjrnxcss4b32w4c1qe",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000005e-0000-000e-0000-00300000005f"
+ },
"id": "0000005e-0000-000e-0000-00300000005f"
},
{
"conversation_role": "h0q7fe607q9oaiw53dbfunmrlposh47fvaoe5mfg8rth7dzl8r0y759kclqbbqzt7zlbu090lkdberm0u78tb",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000060-0000-0000-0000-001e0000003b"
+ },
"id": "00000060-0000-0000-0000-001e0000003b"
},
{
"conversation_role": "rw50gu92raxvq87hqpf7r_xyl",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000030-0000-000e-0000-006f0000001d"
+ },
"id": "00000030-0000-000e-0000-006f0000001d"
},
{
"conversation_role": "5bizt8d567yjavituolq2unxfh0qyih7_9dep7cpix5bucbevifs2m0",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000003f-0000-005e-0000-003200000062"
+ },
"id": "0000003f-0000-005e-0000-003200000062"
},
{
"conversation_role": "1kit803b528tmtyvlkespy",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000002e-0000-007d-0000-005e0000004d"
+ },
"id": "0000002e-0000-007d-0000-005e0000004d"
},
{
"conversation_role": "74l03am2b",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000007c-0000-0013-0000-007100000049"
+ },
"id": "0000007c-0000-0013-0000-007100000049"
},
{
"conversation_role": "8ghe34e3xwi0i1e7cfe8ivltslpzuf15xadc7x5741tzeh1ne_v3m_xzjouowchqe5ubn0jptjorvxoksxwqowgp7oey9ptzpe2cegkplw3445q2z390sf1zy_09ngm",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000012-0000-0058-0000-00500000004d"
+ },
"id": "00000012-0000-0058-0000-00500000004d"
},
{
"conversation_role": "ui1_axn4co_y0u6a8yrmwsam6zar72jdpdorz8xyvxa1_gfd50r4gu47detfx0rgm6s9iqy2",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000060-0000-007f-0000-003c0000001d"
+ },
"id": "00000060-0000-007f-0000-003c0000001d"
},
{
"conversation_role": "32y4b84gygtg3xscfds0vu69bbsir8cbfh0_gmnh6hnbdr6md8807tuoi8ijtsfr2bkfd8d1vlacwytk55gr__t9f48uyd9p1fz07j20",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000034-0000-005a-0000-003600000063"
+ },
"id": "00000034-0000-005a-0000-003600000063"
},
{
"conversation_role": "wf0v8gr2oqqdm",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000006a-0000-007f-0000-006700000068"
+ },
"id": "0000006a-0000-007f-0000-006700000068"
}
],
@@ -109,6 +193,10 @@
]
},
"from": "00005a35-0000-3751-0000-76fe000044c2",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00005a35-0000-3751-0000-76fe000044c2"
+ },
"type": "conversation.member-join"
}
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_4.json b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_4.json
index c43ee0a32bb..cae4823aa42 100644
--- a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_4.json
+++ b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_4.json
@@ -1,5 +1,9 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "000057d8-0000-4ce9-0000-2a9a00001ced"
+ },
"conversation": "000057d8-0000-4ce9-0000-2a9a00001ced",
"time": "1864-05-21T00:12:51.490Z",
"data": {
@@ -7,6 +11,10 @@
"access_role": "activated"
},
"from": "00005b30-0000-0805-0000-116700000485",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00005b30-0000-0805-0000-116700000485"
+ },
"type": "conversation.access-update"
}
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_5.json b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_5.json
index 9b09ee4e5e1..333906d175f 100644
--- a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_5.json
+++ b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_5.json
@@ -1,5 +1,9 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00004615-0000-2e80-0000-552b0000353c"
+ },
"conversation": "00004615-0000-2e80-0000-552b0000353c",
"time": "1864-04-14T01:56:55.057Z",
"data": {
@@ -11,6 +15,10 @@
"access_role": "activated"
},
"from": "0000134e-0000-6a75-0000-470a00006537",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "0000134e-0000-6a75-0000-470a00006537"
+ },
"type": "conversation.access-update"
}
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_6.json b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_6.json
index 4238355db2f..89ed317e3ab 100644
--- a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_6.json
+++ b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_6.json
@@ -1,123 +1,243 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00002aa8-0000-7a99-0000-660700000bd3"
+ },
"conversation": "00002aa8-0000-7a99-0000-660700000bd3",
"time": "1864-05-31T11:11:10.792Z",
"data": {
"users": [
{
"conversation_role": "htshpkwocsefoqvjbzonewymi1zn8fpmdi1o8bwmm7fj161iortxvrz23lrjzabdmh6a55bb8cvq09xv6rq4qdtff95hkuqw4u8tj5ez9xx9cd7pvc_r67s2vw4m",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000041-0000-004b-0000-002300000030"
+ },
"id": "00000041-0000-004b-0000-002300000030"
},
{
"conversation_role": "j2dtw20p_p7_v96xvpsjwe9ww3eyi4zdq8xx2_cabuv0w21u_vz5l09abprf1hue25srgwrlgeszd1ce3mtgz5w",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000055-0000-0065-0000-005000000065"
+ },
"id": "00000055-0000-0065-0000-005000000065"
},
{
"conversation_role": "o7hm7tvk1opilxu1kc5chxj25scof183t5mdwhdkj0zjg7re3vbt5g8988z6gyu4p8sspu8fto0sko9e_m8pzk54zzvwz7vod927_jjcp3wg5jj9n2egwvi8",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000000e-0000-0042-0000-00580000000a"
+ },
"id": "0000000e-0000-0042-0000-00580000000a"
},
{
"conversation_role": "kf14jpkab__n0g0ssfw21_3q52t2op841s0zl8edy11acgb218rr4nmkodozdim",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000006b-0000-0080-0000-00360000000c"
+ },
"id": "0000006b-0000-0080-0000-00360000000c"
},
{
"conversation_role": "06i5vil75hof_mqn8_7cuglrizks",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000031-0000-0006-0000-003f00000069"
+ },
"id": "00000031-0000-0006-0000-003f00000069"
},
{
"conversation_role": "cux_igluvokgr7z7ikcqcmm9dhskcimfufmsxwb11vfv",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000006f-0000-0026-0000-006000000045"
+ },
"id": "0000006f-0000-0026-0000-006000000045"
},
{
"conversation_role": "es0p",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000002a-0000-0080-0000-003e00000014"
+ },
"id": "0000002a-0000-0080-0000-003e00000014"
},
{
"conversation_role": "w28vr_ps429op3rmp3sil1wogmfgf1dsxmmsx2u5smde8srbfb11opw0a_b5z9ywbu9q0yivoz2n70m808m6f1vtvcr6oeh05c20va1jh299hk6q950",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000001d-0000-006f-0000-003e0000003d"
+ },
"id": "0000001d-0000-006f-0000-003e0000003d"
},
{
"conversation_role": "4pgdip19fs0",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000004c-0000-0065-0000-007d00000026"
+ },
"id": "0000004c-0000-0065-0000-007d00000026"
},
{
"conversation_role": "x76ykqupchbjeozez7aqxynobvjd38xuqb",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000028-0000-005b-0000-007d00000042"
+ },
"id": "00000028-0000-005b-0000-007d00000042"
},
{
"conversation_role": "fsttup8l4pwse5n72k34u_swxpalpgzl4gjnko0l7c3gxmu0x6l4nzbyzdcaxstr2iiuxb061f9",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000011-0000-0031-0000-001e00000055"
+ },
"id": "00000011-0000-0031-0000-001e00000055"
},
{
"conversation_role": "73c37ry1xfsx",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000039-0000-003b-0000-002200000013"
+ },
"id": "00000039-0000-003b-0000-002200000013"
},
{
"conversation_role": "51a4e2v57yge9xa_cc6mg67bix0exndp7swn_dppzuk8n5i19xsqaoqlkyv_x2hhv8h4uzkng185o5y_77189zvwk_y8sy1ynp5y8vo0e5p__kwlcl0yztuvtiyyr1",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000007c-0000-0065-0000-001b00000046"
+ },
"id": "0000007c-0000-0065-0000-001b00000046"
},
{
"conversation_role": "3qy1onol9hu2g4hql7ak8gyleg9a2dh0poq72b8opgm3140xjmrvlj0jtovjt3fpbar4x1i08lzdqndo7nhtczrrp9dahulq9fbuhfdrhu7n7kl6tkvu",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000034-0000-004c-0000-001c0000007c"
+ },
"id": "00000034-0000-004c-0000-001c0000007c"
},
{
"conversation_role": "lc4kukb759glnd3j1a5cd141a7a0h8pze2c78n8x3h_9mzn7v8jtfpsgqrvt9lca6l5f8oqk3yplig1ccl8",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000004-0000-0028-0000-002300000045"
+ },
"id": "00000004-0000-0028-0000-002300000045"
},
{
"conversation_role": "pfirchcrh2lo5pq1msq2x93tawq4v37onjphe9fcssiwfdpysse0dvk3ehupya4axtiq6ewmsjjj9xsaimlk0l70ovinyo5zmgil24ckv_fd2v_h4fx9i2s",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000000f-0000-007d-0000-00650000006b"
+ },
"id": "0000000f-0000-007d-0000-00650000006b"
},
{
"conversation_role": "2130v11uf_bzjod2p35u_vhotitn",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000006c-0000-0028-0000-004000000067"
+ },
"id": "0000006c-0000-0028-0000-004000000067"
},
{
"conversation_role": "6idgmk_1d_g5ii1sfpfcrenr8m2afbe2d71llw8xrlzdhxw_g7vn3foj5_abaul9j71_",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000059-0000-007e-0000-00580000006c"
+ },
"id": "00000059-0000-007e-0000-00580000006c"
},
{
"conversation_role": "c9ycux2q_6sj1hecc_cvkz6aupdm4g5rc3gzyw9cnd0wqd0miltcb1i0q6tietu0w7khbhg8fx3z600fgsr2m3rj0mxs1pqwblnhazp1f23t",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000007f-0000-0057-0000-004c00000005"
+ },
"id": "0000007f-0000-0057-0000-004c00000005"
},
{
"conversation_role": "57guddz98hnzetk8xjme1h_gtmczis9jv3xt73rtjgz6jsentre2s7d2",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000004a-0000-000b-0000-001000000004"
+ },
"id": "0000004a-0000-000b-0000-001000000004"
},
{
"conversation_role": "x0qcthwpdmzimnfqh4rd4sf",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000000a-0000-0039-0000-005c00000048"
+ },
"id": "0000000a-0000-0039-0000-005c00000048"
},
{
"conversation_role": "xdobrq683oi0lbxoy9ociqkouclsen5wu8suhbj75co521ipa89bnc7nh3y41fg58bxlet5u0wg94ueejw05iu15zr1kno_oxiqlhx9s9i9zd8ksyb4",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000006a-0000-000d-0000-006300000074"
+ },
"id": "0000006a-0000-000d-0000-006300000074"
},
{
"conversation_role": "9ble9wkz5sx4fof474zgb",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000003-0000-0078-0000-00310000002b"
+ },
"id": "00000003-0000-0078-0000-00310000002b"
},
{
"conversation_role": "6x00nd8of9_prpikunwo7292vzgp6qivsia735dns1s395syckletc2smrzxezrsn1hgjjvenm",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000006-0000-0042-0000-007e00000013"
+ },
"id": "00000006-0000-0042-0000-007e00000013"
},
{
"conversation_role": "dd2lcxld259xsjsqz2h130ksyeixe21s87mhwa7tas1k_ttqefg66ga13x7ixlfuuiaj5p8i16nn6pf3sbn25p8s4ld9virn3tf",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000002d-0000-0043-0000-004f00000078"
+ },
"id": "0000002d-0000-0043-0000-004f00000078"
},
{
"conversation_role": "jiyr52auzomq5ui457z209fcszalvj_wy09_zgc05pfp9x304nwxni",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000015-0000-0069-0000-005400000018"
+ },
"id": "00000015-0000-0069-0000-005400000018"
},
{
"conversation_role": "ldesfdsha0z3olxjyjkijtud5z2ns5oxb5h1vbbamtgymlnmjg4ybed_tfhvntcdr1h78ihk5ztwd27vtiy",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000064-0000-007d-0000-006b00000055"
+ },
"id": "00000064-0000-007d-0000-006b00000055"
},
{
"conversation_role": "hcfut6_dj",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000006d-0000-007a-0000-007a00000017"
+ },
"id": "0000006d-0000-007a-0000-007a00000017"
},
{
"conversation_role": "q5_32a257neednc3",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000005-0000-0065-0000-002600000007"
+ },
"id": "00000005-0000-0065-0000-002600000007"
}
],
@@ -154,6 +274,10 @@
]
},
"from": "000036f7-0000-6d15-0000-0ff200006a4c",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "000036f7-0000-6d15-0000-0ff200006a4c"
+ },
"type": "conversation.member-join"
}
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_7.json b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_7.json
index e21de7f128d..088e1c2dac3 100644
--- a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_7.json
+++ b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_7.json
@@ -1,5 +1,9 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "00006a93-0000-005c-0000-361e00000180"
+ },
"conversation": "00006a93-0000-005c-0000-361e00000180",
"time": "1864-04-25T18:08:10.735Z",
"data": {
@@ -8,6 +12,10 @@
"recipient": "1c"
},
"from": "00007bb6-0000-07cc-0000-687c00002703",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00007bb6-0000-07cc-0000-687c00002703"
+ },
"type": "conversation.otr-message-add"
}
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_8.json b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_8.json
index c3f20d636cc..1788d3288ed 100644
--- a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_8.json
+++ b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_8.json
@@ -1,5 +1,9 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "000022d4-0000-6167-0000-519f0000134c"
+ },
"conversation": "000022d4-0000-6167-0000-519f0000134c",
"time": "1864-05-29T09:46:28.943Z",
"data": {
@@ -9,6 +13,10 @@
"recipient": "00000004-0000-0002-0000-000300000006"
},
"from": "0000200d-0000-386f-0000-0de000003b71",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "0000200d-0000-386f-0000-0de000003b71"
+ },
"type": "conversation.connect-request"
}
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_9.json b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_9.json
index fb3cccc42f8..f017e7589b7 100644
--- a/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_9.json
+++ b/libs/wire-api/test/golden/testObject_RemoveBotResponse_user_9.json
@@ -1,5 +1,9 @@
{
"event": {
+ "qualified_conversation": {
+ "domain": "faraway.example.com",
+ "id": "0000324b-0000-23a4-0000-0fbb00006c87"
+ },
"conversation": "0000324b-0000-23a4-0000-0fbb00006c87",
"time": "1864-05-18T05:11:02.885Z",
"data": {
@@ -8,6 +12,10 @@
"recipient": "19"
},
"from": "00006234-0000-7d47-0000-0b95000079f2",
+ "qualified_from": {
+ "domain": "faraway.example.com",
+ "id": "00006234-0000-7d47-0000-0b95000079f2"
+ },
"type": "conversation.otr-message-add"
}
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_SimpleMember_user_1.json b/libs/wire-api/test/golden/testObject_SimpleMember_user_1.json
index 2ae6de62221..80eaac005a9 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMember_user_1.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMember_user_1.json
@@ -1,4 +1,8 @@
{
- "conversation_role": "qbyp4d5whcwd0owjlrr6oktss00oxflwtid8_ram9r3c2nywq7skew91tok1xxivpkbw6n5l8o5ww4zm220_3pozpvt0obaicadhku7f6e93",
+ "conversation_role": "wire_admin",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000003a-0000-0042-0000-007500000037"
+ },
"id": "0000003a-0000-0042-0000-007500000037"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_SimpleMember_user_10.json b/libs/wire-api/test/golden/testObject_SimpleMember_user_10.json
index f79a59e92e3..207a5662724 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMember_user_10.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMember_user_10.json
@@ -1,4 +1,8 @@
{
"conversation_role": "paru",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000001d-0000-000f-0000-002900000072"
+ },
"id": "0000001d-0000-000f-0000-002900000072"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_SimpleMember_user_11.json b/libs/wire-api/test/golden/testObject_SimpleMember_user_11.json
index 21a560fe824..bd484ba060c 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMember_user_11.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMember_user_11.json
@@ -1,4 +1,8 @@
{
"conversation_role": "e0u15rrzql4y8jymut86vv84l4tjzpfti0_b1w44gy13j3d0dq1y22ws75tkgd4n_9tju4pq34_ddk_g9qpypwu4z3b5",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000007d-0000-0076-0000-001e00000019"
+ },
"id": "0000007d-0000-0076-0000-001e00000019"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_SimpleMember_user_12.json b/libs/wire-api/test/golden/testObject_SimpleMember_user_12.json
index ddfdbc75881..17bf66944e4 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMember_user_12.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMember_user_12.json
@@ -1,4 +1,8 @@
{
"conversation_role": "telj17ej33ilgtqvqajp0ofng9qm6v9b1n32n_l6_vw_xxtk4o7n6r50ea3w1xgzh3eapah1jytfpz0f65utf9xqc4pv",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000003c-0000-0001-0000-004a00000014"
+ },
"id": "0000003c-0000-0001-0000-004a00000014"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_SimpleMember_user_13.json b/libs/wire-api/test/golden/testObject_SimpleMember_user_13.json
index b372b9d712c..84463a68ceb 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMember_user_13.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMember_user_13.json
@@ -1,4 +1,8 @@
{
"conversation_role": "bfamau83n6sskso4rod8fz1tb4tf1zfz8mfd1v0ae1sx17po1",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000052-0000-002c-0000-004500000067"
+ },
"id": "00000052-0000-002c-0000-004500000067"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_SimpleMember_user_14.json b/libs/wire-api/test/golden/testObject_SimpleMember_user_14.json
index b54edbc0d6c..4f9ce06a973 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMember_user_14.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMember_user_14.json
@@ -1,4 +1,8 @@
{
"conversation_role": "tu7zi7d5va224nfegt84g0argkadivw4hlvkj_bpixff19r8j2lf1uhde2rex9ery9xskxm2f_2mpbgutdj6kt56n5proalpciwttcomv3j1pzev6qw3ism",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000018-0000-006d-0000-000600000017"
+ },
"id": "00000018-0000-006d-0000-000600000017"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_SimpleMember_user_15.json b/libs/wire-api/test/golden/testObject_SimpleMember_user_15.json
index 6f1e3dbd8dd..4d0e2fcf4ba 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMember_user_15.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMember_user_15.json
@@ -1,4 +1,8 @@
{
"conversation_role": "rt25zies0df",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000006e-0000-0037-0000-00610000007e"
+ },
"id": "0000006e-0000-0037-0000-00610000007e"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_SimpleMember_user_16.json b/libs/wire-api/test/golden/testObject_SimpleMember_user_16.json
index bc05f792679..06b82a17bbf 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMember_user_16.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMember_user_16.json
@@ -1,4 +1,8 @@
{
"conversation_role": "pknq1f2x",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000042-0000-006a-0000-000800000052"
+ },
"id": "00000042-0000-006a-0000-000800000052"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_SimpleMember_user_17.json b/libs/wire-api/test/golden/testObject_SimpleMember_user_17.json
index c09e3bf0cc2..eb9e4b7ab4b 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMember_user_17.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMember_user_17.json
@@ -1,4 +1,8 @@
{
"conversation_role": "w1bcl23oz4ax6dg14h3y8nxqb77sx9ajonsvx7qd",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000020-0000-0000-0000-00500000005c"
+ },
"id": "00000020-0000-0000-0000-00500000005c"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_SimpleMember_user_18.json b/libs/wire-api/test/golden/testObject_SimpleMember_user_18.json
index 0c046f51efd..bf78e9cbd6c 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMember_user_18.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMember_user_18.json
@@ -1,4 +1,8 @@
{
"conversation_role": "u1c8n7lhvsnr5cdavje5wbezt4an_h92yp0bma6l_6h6dn67lh8_jpk8_eznfja7qhh7wkczfanq5esl7b9y2g16afnnsvgt6i48pmjeo1msq7uuvm",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000049-0000-000c-0000-004d00000043"
+ },
"id": "00000049-0000-000c-0000-004d00000043"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_SimpleMember_user_19.json b/libs/wire-api/test/golden/testObject_SimpleMember_user_19.json
index 41c9dc5beeb..f87a616748f 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMember_user_19.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMember_user_19.json
@@ -1,4 +1,8 @@
{
"conversation_role": "jzmvwd4h3ji2yc2wbog57546ono56qpsobzbszmed5y5436ub8lrvfydxmfleq4j6yj04vdivxpagt5lm5luplyy9zwcbjwyhgcom2njlzvj3ydbmol2onhp75p3",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000031-0000-003d-0000-003800000024"
+ },
"id": "00000031-0000-003d-0000-003800000024"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_SimpleMember_user_2.json b/libs/wire-api/test/golden/testObject_SimpleMember_user_2.json
index 43ff6af0860..67ad5b26c31 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMember_user_2.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMember_user_2.json
@@ -1,4 +1,8 @@
{
- "conversation_role": "ofyvdxbbaf291eyoxm1i16mv2wfa52snql2p9os7shshqpfiw7ivbstjt_nkdqt6_9lz3on3r1nnur8ydc4xae4xf8i2iuu7",
+ "conversation_role": "wire_member",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000046-0000-0027-0000-003c00000022"
+ },
"id": "00000046-0000-0027-0000-003c00000022"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_SimpleMember_user_20.json b/libs/wire-api/test/golden/testObject_SimpleMember_user_20.json
index 26843e231a1..7d86e716092 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMember_user_20.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMember_user_20.json
@@ -1,4 +1,8 @@
{
"conversation_role": "qbb0jgv5yq8ur0ogawcj0gx3f6yau5cnc5x3q8rnq5pn3mn4160ipvryoa2cpz0beg34ur64klqk5a2r9rqvc38w_gp6rbli54r46417_5mmylx5usc9",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000074-0000-0010-0000-001600000078"
+ },
"id": "00000074-0000-0010-0000-001600000078"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_SimpleMember_user_3.json b/libs/wire-api/test/golden/testObject_SimpleMember_user_3.json
index 6d921ea4049..b74eaec28bc 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMember_user_3.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMember_user_3.json
@@ -1,4 +1,8 @@
{
"conversation_role": "7uzp7961dyf_666xqxwvq6uro",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000039-0000-0070-0000-005700000019"
+ },
"id": "00000039-0000-0070-0000-005700000019"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_SimpleMember_user_4.json b/libs/wire-api/test/golden/testObject_SimpleMember_user_4.json
index 0900badd54f..f834cecdf8f 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMember_user_4.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMember_user_4.json
@@ -1,4 +1,8 @@
{
"conversation_role": "4vr9oed4nvhs625ri_cz1cv5kodntk3edmkpu",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000007c-0000-0075-0000-005b00000049"
+ },
"id": "0000007c-0000-0075-0000-005b00000049"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_SimpleMember_user_5.json b/libs/wire-api/test/golden/testObject_SimpleMember_user_5.json
index a29b57cf485..3117b34574c 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMember_user_5.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMember_user_5.json
@@ -1,4 +1,8 @@
{
"conversation_role": "wst92x",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000004c-0000-004e-0000-002400000009"
+ },
"id": "0000004c-0000-004e-0000-002400000009"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_SimpleMember_user_6.json b/libs/wire-api/test/golden/testObject_SimpleMember_user_6.json
index c4c6b935e3b..75de83ef78a 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMember_user_6.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMember_user_6.json
@@ -1,4 +1,8 @@
{
"conversation_role": "nkyx6ypx0p0b_fvx6mt6w5w6n2qpivv9svj2myn5n86isy7n2e07m92t7ostflj4lq1py50bqzdi4smzd",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000052-0000-0053-0000-000400000000"
+ },
"id": "00000052-0000-0053-0000-000400000000"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_SimpleMember_user_7.json b/libs/wire-api/test/golden/testObject_SimpleMember_user_7.json
index 72cbc5734fc..cc213dd2767 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMember_user_7.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMember_user_7.json
@@ -1,4 +1,8 @@
{
"conversation_role": "d8027w_w7pr9fj",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000003d-0000-006f-0000-00480000006e"
+ },
"id": "0000003d-0000-006f-0000-00480000006e"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_SimpleMember_user_8.json b/libs/wire-api/test/golden/testObject_SimpleMember_user_8.json
index 28c8f155f06..7c4a65c39d4 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMember_user_8.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMember_user_8.json
@@ -1,4 +1,8 @@
{
"conversation_role": "_rgnqtn1bdc2eb4nr8ilpka1sm6kt5bvonqm742npdpro1s4b_ydcahfm4q7i0getmnp0vdpod_eye8c_1kb72d_96qypb",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000004c-0000-006c-0000-000800000044"
+ },
"id": "0000004c-0000-006c-0000-000800000044"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_SimpleMember_user_9.json b/libs/wire-api/test/golden/testObject_SimpleMember_user_9.json
index 8bf44b313b5..c247cf598ac 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMember_user_9.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMember_user_9.json
@@ -1,4 +1,8 @@
{
"conversation_role": "sr5pfubd0_cpdp",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000000a-0000-007a-0000-003f0000001b"
+ },
"id": "0000000a-0000-007a-0000-003f0000001b"
}
\ No newline at end of file
diff --git a/libs/wire-api/test/golden/testObject_SimpleMembers_user_1.json b/libs/wire-api/test/golden/testObject_SimpleMembers_user_1.json
index e15ac4adc3a..28d67a6c568 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMembers_user_1.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMembers_user_1.json
@@ -2,74 +2,146 @@
"users": [
{
"conversation_role": "py49zu8bed53ta2nhrhtkv1ck923pk8x70h1zzgp1h15yf6_vcqq7aeckcwpgonge096jg1l2xm4qogs3gucm_s8c_djl718bnwnm6x16rtxttlb47fiazreiew8",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000048-0000-0011-0000-002300000050"
+ },
"id": "00000048-0000-0011-0000-002300000050"
},
{
"conversation_role": "qslp25fyjfvydgtfk3v3ibh8eqdq3kpek7rb11xteg2y5_0a1mv14v5n79jznd5zjfes70nqyeacesqi8v62fmzsc_4zss75er",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000030-0000-003d-0000-00620000002a"
+ },
"id": "00000030-0000-003d-0000-00620000002a"
},
{
"conversation_role": "n0_wuagfmm6ltcjr0n2ib7l2mdg3i0zwtzmb6aribmg2107sirkgo17wjt9d2h66nj3lerw_blivsh6by09a",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000001f-0000-002b-0000-005500000013"
+ },
"id": "0000001f-0000-002b-0000-005500000013"
},
{
"conversation_role": "4q8i3kin7cuo_xpa",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000062-0000-0017-0000-005a00000019"
+ },
"id": "00000062-0000-0017-0000-005a00000019"
},
{
"conversation_role": "e52wem88ym9kubyydku",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000065-0000-002e-0000-00730000002f"
+ },
"id": "00000065-0000-002e-0000-00730000002f"
},
{
"conversation_role": "yson37f_88qcp5chnwpjnwin427qoptb7bmlx5u2454vw95vvt241red8i1pkavlha4l9vx3cr1ajgklb",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000048-0000-0068-0000-002300000042"
+ },
"id": "00000048-0000-0068-0000-002300000042"
},
{
"conversation_role": "q24o118lbfa5zisiltltauh2qyf2lo_vu10hohqtf157wiasc4old5lwbn0g5xarmmu91kfqczv1om08v81k_a",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000021-0000-0057-0000-005d00000055"
+ },
"id": "00000021-0000-0057-0000-005d00000055"
},
{
"conversation_role": "0otsqpgjh2ctmp22nsof114767_vow59km_e",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000080-0000-0071-0000-003400000066"
+ },
"id": "00000080-0000-0071-0000-003400000066"
},
{
"conversation_role": "406ogeb8o68w",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000036-0000-0050-0000-002a0000005b"
+ },
"id": "00000036-0000-0050-0000-002a0000005b"
},
{
"conversation_role": "vgwq1mfqei0embh6msg2q0ucobreh9jl61ql0fge66e9xe",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000044-0000-0025-0000-002e00000026"
+ },
"id": "00000044-0000-0025-0000-002e00000026"
},
{
"conversation_role": "rmv3a7k_p9vlj1l324wnlko6fa0ve13nnf9n0qmey0dgacxewoyss9wih9k0oddw3q634r8ewtj43os8jwg5ka7m58vcqlq2ci6n0a139_g3avnchq9uvi0",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000059-0000-0000-0000-002e0000002d"
+ },
"id": "00000059-0000-0000-0000-002e0000002d"
},
{
"conversation_role": "9pryu0zv3nw_xtb3xr1naukqs0e",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000014-0000-0003-0000-002600000025"
+ },
"id": "00000014-0000-0003-0000-002600000025"
},
{
"conversation_role": "iy",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000002c-0000-0006-0000-007300000061"
+ },
"id": "0000002c-0000-0006-0000-007300000061"
},
{
"conversation_role": "bv0fkxi4521qi41njnulwsz6lp4qwsm0mgbkis1pwjc4bxatdie460vepfj11u_osup17wizy3clm31t_z827yzkw_zcgs",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000003f-0000-002c-0000-00670000002f"
+ },
"id": "0000003f-0000-002c-0000-00670000002f"
},
{
"conversation_role": "5v6_cttr3ctgrijw4h1_gsyi41f4t3dgyh64dhcgeoxvao1h68",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000006c-0000-0072-0000-00400000002d"
+ },
"id": "0000006c-0000-0072-0000-00400000002d"
},
{
"conversation_role": "x0",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000064-0000-0070-0000-000800000049"
+ },
"id": "00000064-0000-0070-0000-000800000049"
},
{
"conversation_role": "ss07m2lagw1v7yvn_swpeauvqdyktrcjreq86gx7shm4xkc3rtimrykvblvtc52pnc8obmsdz475yeet1sxlp0hq7wcaagr2hdi7a7d801khmybj",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000006a-0000-002e-0000-006000000070"
+ },
"id": "0000006a-0000-002e-0000-006000000070"
},
{
"conversation_role": "vb_ng523gxc0ci13cmxscmusff8uw12hvbsvfsa",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000059-0000-005a-0000-002300000061"
+ },
"id": "00000059-0000-005a-0000-002300000061"
}
],
diff --git a/libs/wire-api/test/golden/testObject_SimpleMembers_user_10.json b/libs/wire-api/test/golden/testObject_SimpleMembers_user_10.json
index 08ea26e15dd..0b9e7a7d9ae 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMembers_user_10.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMembers_user_10.json
@@ -2,14 +2,26 @@
"users": [
{
"conversation_role": "buaqu8i1j2czfkdn3jyq1u3m5w3ohl9tuy9c8kihit3s9cax_4f62sr7kj7wfk6gtf6bsrkl6fcvh59idymwykehmvnqfo232q4m2gnc05237ikemuoto",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000047-0000-0044-0000-005c0000003c"
+ },
"id": "00000047-0000-0044-0000-005c0000003c"
},
{
"conversation_role": "64s251imkzo_1fnf4o14i68wjowm02yfae3casjqc6fo_qjhhep50tjlir_i1ggt5qfri_1lk07y7ue81lwykuv9m2se6t8rtkm98zaz1k30",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000068-0000-006a-0000-002c00000071"
+ },
"id": "00000068-0000-006a-0000-002c00000071"
},
{
"conversation_role": "5eugts7sax037o6wowr1yoccc3hp8t226kk3y6h0dqdljvkktrxse2ci788qpulg7o48nco0x5jn1ahwll0vmsmpdzx_oqt9bpejkcd6w2sqrevyfoxei",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000040-0000-0068-0000-001300000019"
+ },
"id": "00000040-0000-0068-0000-001300000019"
}
],
diff --git a/libs/wire-api/test/golden/testObject_SimpleMembers_user_11.json b/libs/wire-api/test/golden/testObject_SimpleMembers_user_11.json
index 8ac7af26fd7..773cc8c4c30 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMembers_user_11.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMembers_user_11.json
@@ -2,110 +2,218 @@
"users": [
{
"conversation_role": "yprw788nm_1n_l3i6g1xn1xjokilmavqko9otxa26hobs7e7s1fgruka4iom01i00aoyui37so",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000069-0000-0074-0000-006800000058"
+ },
"id": "00000069-0000-0074-0000-006800000058"
},
{
"conversation_role": "095o8ll6js3jmhid4vk1j7vc2x_cxq4u7wqr8quf2ndx7wre1525bpa89_k5b6bvy8ypjlkk5xe1u7jqy40dk7blp3fmp0l1vfzg1em2pkpv8dtzp50rgqy_s1c",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000001c-0000-007e-0000-003300000074"
+ },
"id": "0000001c-0000-007e-0000-003300000074"
},
{
"conversation_role": "n9yc3pd38tzb9y_h0oi_d_4r01bpumk0puut8s72kdztlrl3k89d49_07kz_z2br0vey8b1fyo4o_45j7vrpz3wiyrdsjr6l3rg8lwhwk_u5flh_62ld3t",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000025-0000-0064-0000-007000000046"
+ },
"id": "00000025-0000-0064-0000-007000000046"
},
{
"conversation_role": "15firv29l22tvugxzg9x0g59_29h02jqnfg9c5p5e7tr2m64u5bnmsp1fzs8mcvqsb17ym4k1q1ap96v9wxgs1",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000037-0000-000a-0000-00140000001a"
+ },
"id": "00000037-0000-000a-0000-00140000001a"
},
{
"conversation_role": "tddu3qs3p60da19ibmx92unwy8mu9goocijbeamqw4bn3d5kt6_zkm2x1j2mawr_ygt",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000004b-0000-004b-0000-005000000005"
+ },
"id": "0000004b-0000-004b-0000-005000000005"
},
{
"conversation_role": "_ooid3_m9x6065k4n_ka4m0n9hf9anvvmlosi6v4a9e7960cc1elsy7h_7i_bjq3573eh2q7d65zhsqkc69uef7lnv4qqnr8disz4y3idhnvvw_7z8",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000005b-0000-004a-0000-000f00000029"
+ },
"id": "0000005b-0000-004a-0000-000f00000029"
},
{
"conversation_role": "u757zclatb90zyqr29fhuyho0ll2ks90fjji59df50j5aj4tga82k5qsv6ltqbabgx2j3tiofb1iorkzw_d6mhe_g9lzt8cb0iqwa7vag0pqrwfhs5lf7b8qm9f",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000069-0000-003e-0000-002300000076"
+ },
"id": "00000069-0000-003e-0000-002300000076"
},
{
"conversation_role": "ku38a6jk6fswgsgegqka_b33d6gqkwcy7egbx2rpr4pyravsymugig8l6flqxjyyl",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000003e-0000-0047-0000-005e0000006a"
+ },
"id": "0000003e-0000-0047-0000-005e0000006a"
},
{
"conversation_role": "x3th543fq4asgv",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000003d-0000-0034-0000-001100000003"
+ },
"id": "0000003d-0000-0034-0000-001100000003"
},
{
"conversation_role": "qc679x0r4twf5feu87fjf1dukbgbjil0otcoyim397",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000042-0000-0018-0000-006f00000063"
+ },
"id": "00000042-0000-0018-0000-006f00000063"
},
{
"conversation_role": "neniwbge16i_igh4jj_02qflp698pz5xy6hv435ma6q2qlxn3dyz2oao0b43gg93m",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000066-0000-0065-0000-00640000006c"
+ },
"id": "00000066-0000-0065-0000-00640000006c"
},
{
"conversation_role": "mbsnb3cb9i2dxlbz6h0l9_ocpa6zdmtt6708g6bi6b5o59v3s",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000005b-0000-0068-0000-006300000031"
+ },
"id": "0000005b-0000-0068-0000-006300000031"
},
{
"conversation_role": "bv8e7m3xfc3bt639goa1tied4",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000025-0000-000d-0000-000000000008"
+ },
"id": "00000025-0000-000d-0000-000000000008"
},
{
"conversation_role": "oejuw0rpkxojd7lwdvvmypnw5jga0w0i7kf84ryviznjgm_3nd1ls5ykcij2b_xqx9dc36hafa1lvk4x_vo_",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000010-0000-0007-0000-005700000011"
+ },
"id": "00000010-0000-0007-0000-005700000011"
},
{
"conversation_role": "nzqm5_qyv5uj1f47xveo_2hlkqt5n6jrb5o14invzlhe2ddo66",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000059-0000-0001-0000-005e00000073"
+ },
"id": "00000059-0000-0001-0000-005e00000073"
},
{
"conversation_role": "i2_ymg4hcm7bl3ll_9azdlhrur24lolk388v7o2dz1d_zvgyi4btbztoucql64gwoxoilsegph5rpc6n6u2tj9uunlgk29xvqntt8_q41l0cuc7pof26ea5wbzg91e3w",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000063-0000-0061-0000-005f00000045"
+ },
"id": "00000063-0000-0061-0000-005f00000045"
},
{
"conversation_role": "o7n8q1ocx3r99s32sb4_3xg24xz1akvsjebh_kn5cgaxo3e9j5f31cuewnzay80hngq6jjmpqxzde9da5etny",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000002e-0000-001e-0000-00350000001a"
+ },
"id": "0000002e-0000-001e-0000-00350000001a"
},
{
"conversation_role": "32ph0fihxtoawktwdjjd8680nz_mx8tlawldvwm8jba2kjd6tjwi4obhmpnnfqzcdcz31y_1e00gtmrugnjfwh8_nmhgca17jla9s9yy9q",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000013-0000-005c-0000-006100000023"
+ },
"id": "00000013-0000-005c-0000-006100000023"
},
{
"conversation_role": "c3ydrescfgmvsgks6xy866xluancois0b4vl6ypsl6810rlnu",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000000e-0000-0031-0000-00300000000c"
+ },
"id": "0000000e-0000-0031-0000-00300000000c"
},
{
"conversation_role": "nvmtteg8vpg28a9srnw_vn7er1krdecoovramdcin7qdpvrx6bildn885wlfcav9nooubk1hs6g0u5v1v8t0p8vip08o2x1pqj",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000007e-0000-002a-0000-007c0000004f"
+ },
"id": "0000007e-0000-002a-0000-007c0000004f"
},
{
"conversation_role": "bhpymrq9y__8p",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000011-0000-001e-0000-001e00000054"
+ },
"id": "00000011-0000-001e-0000-001e00000054"
},
{
"conversation_role": "mmhox9z9kjhkvj_0l8me0ecnp1m2slotp389sts11f43v71arii5z19n8tb6ct2d4hyyjd45vvwwa_qtuejbwvguyje",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000005f-0000-000d-0000-004e00000016"
+ },
"id": "0000005f-0000-000d-0000-004e00000016"
},
{
"conversation_role": "xyep0gej_kghofx50j3bbolxbm2i58wwp0t_l0pscq4",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000001f-0000-005c-0000-00570000002a"
+ },
"id": "0000001f-0000-005c-0000-00570000002a"
},
{
"conversation_role": "5ozb9c3tnkwaiu4bpw2_nn1o3ib55gjnwen6lw4ltaoitt0ngnxbwahqj631w3pfgphrp0yoh19ip0qfn29p84zijlnystitjm8_v39o4swr2xs3ahs8s2",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000003-0000-0050-0000-00110000004d"
+ },
"id": "00000003-0000-0050-0000-00110000004d"
},
{
"conversation_role": "04n6",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000055-0000-006f-0000-002f0000007a"
+ },
"id": "00000055-0000-006f-0000-002f0000007a"
},
{
"conversation_role": "m1vbstno19orwr77zwq8q8ak1xxdhotqyn30kdv9fq44n2zr0rn46gqfrw8lxp7mt7eywgku3gudup",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000005a-0000-0067-0000-00600000006b"
+ },
"id": "0000005a-0000-0067-0000-00600000006b"
},
{
"conversation_role": "v_ahoalwm78dh_ggai7wusblsnlwhibegsuxe5w1ibm2cnj79a64r_s72hwigx1cw",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000048-0000-007b-0000-001500000034"
+ },
"id": "00000048-0000-007b-0000-001500000034"
}
],
diff --git a/libs/wire-api/test/golden/testObject_SimpleMembers_user_12.json b/libs/wire-api/test/golden/testObject_SimpleMembers_user_12.json
index 6173c18c925..87a716ba820 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMembers_user_12.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMembers_user_12.json
@@ -2,74 +2,146 @@
"users": [
{
"conversation_role": "r812vw__xb9t_hgb5ryc52eujh_ss6aem87h1hakj2u8wvjshpwqrar6ndm5cuka0pkezokcvziv93_8ay2q5a",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000003b-0000-0016-0000-003600000008"
+ },
"id": "0000003b-0000-0016-0000-003600000008"
},
{
"conversation_role": "xf6x34y4hcbgklhrr9a7jkjiclu5dv89m59b5sn40ui8iof88mse47t57ti7zch5cf866tzqua171us",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000056-0000-0050-0000-00040000006d"
+ },
"id": "00000056-0000-0050-0000-00040000006d"
},
{
"conversation_role": "07bbbbgwl0gkv1pfj719wn3z0n8nehby_fk3h6gs39csow68u4_3pbly54fqkng37jqxwr6ym6injx",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000006e-0000-004e-0000-005200000050"
+ },
"id": "0000006e-0000-004e-0000-005200000050"
},
{
"conversation_role": "bj5m",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000069-0000-001a-0000-005c0000007b"
+ },
"id": "00000069-0000-001a-0000-005c0000007b"
},
{
"conversation_role": "ze5lhmk1d8rrjto9615pcoluink8ybkouqa90kogtrbokfv2tdbypoi8inkbi9snsymli7r9bk_ilqjq8ktb7ia2nr2bf6k667nry",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000004d-0000-0002-0000-005700000067"
+ },
"id": "0000004d-0000-0002-0000-005700000067"
},
{
"conversation_role": "z3dbyprvipeu8kl4fabnh24fo77t7gqcs0chxw34ovuru0mxeu6e_jl3s744uggcnwqcyhuzkn1ueko_k0",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000001b-0000-0076-0000-002800000008"
+ },
"id": "0000001b-0000-0076-0000-002800000008"
},
{
"conversation_role": "sq4pc2q1xo14fl8yiegpw0_5y24vohkynzm6zselylhu2xtd3vi4w7odhh1yv9ux01q31s02lv0p337do46bqsjfjywxu1mv",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000032-0000-0007-0000-000400000047"
+ },
"id": "00000032-0000-0007-0000-000400000047"
},
{
"conversation_role": "dnrdny",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000054-0000-0055-0000-002800000002"
+ },
"id": "00000054-0000-0055-0000-002800000002"
},
{
"conversation_role": "gf52",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000001c-0000-004b-0000-002c00000037"
+ },
"id": "0000001c-0000-004b-0000-002c00000037"
},
{
"conversation_role": "yo2r0bi1rrfg2rws_v18eravmdit0igdaksg3atrzjek7u03ip5fjoo6stxjn2xpie700ejkalgzw0zhl3t3j_",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000034-0000-007e-0000-006100000061"
+ },
"id": "00000034-0000-007e-0000-006100000061"
},
{
"conversation_role": "hnoqjm2owsv7yrc899nidzee4ib07r40vfplmxyi9_uf2l49gd5htfmckn3bscip7tygw5hc1bdnd66i9ojjc0bzrpxq9blro73yov2xsb940g7dnijsvvkji",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000030-0000-000b-0000-00790000002a"
+ },
"id": "00000030-0000-000b-0000-00790000002a"
},
{
"conversation_role": "gnpcz5crw82yyqtlvvvfdps3b5uxqr0a",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000004f-0000-0043-0000-004c0000002b"
+ },
"id": "0000004f-0000-0043-0000-004c0000002b"
},
{
"conversation_role": "h84nu68fxxen4b8d5i8br4gixwyntx3o597v_ds147th29_vkuxblstg9af6x3p7",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000079-0000-004b-0000-005e00000033"
+ },
"id": "00000079-0000-004b-0000-005e00000033"
},
{
"conversation_role": "uboc5sab9w92",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000003b-0000-006c-0000-000f00000059"
+ },
"id": "0000003b-0000-006c-0000-000f00000059"
},
{
"conversation_role": "1mcw_zxelu_doxdkqrc5tf660toco4vdv99oecl106z1ygzfnqo6buoysg_s",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000080-0000-001e-0000-002100000043"
+ },
"id": "00000080-0000-001e-0000-002100000043"
},
{
"conversation_role": "jv38u3mzfcdi7xln9al3yepden29o1y6a1xtblfi98cg_bpehklvyf8twyfwinev0ozfokbw71iyh_98ajkyd2z1c2d3a9c09ig14r0tcwy6pqpo",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000069-0000-0047-0000-00000000004e"
+ },
"id": "00000069-0000-0047-0000-00000000004e"
},
{
"conversation_role": "v5l6fw_m_1lakwhcd6g0uz0gpba82jwjdad0qyypc0plx1t1hnu_6zhi3cg6cy25rj3l5aj50pezusaueat8mnfkj_uescuilehc6b6prp8f4lm_ae0dxxvwp3rgu2e",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000001d-0000-007e-0000-004300000036"
+ },
"id": "0000001d-0000-007e-0000-004300000036"
},
{
"conversation_role": "xyb65ic8vzt9x9k1i_a81f6cngzuoii",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000061-0000-005f-0000-00280000002d"
+ },
"id": "00000061-0000-005f-0000-00280000002d"
}
],
diff --git a/libs/wire-api/test/golden/testObject_SimpleMembers_user_13.json b/libs/wire-api/test/golden/testObject_SimpleMembers_user_13.json
index 4e6655a9acf..0a3846bb9a7 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMembers_user_13.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMembers_user_13.json
@@ -2,42 +2,82 @@
"users": [
{
"conversation_role": "auzu2e8pwoe6c0gqamygef4xzybb4o1_yoxbelgaw2012jz9owv9stt14y2d_yi1yi8huvqyhele83b_99fg8ncenqi40pqjl18nkgvwilzo8kahww",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000050-0000-0074-0000-00260000002b"
+ },
"id": "00000050-0000-0074-0000-00260000002b"
},
{
"conversation_role": "8zkp0il16289nuuv9n3h2p8e7znc_4npg5qzdnt18t1l3yx0m40xugm9z_b1w_p98k0b02oq7enifxr4r9b1zyvax",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000000-0000-001d-0000-005f00000055"
+ },
"id": "00000000-0000-001d-0000-005f00000055"
},
{
"conversation_role": "4kkuwyima3ztybzpf3ccy2_mrgcz2sv0nvb29bxjm90dgk6ft_14r7p0qyy12crv_z",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000005-0000-0010-0000-003b00000043"
+ },
"id": "00000005-0000-0010-0000-003b00000043"
},
{
"conversation_role": "pcod0980px6sue9r5cjn7ok4ad9sl6rqpmlmhwu1ju8kp7m757o2axicjqha4e9wz_v3wx3ixb6swh3bujsxpc9g0rjd_und",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000040-0000-0065-0000-007000000049"
+ },
"id": "00000040-0000-0065-0000-007000000049"
},
{
"conversation_role": "qylzcwu0dvtjvra93ocg8fyuyzzowac5yo5410wh4sveczmfq0t2y2e6cae4fux96q",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000015-0000-007f-0000-006c0000004d"
+ },
"id": "00000015-0000-007f-0000-006c0000004d"
},
{
"conversation_role": "z3id3idffe8rl53wpyrd3f2l0y56qxz",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000000e-0000-007b-0000-003400000043"
+ },
"id": "0000000e-0000-007b-0000-003400000043"
},
{
"conversation_role": "2voj8d_5ydou6phiassv9tzhnw185814n90y8rbx5i",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000005d-0000-000d-0000-004500000021"
+ },
"id": "0000005d-0000-000d-0000-004500000021"
},
{
"conversation_role": "09y9r0sl0or7yvw_ztcg3_5xioeq6hk0lwmycvqtfnmhtg84qeotcl3yltg1ibzwdkgw3qz397otoa3xsqvn2uzsvqyzt87_6is2zotb4cgc5m8",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000002d-0000-003c-0000-003e0000000f"
+ },
"id": "0000002d-0000-003c-0000-003e0000000f"
},
{
"conversation_role": "tw7z9ajikrm79pv0q2gq5fjndf940qdzyjznb052bb9b_6zhhdunxgm91cj6mf04yp1rzapwrx1sox8z9sfijxy1xxn61b0",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000000b-0000-0042-0000-00740000003b"
+ },
"id": "0000000b-0000-0042-0000-00740000003b"
},
{
"conversation_role": "nmhc57h6qa7lzv0d0scl8_53iwuitrlmmujkwf_vgjgn4s027b5i9hbt2nxhm1d",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000048-0000-0039-0000-008000000004"
+ },
"id": "00000048-0000-0039-0000-008000000004"
}
],
diff --git a/libs/wire-api/test/golden/testObject_SimpleMembers_user_14.json b/libs/wire-api/test/golden/testObject_SimpleMembers_user_14.json
index 62830406692..d52587c9011 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMembers_user_14.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMembers_user_14.json
@@ -2,102 +2,202 @@
"users": [
{
"conversation_role": "5qxti74lbqe_tgvvnq7ub2xxn0e2w0",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000049-0000-0018-0000-002200000071"
+ },
"id": "00000049-0000-0018-0000-002200000071"
},
{
"conversation_role": "mwzccu_p4zazafbgnvf",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000003f-0000-004e-0000-005800000030"
+ },
"id": "0000003f-0000-004e-0000-005800000030"
},
{
"conversation_role": "rmr13bsn9lo1dil9j12jj31qdod3izckzpsrflf653suq328bmnd_kirumpr",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000017-0000-007e-0000-006f00000027"
+ },
"id": "00000017-0000-007e-0000-006f00000027"
},
{
"conversation_role": "_tae",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000005-0000-003f-0000-00280000005e"
+ },
"id": "00000005-0000-003f-0000-00280000005e"
},
{
"conversation_role": "kaa5_qbk5nvvgx4jowierx",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000002b-0000-0003-0000-004a0000002d"
+ },
"id": "0000002b-0000-0003-0000-004a0000002d"
},
{
"conversation_role": "2hco6n9dqp4qph8alctzrcw91aiw1d4eb5g6ebeb3739i31b4o8seok8krf1z95t3zft4gif5ib9qtsuuzvb0ip17svpfk21akw0d_hz46u",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000005d-0000-0054-0000-002500000028"
+ },
"id": "0000005d-0000-0054-0000-002500000028"
},
{
"conversation_role": "o1bfk_p6xvxp7t1i6f3d57jv2_yl4nq5or1zy4vd2dh22ue895yoduwjo3wc5qzostuhbw369j",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000005e-0000-0002-0000-004b00000045"
+ },
"id": "0000005e-0000-0002-0000-004b00000045"
},
{
"conversation_role": "vf6s6yc5eavaytm7_6",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000005e-0000-0027-0000-003d00000065"
+ },
"id": "0000005e-0000-0027-0000-003d00000065"
},
{
"conversation_role": "n7r9vlgda6kn7ehvrz_hrl6t1p07xr42_rgp",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000035-0000-0035-0000-00010000001f"
+ },
"id": "00000035-0000-0035-0000-00010000001f"
},
{
"conversation_role": "qtfn187ab22rzoan9jy9ug2qyjisshxdeo184e8cjm",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000054-0000-0069-0000-002e0000007c"
+ },
"id": "00000054-0000-0069-0000-002e0000007c"
},
{
"conversation_role": "lxynbdsl575ahtb1fzz_0ucdcsmeiu4baq0ziei5",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000056-0000-005a-0000-006a0000004c"
+ },
"id": "00000056-0000-005a-0000-006a0000004c"
},
{
"conversation_role": "q4m0kblmex11x_k__yurqoqixdbhbcluk60_kpje7xvt5drk0jdp2jh29ql4hlvz_af8yx61ptki414nip32h59m1m_spku9ac9v8pfxo_ue6",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000041-0000-0013-0000-007700000017"
+ },
"id": "00000041-0000-0013-0000-007700000017"
},
{
"conversation_role": "xt24wodqlibu4gtj128oj8e61z4gt_5d_we5m9jk35crgs8levtcul1pwak1vxn95q9h4vqss5qlezj4r3igvmyv4y",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000022-0000-0071-0000-000f00000072"
+ },
"id": "00000022-0000-0071-0000-000f00000072"
},
{
"conversation_role": "vitd82h50v",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000003b-0000-003c-0000-003500000028"
+ },
"id": "0000003b-0000-003c-0000-003500000028"
},
{
"conversation_role": "io1uuzbdi7sfvy93f6kgdq31xskuwc8mxphwwrpv9rxc4o8ycdu4l4_0_26hm1g03g2",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000047-0000-0005-0000-001a0000007b"
+ },
"id": "00000047-0000-0005-0000-001a0000007b"
},
{
"conversation_role": "cksijt3o36xuu324i61apsuwdi32k3l1x_oalfaqqtk",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000000a-0000-0039-0000-00530000006f"
+ },
"id": "0000000a-0000-0039-0000-00530000006f"
},
{
"conversation_role": "ru1kksg2ef5_yo7i5uwq",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000047-0000-0062-0000-007700000025"
+ },
"id": "00000047-0000-0062-0000-007700000025"
},
{
"conversation_role": "lyspep_wcyu0fegqwpmns9lzjpy49i_6ufmhkft3bbmf_yi76hzdacj7",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000049-0000-000c-0000-002400000060"
+ },
"id": "00000049-0000-000c-0000-002400000060"
},
{
"conversation_role": "q8upik7s6rzlcqfbtrx0ty9_pjrqeq02b4nkdnggfu_y_ey8h430k8l900czggrlngyvz0hezpfqg0ta7dv7enlsujqhv9w2qcmrye97ozaswyg671b6cqk5_yprgn5",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000028-0000-001d-0000-005000000045"
+ },
"id": "00000028-0000-001d-0000-005000000045"
},
{
"conversation_role": "e3xri9yngsx6817txk_k58ybfykismurlmmhsa7k5xv5l8g5qgx48h9sp9ir4tp2n7i01wc4780lwvl9o31yacvdashtu82108yevwv1rnh1co8bzws28_01ao5jhv7",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000071-0000-0014-0000-002000000080"
+ },
"id": "00000071-0000-0014-0000-002000000080"
},
{
"conversation_role": "tzu6fq7owrz3hkm_7tmtmzr4oj9pyo1oi0bq4hvp3lrn3e5t6ep0x4g84nnmg79kag8tdaoopluff0eavzqpp57ij3us0xat7jua1g2iuhfjlrpoen2dyw1eulrqa5",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000037-0000-004c-0000-006800000015"
+ },
"id": "00000037-0000-004c-0000-006800000015"
},
{
"conversation_role": "r7xed76rtgltedolcrxbq67tyo5u5arm9ip49bo5szs24skzui_3h65_2j0md66gjlz850waloiuiqsd",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000005a-0000-0014-0000-005c0000000b"
+ },
"id": "0000005a-0000-0014-0000-005c0000000b"
},
{
"conversation_role": "80_0tuom0zml0hz7q8ioxscxusk7ghx63wp5o83lax5",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000000c-0000-0059-0000-004600000053"
+ },
"id": "0000000c-0000-0059-0000-004600000053"
},
{
"conversation_role": "ae3h61opsksj5x5if1tt3a74ehzw02ds6dqisz_5l",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000050-0000-0080-0000-00350000007c"
+ },
"id": "00000050-0000-0080-0000-00350000007c"
},
{
"conversation_role": "c_fbjpth0u2yni1mwed8xyjo7hvrev2ojjb3g8vu2sij81cjnehtpaq5mkd_55qf0eavaxtmrhzv20vbhrxssewk5m7rmxgveuva24e05xs",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000014-0000-0046-0000-008000000048"
+ },
"id": "00000014-0000-0046-0000-008000000048"
}
],
diff --git a/libs/wire-api/test/golden/testObject_SimpleMembers_user_15.json b/libs/wire-api/test/golden/testObject_SimpleMembers_user_15.json
index 6ec0682ac14..6ba205a0cc9 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMembers_user_15.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMembers_user_15.json
@@ -2,58 +2,114 @@
"users": [
{
"conversation_role": "da1vnxfznxggp6c2qcjdx4sbo4usg7jb58hmd_ylzyr_97m9rpyg6gmw9ikw",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000006a-0000-0032-0000-004f0000001e"
+ },
"id": "0000006a-0000-0032-0000-004f0000001e"
},
{
"conversation_role": "asfpn3xoxsvsz8ubdt6b3b",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000001f-0000-005a-0000-001a00000078"
+ },
"id": "0000001f-0000-005a-0000-001a00000078"
},
{
"conversation_role": "ftkjnuoy9i1h0yyf1x87m97flhx21n2475_rsnn76nkpl9toieae7wk0y_f83ji",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000001a-0000-0017-0000-007e0000000c"
+ },
"id": "0000001a-0000-0017-0000-007e0000000c"
},
{
"conversation_role": "g22_kcj2fae4nspxpz30n5f6ib5bhrb",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000001e-0000-0019-0000-00180000004c"
+ },
"id": "0000001e-0000-0019-0000-00180000004c"
},
{
"conversation_role": "qzkowgmbm3t4ck1lzb96ero0d6yw79kzdf2q",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000006d-0000-001b-0000-00050000003f"
+ },
"id": "0000006d-0000-001b-0000-00050000003f"
},
{
"conversation_role": "cyiw0yfayzt_ynv0h94pdv0hl5u46adyyyb6n",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000037-0000-001c-0000-000c0000002f"
+ },
"id": "00000037-0000-001c-0000-000c0000002f"
},
{
"conversation_role": "x3x3gfejb_1d1g9nsyw0rey0_tm9zs6pyuily3nrjsue7p1mp_15kffuojhi66z_t_lmnr_lq79wzvcjm3czs7i_9lvokkakhmfwkdg3f",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000047-0000-0014-0000-007e00000049"
+ },
"id": "00000047-0000-0014-0000-007e00000049"
},
{
"conversation_role": "s1cu0tibrwjvgpa49x9sk9kuzyd4hco7pj3gnbcc8ie519vmobd70ln2im2dx_yg_qoh4rc8",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000028-0000-002f-0000-004a00000063"
+ },
"id": "00000028-0000-002f-0000-004a00000063"
},
{
"conversation_role": "e6t98s2m_0jqjwibfan257dq0tbxl452q0dcs5mkl4kn",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000027-0000-0075-0000-003f00000016"
+ },
"id": "00000027-0000-0075-0000-003f00000016"
},
{
"conversation_role": "9jjkkwb5spu0x_honfioztulhgg2vu7dwcxngha4581j3dj73mo01oh3r6kdbpbxiwrt2k8q6ixcuu97qzpc962rlz4c7en9do885ykjstaru6yjm6w",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000045-0000-007a-0000-003900000015"
+ },
"id": "00000045-0000-007a-0000-003900000015"
},
{
"conversation_role": "wb1qwb0yfrwzna1gx4xcjrb9uvilio5pv_glva_sy8s9zr5udj10oy3ygf6jvbl0e92z6ucw2c3fur9ebha005gpr45jqkf3_hs40e3f6dssc",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000000-0000-0040-0000-002e0000007c"
+ },
"id": "00000000-0000-0040-0000-002e0000007c"
},
{
"conversation_role": "hq3mg17cd7p27q00dhyjrdu7pr4kdplicp4ipm",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000057-0000-0060-0000-006e0000001d"
+ },
"id": "00000057-0000-0060-0000-006e0000001d"
},
{
"conversation_role": "v6z1f27bvomf76x_tp5e3yik4qfx6xmohuu7sr40ijtw8b0v10746aja2yyhomb9yov9f4acq0pwng2cg76gqdh8moow_tzeonmqol6wz191m8oo4j7c8_wq",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000037-0000-0007-0000-006600000055"
+ },
"id": "00000037-0000-0007-0000-006600000055"
},
{
"conversation_role": "8_lth324f81q1zr6nhz1jw5oeu4ovjqnl8lobb9t3azlu7hj3s62_xm30b3fie4s",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000032-0000-0078-0000-005100000021"
+ },
"id": "00000032-0000-0078-0000-005100000021"
}
],
diff --git a/libs/wire-api/test/golden/testObject_SimpleMembers_user_16.json b/libs/wire-api/test/golden/testObject_SimpleMembers_user_16.json
index 1ead34ee265..21032df838d 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMembers_user_16.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMembers_user_16.json
@@ -2,78 +2,154 @@
"users": [
{
"conversation_role": "z2z2ju3pgvxysob_3e2wg_tyxfp1wruzek4c6iuyk23e5qxuieyz3tg436tvzl9l8k5aa_bexy1m9ggauxms8pug1f",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000001c-0000-0079-0000-006800000001"
+ },
"id": "0000001c-0000-0079-0000-006800000001"
},
{
"conversation_role": "5roj3c12kqt7",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000005c-0000-0056-0000-00780000001a"
+ },
"id": "0000005c-0000-0056-0000-00780000001a"
},
{
"conversation_role": "r_ivn3ruci8x4pl6bl1g_jex4",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000032-0000-007f-0000-00270000002a"
+ },
"id": "00000032-0000-007f-0000-00270000002a"
},
{
"conversation_role": "x__i5068zhcdautdjavpic3zi7u950hdw_iy63gdd0h6zbs1pfviyyui2zl",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000002b-0000-000e-0000-003400000009"
+ },
"id": "0000002b-0000-000e-0000-003400000009"
},
{
"conversation_role": "g5ignr39a41zundyudm1rovkz6a3rjy3dodkwk0ht3jnsqp1maz2ulc7yx93z7uy_dyqso9ofxblm2xqs",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000007a-0000-0017-0000-005800000050"
+ },
"id": "0000007a-0000-0017-0000-005800000050"
},
{
"conversation_role": "ltmubflt4eswsuurwvqxkd_ngfbkyilt00dzsjckdyh2eod2v804nw0xc8jbkz8bg29nud9oe3mlgvwoml4t8cmukd7un3ycogbvojmf12ktaxx",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000061-0000-0036-0000-006700000071"
+ },
"id": "00000061-0000-0036-0000-006700000071"
},
{
"conversation_role": "ox94nzepea1423z47_yd1txi",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000060-0000-007e-0000-004f0000005a"
+ },
"id": "00000060-0000-007e-0000-004f0000005a"
},
{
"conversation_role": "dvrbz7c9_igfu5vl3_9ujy5dqwaevjrb7f1n2kchbxroz8ccnktv6nrybj9s0ogviznxyzw6r6ebu72su9hsz9l62fly8cf_kkf6aeri9thd4z2",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000002e-0000-0016-0000-001e0000001a"
+ },
"id": "0000002e-0000-0016-0000-001e0000001a"
},
{
"conversation_role": "307w5bmxkox9r8klphxtmjge_8jgawjnotx_r0krsadx_n7",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000006b-0000-0038-0000-006a0000004d"
+ },
"id": "0000006b-0000-0038-0000-006a0000004d"
},
{
"conversation_role": "j6_h0zam0_k8x6coroh4ixk9m3pk5acmwx_cg1q7mpnmn1zha_i",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000043-0000-003e-0000-005a0000007d"
+ },
"id": "00000043-0000-003e-0000-005a0000007d"
},
{
"conversation_role": "9j1_jkemhozgdmdcoh39j4gxbccuysthtljfwf7qwgjk50tkamk2p_xcw7i9cclp35faxipa5qt2i23_u2n35anfp9jihl8fj2jebisxmgkfnmh89z",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000002d-0000-0010-0000-00010000000f"
+ },
"id": "0000002d-0000-0010-0000-00010000000f"
},
{
"conversation_role": "ds4scvwxorxgxtlskf27hu",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000043-0000-0070-0000-006400000045"
+ },
"id": "00000043-0000-0070-0000-006400000045"
},
{
"conversation_role": "1j2p5d49kfu8omp83fr67o4qpdb07",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000026-0000-002a-0000-00150000005d"
+ },
"id": "00000026-0000-002a-0000-00150000005d"
},
{
"conversation_role": "j6xf6yvyevf8aweam9h70ga1gs4lr_5n7khmou70p2_g1qzjpsgpdkf45scpqggc2rve2aqrev88u91sj3sny4cavowa1hosih61ycaq0pf41inxqhwzhc",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000079-0000-0040-0000-006100000033"
+ },
"id": "00000079-0000-0040-0000-006100000033"
},
{
"conversation_role": "u0lw_wyfzjmmu17s65cau3i295l_0c4hu823csp473bry2cn2zr24vsay4w2m2936y9ja0mvapjxafww89o",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000007b-0000-0033-0000-005a00000007"
+ },
"id": "0000007b-0000-0033-0000-005a00000007"
},
{
"conversation_role": "u5wny30naytnu39dwahr_5trcz66uqb4qrbvvhiu2juwbbkv8udp8whvw3jhy2o2s5jwmwesp_6qx_ceatpyex9xssv4z38p1xs3mpau7",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000078-0000-0058-0000-006300000019"
+ },
"id": "00000078-0000-0058-0000-006300000019"
},
{
"conversation_role": "cgx4sx81so7w8wyohtqvr53bzf_8od3j77",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000068-0000-0042-0000-007b0000002b"
+ },
"id": "00000068-0000-0042-0000-007b0000002b"
},
{
"conversation_role": "p35w8lh2_arnf44pbqrk3g4ln0881ml0b4t",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000004b-0000-002a-0000-007000000043"
+ },
"id": "0000004b-0000-002a-0000-007000000043"
},
{
"conversation_role": "0z7pdm65ezdilg_qqnzz34l5e1zi8gsw78qbwitnu2ng",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000006d-0000-007b-0000-006b00000005"
+ },
"id": "0000006d-0000-007b-0000-006b00000005"
}
],
diff --git a/libs/wire-api/test/golden/testObject_SimpleMembers_user_17.json b/libs/wire-api/test/golden/testObject_SimpleMembers_user_17.json
index 2c22fd05108..ad3cbd0ccf0 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMembers_user_17.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMembers_user_17.json
@@ -2,26 +2,50 @@
"users": [
{
"conversation_role": "ui5u6nk8og1da9q8_ha4hhv2v_qrs_mveewm6h5_4384yf4ovtp67w_z6x9_waqln013ahg3rw9oky5o4yff6v",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000071-0000-0050-0000-006200000041"
+ },
"id": "00000071-0000-0050-0000-006200000041"
},
{
"conversation_role": "qsowevndt0gqwh1yvpqxd_4u3junr66dhuerv38qrhzv5kf9i38fkd",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000003-0000-0074-0000-004e0000003c"
+ },
"id": "00000003-0000-0074-0000-004e0000003c"
},
{
"conversation_role": "dq1mag9bzqoenu3chbc2mn91ivbh",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000000b-0000-003a-0000-003900000026"
+ },
"id": "0000000b-0000-003a-0000-003900000026"
},
{
"conversation_role": "9ij9p1jla54lbtk66mhbakd7m7p502p6tz1ryyaep94rz7upsquixaaf6eoewz_oziw_ok7xbo49",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000001b-0000-0058-0000-000500000057"
+ },
"id": "0000001b-0000-0058-0000-000500000057"
},
{
"conversation_role": "13tjs8e20xxqd296duhver4er47dj47v2yyspcpfz5pdhbmnmlxyzar0w2recatb6r4_20zcd",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000007a-0000-001d-0000-005800000052"
+ },
"id": "0000007a-0000-001d-0000-005800000052"
},
{
"conversation_role": "gy3s_2c0r9lzi08hjnkbc9pbhdu3yvg409ipjztpmthie_j834nn12zjq_m56w1dqqi8mpde",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000001a-0000-001e-0000-00360000007b"
+ },
"id": "0000001a-0000-001e-0000-00360000007b"
}
],
diff --git a/libs/wire-api/test/golden/testObject_SimpleMembers_user_18.json b/libs/wire-api/test/golden/testObject_SimpleMembers_user_18.json
index 39f7f068c1f..0e5a73d5dea 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMembers_user_18.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMembers_user_18.json
@@ -2,90 +2,178 @@
"users": [
{
"conversation_role": "3me5rxn2utoa25v5xxht8ulguq6yxi7tp38dwoyvs_4o40u1to2j5nrtykcbqmxuefqoulfptt90s",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000013-0000-003e-0000-006000000058"
+ },
"id": "00000013-0000-003e-0000-006000000058"
},
{
"conversation_role": "934awteu5wur99l7fnw80clvhm_gza7sdsh12wm_ppvma27jwl2ry8u4q3pdm2sqae_w4bqn1l7k3bscfww2c6i",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000002-0000-0026-0000-005d00000007"
+ },
"id": "00000002-0000-0026-0000-005d00000007"
},
{
"conversation_role": "ykd2zkk62g1dcm7nnuwr3xbho312yshv5ies_zgv954zlari0ayv9x6gnxdckc4s36vvdfdg3ohsr__e5_tlo_y6lbnmhilm_gwblmnzgiqxxmhvegnbh6haxg",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000078-0000-005b-0000-00580000004f"
+ },
"id": "00000078-0000-005b-0000-00580000004f"
},
{
"conversation_role": "tg_h0qkv4aijetsz83m1kgblaem7q",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000054-0000-0053-0000-00340000006e"
+ },
"id": "00000054-0000-0053-0000-00340000006e"
},
{
"conversation_role": "wxx0aq41xb9dkhyi1gai4twn840_gv26hyjwwo8xaycbju4xowxt40eimnud63h61y56aacmio7reb1u7xhbkdpkvzr7uw_pu_o",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000023-0000-002f-0000-002000000063"
+ },
"id": "00000023-0000-002f-0000-002000000063"
},
{
"conversation_role": "8axn63dyge68i43hczeorjbtz3cyd9nv316fhppz7bfn6ev57rxedqhohixccni74vrd5mujd3xudu1s5jrw5fjcpo5uy52z6mxjpnsi14md10_6o",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000022-0000-0057-0000-006f00000014"
+ },
"id": "00000022-0000-0057-0000-006f00000014"
},
{
"conversation_role": "b72qpthui23k7cbxz8m3226h",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000003-0000-0013-0000-003b0000002d"
+ },
"id": "00000003-0000-0013-0000-003b0000002d"
},
{
"conversation_role": "4ndtltebfabogp8i9skodvx86xbu_",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000052-0000-0046-0000-002e0000003a"
+ },
"id": "00000052-0000-0046-0000-002e0000003a"
},
{
"conversation_role": "r9jg4",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000048-0000-0028-0000-001b00000006"
+ },
"id": "00000048-0000-0028-0000-001b00000006"
},
{
"conversation_role": "1_r3da_nqzfzbs_6j8sztfleq4ov3zk7e6lhjg04",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000007b-0000-004d-0000-005500000006"
+ },
"id": "0000007b-0000-004d-0000-005500000006"
},
{
"conversation_role": "_v361ue5c23jdmlu43s7eckol6hzqgdvd49z_ga87_gtfu6s6j49c2g12tfsv",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000000f-0000-0056-0000-002a00000066"
+ },
"id": "0000000f-0000-0056-0000-002a00000066"
},
{
"conversation_role": "woty16_d9_5ot5k0aur_vvud9z_3f41om2hxf7bc4bc1dagzzecnhmnl2asd_slndkj81g2p_9kibhfw71_7wlj9n",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000014-0000-006d-0000-00600000005f"
+ },
"id": "00000014-0000-006d-0000-00600000005f"
},
{
"conversation_role": "gwad3aujch9jwn8wgs36djkofbhgc80q3xpg08kziibyr249qor8xzyhn724zmj57mup_15ik_ts3985q94t2ycjatnt5jzurfb8jy06y4dqiyh3aowp0bgn",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000005c-0000-0022-0000-007c00000001"
+ },
"id": "0000005c-0000-0022-0000-007c00000001"
},
{
"conversation_role": "0_lettu8qvkqk6krt4_nez4e95b7y",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000008-0000-0042-0000-00250000007a"
+ },
"id": "00000008-0000-0042-0000-00250000007a"
},
{
"conversation_role": "z3o8c78vi1ynsrc_s6ebpnz96960dez7lnlijjz843un77jtnj9a5pah",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000001f-0000-0022-0000-007c00000028"
+ },
"id": "0000001f-0000-0022-0000-007c00000028"
},
{
"conversation_role": "4qwvs96y63anponvr9dm5rlixyqhi3jumk9q5827hpksw8n63u_mcg90c3ymz6flf4g5hfcczn3j6rvoiushsvltz30mou0m6_swr8p9ajzs67a1bkc7",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000022-0000-0045-0000-007d00000007"
+ },
"id": "00000022-0000-0045-0000-007d00000007"
},
{
"conversation_role": "fnxibm1l1089wzrxa",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000007f-0000-006c-0000-006f0000005a"
+ },
"id": "0000007f-0000-006c-0000-006f0000005a"
},
{
"conversation_role": "2ty62trmvnnqkyblir4w1hdba9r10gfkxjb8ddj0riit2i7ymxpprtrcgs2p6w05prtzxuhj_07nuntgsk8x4o2e1pe6cijkk1igi45_e49d38x4b_",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000022-0000-0020-0000-006900000033"
+ },
"id": "00000022-0000-0020-0000-006900000033"
},
{
"conversation_role": "b6fsp76pbub9rakkxrs7lk8gsh005ajo5m8ap4apvxjxoak95s",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000000-0000-002f-0000-007000000064"
+ },
"id": "00000000-0000-002f-0000-007000000064"
},
{
"conversation_role": "hinbqrmh843xzsvpu_a6ifnc6lc164f58gkhv",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000002-0000-0042-0000-000000000058"
+ },
"id": "00000002-0000-0042-0000-000000000058"
},
{
"conversation_role": "18os8cjhuuv8ng",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000005f-0000-0052-0000-005400000002"
+ },
"id": "0000005f-0000-0052-0000-005400000002"
},
{
"conversation_role": "4k62w0mz4j2hsstjelh8zx0gpg927v3ggod9z17i",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000004b-0000-0046-0000-006e00000024"
+ },
"id": "0000004b-0000-0046-0000-006e00000024"
}
],
diff --git a/libs/wire-api/test/golden/testObject_SimpleMembers_user_19.json b/libs/wire-api/test/golden/testObject_SimpleMembers_user_19.json
index 4865be120aa..6b68097086f 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMembers_user_19.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMembers_user_19.json
@@ -2,82 +2,162 @@
"users": [
{
"conversation_role": "4fex2pu__ri6dlr68us285w6yv4alufdibfd_b8zt7ckdo7ej590lkosvd4be8cg4acr7",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000036-0000-0080-0000-004600000006"
+ },
"id": "00000036-0000-0080-0000-004600000006"
},
{
"conversation_role": "6lab82iykqaweibdnw89206lrz9vs16h6ae31uruwd0dat90ms",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000078-0000-007c-0000-004d0000001c"
+ },
"id": "00000078-0000-007c-0000-004d0000001c"
},
{
"conversation_role": "9lhsg7v6b93dyc9mrbtoh8upg5uotf4sygb7ivnzssk0vaj_i7dxoxttzeklh8am0dkzxlm5shu_xht44i7q2ngu5i9itakyus38vfxwhlo9vv14tbtky4do2gy",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000002b-0000-0013-0000-006900000050"
+ },
"id": "0000002b-0000-0013-0000-006900000050"
},
{
"conversation_role": "u9dnn4lg0fkq7wjm352pnvivghndsyu5dc1v7",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000037-0000-0053-0000-00300000001c"
+ },
"id": "00000037-0000-0053-0000-00300000001c"
},
{
"conversation_role": "1zrz9vvgb_owenushueadxheydq3xj7p6qnshwytttwuihgplc3swswxt7135l61u719cxyckizmc0tvss209e_u0vs9cq3g7iotw6_rjv1xekwz59jvxbf",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000001a-0000-0014-0000-004100000073"
+ },
"id": "0000001a-0000-0014-0000-004100000073"
},
{
"conversation_role": "du5q_xfl5_euursw0bgwzmmfikr1jql29qf58tworxnj4c5z3oxp4g2y9hwk9l0azincl_yj7cygwz_k2lse_xhs4j1vj56g58",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000001-0000-003c-0000-007f00000051"
+ },
"id": "00000001-0000-003c-0000-007f00000051"
},
{
"conversation_role": "b3pojvx2wmrpy7q6adcuo5szs",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000023-0000-001f-0000-001b0000007f"
+ },
"id": "00000023-0000-001f-0000-001b0000007f"
},
{
"conversation_role": "tai2j6zt1iloa7k4lvhete0ia1lixhu0aakslmc3hnva6iiv4lmb7yjspyjz74wcwhg1hwligz1nvc4hkhwijrnf96epf3yc1sdzwe3ml2tj2stucgz",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000055-0000-004d-0000-006900000053"
+ },
"id": "00000055-0000-004d-0000-006900000053"
},
{
"conversation_role": "xbu28z6zird3kd4iqv0j2r7_e0b2qdxzpdeuzvxb__idnrzhib1rud5o98b4",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000047-0000-0003-0000-000500000001"
+ },
"id": "00000047-0000-0003-0000-000500000001"
},
{
"conversation_role": "rl_mm",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000004b-0000-003b-0000-007f0000005a"
+ },
"id": "0000004b-0000-003b-0000-007f0000005a"
},
{
"conversation_role": "59kuc3qc4e8bi2a47kw9irbr56x1p95x5qpmapy5q8e_obwek1a356gjb_pekd0oujb08e8u7536n416v4a3k574xz_m6shboen7iq_lihb",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000006-0000-0034-0000-007e00000030"
+ },
"id": "00000006-0000-0034-0000-007e00000030"
},
{
"conversation_role": "u2p8qhkz55ga7ay_lyst30ei5_7mg46cj60uhe0pj2tjbcwaoamnzmlqlwyv6thsr_k36dr69gusa838_a9aoh",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000006d-0000-007e-0000-000f00000060"
+ },
"id": "0000006d-0000-007e-0000-000f00000060"
},
{
"conversation_role": "vcan3ha2ahaaaxbs_rks5vygwuny8zp6st17fv9pk04f_2onxvw_guw",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000075-0000-0022-0000-001100000044"
+ },
"id": "00000075-0000-0022-0000-001100000044"
},
{
"conversation_role": "1ilr38ti5n1mbpm3qysh5e4wou0251c7iarmlo5p8x0dm9gc4wtmuzy1gpc6kubnxcc0tkyjmkhxpncffog4u5n_x7qhwwnyzbqlo_kpz33iwjwqtub8a",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000069-0000-0024-0000-00790000005f"
+ },
"id": "00000069-0000-0024-0000-00790000005f"
},
{
"conversation_role": "6rtysqt18oeenfxoi3n5751fia55yfvpiz9bfmy2g31sibwv7ewjl737n6yb_zmc2q3fhikqwtvp9tyynm6wu0p",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000068-0000-0040-0000-003100000045"
+ },
"id": "00000068-0000-0040-0000-003100000045"
},
{
"conversation_role": "_g3yp9j43gm8l8d5vgx3kq2as1e0q2qwtro_ah8s5tp4mpzd4syw1kjno1lb_u2qaoqqg3cp5xq873oi3f95llejnd31s3nhzi6m8r",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000004a-0000-0007-0000-005f0000003d"
+ },
"id": "0000004a-0000-0007-0000-005f0000003d"
},
{
"conversation_role": "qq2hd0",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000068-0000-0008-0000-006f00000067"
+ },
"id": "00000068-0000-0008-0000-006f00000067"
},
{
"conversation_role": "8hsl7yd_1raa4a",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000002-0000-000d-0000-00320000006b"
+ },
"id": "00000002-0000-000d-0000-00320000006b"
},
{
"conversation_role": "ftgvbfyalwkdozipte",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000001-0000-000f-0000-001800000008"
+ },
"id": "00000001-0000-000f-0000-001800000008"
},
{
"conversation_role": "ubqu_ach_3pmq8xmxniwo1ddu7poprzvvorzmxytdpmavfeond4do7p9g7txo2n9pxvazodp_bxro6ej6kb_qj5m",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000004e-0000-001f-0000-001e0000007d"
+ },
"id": "0000004e-0000-001f-0000-001e0000007d"
}
],
diff --git a/libs/wire-api/test/golden/testObject_SimpleMembers_user_2.json b/libs/wire-api/test/golden/testObject_SimpleMembers_user_2.json
index b8348658ec7..dda3aa5869a 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMembers_user_2.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMembers_user_2.json
@@ -2,118 +2,234 @@
"users": [
{
"conversation_role": "s_l79fneq3swkwha5llyp8_b7hw9tyi906s7c5c3n1t_v1rkax_gx88gbc8tti9z8e5ad1y0n3irysgradbjj8_ykfkhjv2xu70",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000004c-0000-0022-0000-00720000007c"
+ },
"id": "0000004c-0000-0022-0000-00720000007c"
},
{
"conversation_role": "jhrez7dufl3ne050doxot1f7mhup7a0rr59472xmcvukln0cw",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000002c-0000-0079-0000-004b0000002a"
+ },
"id": "0000002c-0000-0079-0000-004b0000002a"
},
{
"conversation_role": "t9e5nbirc5uv1n4jda1bo8mwc72si1wi0_hngmo0sw",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000006d-0000-0033-0000-006d00000070"
+ },
"id": "0000006d-0000-0033-0000-006d00000070"
},
{
"conversation_role": "fee9mv0u39pyxfjffutut9ahag22y4_bjd_gcflwenmgndeztyuur4ypax_3kwt2i4extz5mg30c6l_6lwtff1tmbh_82uo9y7ni42m2yjjfvwu13gqx2ucw3iv_wh",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000018-0000-0022-0000-001b00000048"
+ },
"id": "00000018-0000-0022-0000-001b00000048"
},
{
"conversation_role": "4o0ctcw73niokwjhjo8_65khxlxx_1o9ktctoq5kdmm39640gc2f3uc3nq99bq_93sgnhvd04wx3pgw1n1l",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000000-0000-004e-0000-002f00000038"
+ },
"id": "00000000-0000-004e-0000-002f00000038"
},
{
"conversation_role": "4x1goxovt1vshlij0yhfb9hu_adl4dvucsylf6o32fdsrmx0yr1lan69pyz4o50025mqtu1xi9b5h6zky7y31mkw3_lunyoglxxm0mn4loue8wa5c9kqtw3",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000003e-0000-005f-0000-004b00000038"
+ },
"id": "0000003e-0000-005f-0000-004b00000038"
},
{
"conversation_role": "g1lrkbr7ouvsrch981kwrz1k8un",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000008-0000-0030-0000-004000000062"
+ },
"id": "00000008-0000-0030-0000-004000000062"
},
{
"conversation_role": "7zc5bbxmb4igwsjplqmnttlwrhs4k5dangjj0zvpflv6q6kqfksglq1xq5992v7ce34w3s_s08jfco91s_c4dhbzcyygwfxaty7",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000071-0000-0074-0000-007500000032"
+ },
"id": "00000071-0000-0074-0000-007500000032"
},
{
"conversation_role": "yqpt1iljztlmcsh2u3gt5s_gg1t7x81iwpp8ui501",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000025-0000-003f-0000-000c0000000a"
+ },
"id": "00000025-0000-003f-0000-000c0000000a"
},
{
"conversation_role": "vq6envh0bnegl9x1t",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000006f-0000-007f-0000-007300000013"
+ },
"id": "0000006f-0000-007f-0000-007300000013"
},
{
"conversation_role": "3ehnf28yt11ip57arzrw7pow5m1jsjmcvd3dd5v36aftd38n0612dzjp2pofintyzuue89h_vgk47j0r4jsz4anewa_vko96m",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000001-0000-006b-0000-00200000006a"
+ },
"id": "00000001-0000-006b-0000-00200000006a"
},
{
"conversation_role": "de169f9r4vegvo0tcmv0wd8_tp0jw8c2hpv_q2ya_48gner4ablbfke36imbne2wz2miqc_wsbfp5nmgklu1sv9dnar5ftny4s7_w",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000000-0000-0009-0000-003f00000060"
+ },
"id": "00000000-0000-0009-0000-003f00000060"
},
{
"conversation_role": "191um99jwj93l_cv5zdb6op2a5j3tkismgxlv0jzf90zbw4hi9i611nilzp2i3dq16fj1naa0mdqou9",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000071-0000-0077-0000-004600000064"
+ },
"id": "00000071-0000-0077-0000-004600000064"
},
{
"conversation_role": "b0oxc3cm4deaiuhqlip8cerktwoqbdp_z56h8jeyfc5any",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000005d-0000-0060-0000-000500000063"
+ },
"id": "0000005d-0000-0060-0000-000500000063"
},
{
"conversation_role": "pti7ldmszyimj_wsjq9k0p0z5jb5z3kar759v7tmwifoxgv1mkz2n4igze26p53mr34a4ghcv67fhvdqq4p7h6klye7ndhoezo6hd243gtibdr",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000074-0000-0013-0000-005700000074"
+ },
"id": "00000074-0000-0013-0000-005700000074"
},
{
"conversation_role": "5tsp_2",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000063-0000-0078-0000-001000000046"
+ },
"id": "00000063-0000-0078-0000-001000000046"
},
{
"conversation_role": "xn825hf3etf479oc7vjahb",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000006c-0000-0062-0000-00440000002e"
+ },
"id": "0000006c-0000-0062-0000-00440000002e"
},
{
"conversation_role": "6lyro5zz2erfgps9u1hpzlefe364l1uhhfmynczytotfna4wta_z9gkbxkhsn4zcz6ct1yyliz6fkb1fo4fqlgjs5toi44o81j1e8_oldj",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000070-0000-0030-0000-000e00000075"
+ },
"id": "00000070-0000-0030-0000-000e00000075"
},
{
"conversation_role": "iabgwa1qc9g1jvz8qvs2zf8knqstfk7uxbg8ok9i6jgb7ngqk9f441bxnxgb35uerdky5atdda5g0h8ywv3x83qt",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000007b-0000-0058-0000-003000000071"
+ },
"id": "0000007b-0000-0058-0000-003000000071"
},
{
"conversation_role": "88__aymok2p9flpv0xp5nujwww7ubyxmojd74cim22ixoig78e7ov7vf4s39x3nh85wtr2z0wvsd5lcr48ut9gjgrt1bc7qpo",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000007f-0000-0070-0000-007700000034"
+ },
"id": "0000007f-0000-0070-0000-007700000034"
},
{
"conversation_role": "2ogp8swsxisn1w2bohi8rcvl_1rtx0m34lr6x8sqkt9",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000042-0000-001c-0000-005900000054"
+ },
"id": "00000042-0000-001c-0000-005900000054"
},
{
"conversation_role": "xu52_xbb5yijobss5ls1kkdn_mhgvqkasyfcn1o99ds1pi7zp8lbypszkarhji_bbsdpqylore9zd_woft67s",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000000c-0000-0072-0000-001100000014"
+ },
"id": "0000000c-0000-0072-0000-001100000014"
},
{
"conversation_role": "pma4ikgggpi_q0rvtdvjoff8fztnbolrl6oty_yvxm3qksaeg0l9bh8byrde5mto2f2a1rmn",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000073-0000-0042-0000-005a0000001c"
+ },
"id": "00000073-0000-0042-0000-005a0000001c"
},
{
"conversation_role": "njm9ltp6fr3yk2ke5skszy0xspo7blk",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000029-0000-0042-0000-001a00000071"
+ },
"id": "00000029-0000-0042-0000-001a00000071"
},
{
"conversation_role": "2itlnpkl2w0wh1pso963adsg8psnf8sql_ez6o9qcmy_scfvcvjcin6khn6ye_fqh5z1n52nyqis3wllwnnym6itdqccgr9fk9ttne_h0",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000057-0000-0025-0000-002400000040"
+ },
"id": "00000057-0000-0025-0000-002400000040"
},
{
"conversation_role": "x61tv07e4higron1y",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000009-0000-0034-0000-006c00000009"
+ },
"id": "00000009-0000-0034-0000-006c00000009"
},
{
"conversation_role": "y7ttveh1qbqrww6el6rpjbz13kla3873tu2t",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000071-0000-0069-0000-00310000002b"
+ },
"id": "00000071-0000-0069-0000-00310000002b"
},
{
"conversation_role": "y2791q0e6ve5oof9ep",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000007a-0000-0016-0000-005a0000001f"
+ },
"id": "0000007a-0000-0016-0000-005a0000001f"
},
{
"conversation_role": "db051rwfps8foxf3bqqk8",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000003b-0000-0075-0000-000500000008"
+ },
"id": "0000003b-0000-0075-0000-000500000008"
}
],
diff --git a/libs/wire-api/test/golden/testObject_SimpleMembers_user_20.json b/libs/wire-api/test/golden/testObject_SimpleMembers_user_20.json
index 75bbaada33a..c73e366fa1c 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMembers_user_20.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMembers_user_20.json
@@ -2,58 +2,114 @@
"users": [
{
"conversation_role": "wmx8molqscfxab9_imrcssdgf0_4m2ik51npx6i23vig82mer1rji1xwvddqxasyw6jqmy0xzykd2ums",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000001-0000-0026-0000-00580000001f"
+ },
"id": "00000001-0000-0026-0000-00580000001f"
},
{
"conversation_role": "u0n8uiinlswpdr1oqstlmu1hfv3pfoo7ew8z00r2jvzkpkjpfd3u6kb39_sj73exbhv6a0779b1y69momnis84f5w_3uqlmqcp6on3zj6of7t63nwuxn",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000002b-0000-003b-0000-007600000012"
+ },
"id": "0000002b-0000-003b-0000-007600000012"
},
{
"conversation_role": "lvczhtpd0dgdsvzxtzyelmrbh6dkl17j3drta713mm4i",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000005b-0000-005f-0000-006500000034"
+ },
"id": "0000005b-0000-005f-0000-006500000034"
},
{
"conversation_role": "mhpxkbgdx02x3v2hsjtfm3phl92n4w9ka70i004and0apz620h97vhlp2pxy_moo1op2ipoettcczcxdugw_wus5inhfxzd4fn_jndz0n6wb0kapf76e5r4hldsqqft8",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000002b-0000-0003-0000-001700000060"
+ },
"id": "0000002b-0000-0003-0000-001700000060"
},
{
"conversation_role": "sjkd77e2wg_ddyj59wpadnncaup_41e7m8ayhs936zwkfy5",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000072-0000-0012-0000-000500000052"
+ },
"id": "00000072-0000-0012-0000-000500000052"
},
{
"conversation_role": "3bvwtadqp5w2ode0z",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000003a-0000-002b-0000-00050000002d"
+ },
"id": "0000003a-0000-002b-0000-00050000002d"
},
{
"conversation_role": "2x_5ctyin9ildjj5gs1kwwiw1ipbsdeaqslm8dn8zh02vs1q7id3_whp40e2jgkogpdza5_zm8czqm9ykl_v1",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000047-0000-006c-0000-00500000003b"
+ },
"id": "00000047-0000-006c-0000-00500000003b"
},
{
"conversation_role": "rspqdh94do5jxbxub9t7",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000000-0000-0075-0000-000b0000000b"
+ },
"id": "00000000-0000-0075-0000-000b0000000b"
},
{
"conversation_role": "fqkwb05s1i7aww45jcx5hptvdzd856n2y_8uy5v35zcxhu07jp6v19ax1juyczkgtiiw",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000006d-0000-002b-0000-003800000003"
+ },
"id": "0000006d-0000-002b-0000-003800000003"
},
{
"conversation_role": "fq7zom614_e",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000005c-0000-003c-0000-003d00000059"
+ },
"id": "0000005c-0000-003c-0000-003d00000059"
},
{
"conversation_role": "j3kukcuzzfid3ecr70nzd",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000015-0000-0050-0000-002200000061"
+ },
"id": "00000015-0000-0050-0000-002200000061"
},
{
"conversation_role": "4r6cieh3t_a2ydpm8shzvl_q9ellq1k4sfdpvng5xzqfnwrwwet5wb0m2nzu8ze_dd7nxnauw2ylvv2y57ykt5k9899capc4ke2l2h2yq1wfjzb4zck38xmec61bvl",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000002e-0000-002e-0000-006100000030"
+ },
"id": "0000002e-0000-002e-0000-006100000030"
},
{
"conversation_role": "5v8e6cih_ueu_a2wd28uj8boqxv3gmfx15u1chfrbf_1fupa7fo_yqd",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000010-0000-0030-0000-006c0000006b"
+ },
"id": "00000010-0000-0030-0000-006c0000006b"
},
{
"conversation_role": "dxwk4qalr3oi4jh6v8e3r4agor5vce0b_5w_b3fwmdwfhc3_mqsk94ngdw",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000037-0000-0048-0000-00460000006a"
+ },
"id": "00000037-0000-0048-0000-00460000006a"
}
],
diff --git a/libs/wire-api/test/golden/testObject_SimpleMembers_user_3.json b/libs/wire-api/test/golden/testObject_SimpleMembers_user_3.json
index 066131a9267..72d2a3c36e0 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMembers_user_3.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMembers_user_3.json
@@ -2,26 +2,50 @@
"users": [
{
"conversation_role": "cfz_d4ungmducvtxdmamhrfwox3_ixnvu3lgxutif4hvhqh2gpcdheclk_t1tc2fo6o1f1l4olzojelbqaktba77gshp4jsodxnlvuhfjv_2yc3xd4hqcjatvqibrf0t",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000003-0000-001c-0000-004200000050"
+ },
"id": "00000003-0000-001c-0000-004200000050"
},
{
"conversation_role": "3qmhpz4xwoe0esh_q57fof2a0dntyt1rzwsrii_srhk067ashevn25ypd9ulscohnonbk99kka6lo1fvh2405_",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000025-0000-000b-0000-005200000004"
+ },
"id": "00000025-0000-000b-0000-005200000004"
},
{
"conversation_role": "so7mgd7pd8f5bl2hc28161aqhqht1ii3ysfmj",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000022-0000-0022-0000-00560000005b"
+ },
"id": "00000022-0000-0022-0000-00560000005b"
},
{
"conversation_role": "dcd0mqj2i4w35js",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000065-0000-001d-0000-00530000001a"
+ },
"id": "00000065-0000-001d-0000-00530000001a"
},
{
"conversation_role": "0n5d40stfqajajw2_q70xrtjct7oursrdqbr",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000021-0000-003e-0000-00140000005d"
+ },
"id": "00000021-0000-003e-0000-00140000005d"
},
{
"conversation_role": "6xsb6xh6qstehp328c8pbh4z4nnkjqtv8",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000016-0000-001d-0000-002400000078"
+ },
"id": "00000016-0000-001d-0000-002400000078"
}
],
diff --git a/libs/wire-api/test/golden/testObject_SimpleMembers_user_4.json b/libs/wire-api/test/golden/testObject_SimpleMembers_user_4.json
index 560447a7157..6378f102c82 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMembers_user_4.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMembers_user_4.json
@@ -2,10 +2,18 @@
"users": [
{
"conversation_role": "ev2phc8z5r_qnawlxzgf7ba70oq8yebweiuaoe0cslzfoffdpos4edxi24p1fi09o7535laz7vuh4c96g2tracz4ofyu5fw",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000007f-0000-007b-0000-002c00000013"
+ },
"id": "0000007f-0000-007b-0000-002c00000013"
},
{
"conversation_role": "2dpw9m0sf8_",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000079-0000-0021-0000-007000000061"
+ },
"id": "00000079-0000-0021-0000-007000000061"
}
],
diff --git a/libs/wire-api/test/golden/testObject_SimpleMembers_user_5.json b/libs/wire-api/test/golden/testObject_SimpleMembers_user_5.json
index 5c96f0d01b4..be881b86578 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMembers_user_5.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMembers_user_5.json
@@ -2,62 +2,122 @@
"users": [
{
"conversation_role": "rus8lexaw6nztct_zjcjucxjrs4_atd_3spmbofo3nzlh5ia3llctfiqjs46jw8l6mqazv2pwp93akrkbh6ialmv23yk58_52c34qnbkgdvvephp28",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000053-0000-0076-0000-00100000004e"
+ },
"id": "00000053-0000-0076-0000-00100000004e"
},
{
"conversation_role": "rirdp8hwy4qbgbjktu7oonchq2s_kpntqqffd7eh24isbfkwwq0lgmeo3o7rxbuehhwe6dt99xhao3fswgm0xqa2_ag5as",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000001-0000-0075-0000-006500000014"
+ },
"id": "00000001-0000-0075-0000-006500000014"
},
{
"conversation_role": "48idob6a3qg0k4cyr4x5b3gvqafdeogqtnh_69ov347xwn54j",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000000a-0000-0068-0000-00100000000c"
+ },
"id": "0000000a-0000-0068-0000-00100000000c"
},
{
"conversation_role": "u4ruh9mhqk3m_u6e4guj7ee40_svakap_92pwi89sdme0pkh6inwk4ttg1xmoottx1uy6ryv52w_2lf340g7ohndxa2r3iwue2k3r6c084kifr914ulcon",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000070-0000-000f-0000-002400000032"
+ },
"id": "00000070-0000-000f-0000-002400000032"
},
{
"conversation_role": "rc779wbhz5nabuxyzdrv6n9oiq06olf0",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000002c-0000-0000-0000-007500000037"
+ },
"id": "0000002c-0000-0000-0000-007500000037"
},
{
"conversation_role": "1yeunp1yvablh18tnsoaa8xnggbpbviyabkfh6abd9vw0mrcy3x3gu3m2jvnjhroe44y55c9",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000005c-0000-0009-0000-001700000033"
+ },
"id": "0000005c-0000-0009-0000-001700000033"
},
{
"conversation_role": "opmdgq5u1h4ersm_ydrpyydfv2cdlsj7uwkqaz892ajcmuwi28c197",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000011-0000-004f-0000-007400000055"
+ },
"id": "00000011-0000-004f-0000-007400000055"
},
{
"conversation_role": "nkencc_183b83h8xpvgbgub80wyn9y2mrdj4b3at0t1iuc39e4szcufloiluzpfm1p63ozppoj_xrd0yen3ain9jpmb6nbhjw",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000005b-0000-0076-0000-007f00000034"
+ },
"id": "0000005b-0000-0076-0000-007f00000034"
},
{
"conversation_role": "q12f4o68fsu7kmvmha9h7pqadr37mr7anszmrm4gzyij_ejo78kbtxr85ko8ewzbrz6wuuzm1fwjir67o_0x66_gca2up99w4dzvtjhwsonumkcxx8ffhkln2y7i",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000044-0000-0065-0000-004f00000039"
+ },
"id": "00000044-0000-0065-0000-004f00000039"
},
{
"conversation_role": "d87qu8t82u8q8isnqw_0_55hpuuwnjfvra2ieaogqqdn6iwv8b",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000000f-0000-0059-0000-002100000019"
+ },
"id": "0000000f-0000-0059-0000-002100000019"
},
{
"conversation_role": "flgbw825uiqbe3zydw97iha4be77fnoey3ppqzf6nmwkm8w8gqlwidk6dubc6id4iqqrxb8gbgdrfwwiye1j371xkb2_786vzon9",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000076-0000-0044-0000-00110000002b"
+ },
"id": "00000076-0000-0044-0000-00110000002b"
},
{
"conversation_role": "g43z_vqs1w3nubu3uuvq7eycshex3ug1mz7h50o8k4mu9q1tm_z",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000007c-0000-0016-0000-001200000027"
+ },
"id": "0000007c-0000-0016-0000-001200000027"
},
{
"conversation_role": "toatgflm9kmzec4xpbt596ti99yjqh96g4tp",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000003b-0000-0005-0000-002a00000003"
+ },
"id": "0000003b-0000-0005-0000-002a00000003"
},
{
"conversation_role": "xgpcwa43",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000002c-0000-005d-0000-005e0000005e"
+ },
"id": "0000002c-0000-005d-0000-005e0000005e"
},
{
"conversation_role": "jpp1rdcx0mto46xxx26moxicgo2c2voj_ufk9czxpjt9na71urpd9pyllrnpot4cfj7hjqr1renpy0d2tntwnsq303mws_vxbiyitlavo9yszzlv4s4b996336bt",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000004b-0000-004c-0000-001300000001"
+ },
"id": "0000004b-0000-004c-0000-001300000001"
}
],
diff --git a/libs/wire-api/test/golden/testObject_SimpleMembers_user_6.json b/libs/wire-api/test/golden/testObject_SimpleMembers_user_6.json
index b1e931d7e5d..5c0ce6fb0fa 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMembers_user_6.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMembers_user_6.json
@@ -2,94 +2,186 @@
"users": [
{
"conversation_role": "9adipks6ebghvg1g9a9w1h_oto9w98k8v6i81qpp7l0hk874ixzqqt3qcsjfbscqfyszyz_miodcsoxoz8qlc4405cth5mlo646en1e",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000079-0000-002a-0000-004d0000001d"
+ },
"id": "00000079-0000-002a-0000-004d0000001d"
},
{
"conversation_role": "99uoa6zruc85ailr9e9lu5537qrixoaq1ufioh4uepukbae",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000001f-0000-0042-0000-00530000002e"
+ },
"id": "0000001f-0000-0042-0000-00530000002e"
},
{
"conversation_role": "v7wd6iz93y9034f6eq7u__okr92zkjvkwgtbidzo1wm1r2g1qv7r9vab4mgqiicw3k1i_z21zrf3tfp717rb04q7e",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000049-0000-0022-0000-003f00000045"
+ },
"id": "00000049-0000-0022-0000-003f00000045"
},
{
"conversation_role": "xxj8_x7sgu_7j6fjxshorrc5pn_nwrx1_kft7yl8w2383w5eti15qiu0xzmaqa3913938f_",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000011-0000-002d-0000-004e0000003c"
+ },
"id": "00000011-0000-002d-0000-004e0000003c"
},
{
"conversation_role": "jr8qjzzzoqzxh67eh8qsqp531s0a7ji3a7vtji48tcf56g_fnjn3nax",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000039-0000-0032-0000-006b0000007c"
+ },
"id": "00000039-0000-0032-0000-006b0000007c"
},
{
"conversation_role": "74mkhdsejovwuzzul9lpdysdt1viedz37o1li8o5lxay9hy2il4_7puyks95krk_7w2t4m9lbadx1ay3abk_mxkq0d6ufg2f9ytx_",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000071-0000-0033-0000-006e00000063"
+ },
"id": "00000071-0000-0033-0000-006e00000063"
},
{
"conversation_role": "rk088vrknhjb1qmbzz5b44yziqkeospcorqg3y3f01phq9d7c5ngwhlnq7lich87au3m2yas35ss1vnscom1wv",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000005e-0000-0062-0000-00220000003f"
+ },
"id": "0000005e-0000-0062-0000-00220000003f"
},
{
"conversation_role": "v3m955rl1j5st0fk3t8l0ist2rq5lefq_wt2uwd_h6b1obaa3mt115ph0ukx",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000036-0000-003e-0000-00330000002b"
+ },
"id": "00000036-0000-003e-0000-00330000002b"
},
{
"conversation_role": "zay_s86wwogz16lz8f7rsq40uxgt1j7z0wgj6_t0e6pjvj1n9ri0cpjmjywyqq951ye3jhql4yqb2tjtgc7g6u9yp2_92dcesgftj6l",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000026-0000-0070-0000-006f00000029"
+ },
"id": "00000026-0000-0070-0000-006f00000029"
},
{
"conversation_role": "l_1m2",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000059-0000-0053-0000-005e00000079"
+ },
"id": "00000059-0000-0053-0000-005e00000079"
},
{
"conversation_role": "e65xwo7h8khwqvfvmvj02jj2jkz3wa8bei_",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000001a-0000-0069-0000-002e00000072"
+ },
"id": "0000001a-0000-0069-0000-002e00000072"
},
{
"conversation_role": "ly60ylpqtqx3vqe00gw",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000006c-0000-000a-0000-00470000007f"
+ },
"id": "0000006c-0000-000a-0000-00470000007f"
},
{
"conversation_role": "0obmr5yj455s75alew",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000001c-0000-0019-0000-007300000070"
+ },
"id": "0000001c-0000-0019-0000-007300000070"
},
{
"conversation_role": "zlbldhai8hpxijlergrh38ixsxhau35d5_gkejdkgeuz1w6_ojxzrscj3r2wbmhyi175ls73yp0w4schak_f_e8",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000017-0000-0046-0000-003e0000005c"
+ },
"id": "00000017-0000-0046-0000-003e0000005c"
},
{
"conversation_role": "y9dwyc8k9u40pa5h684208vcrlnoryjjekb_l623h1f05gm__mwvcr41m08r4t1kcjrvyfm559vc3_h63gpe2",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000000f-0000-005f-0000-006200000042"
+ },
"id": "0000000f-0000-005f-0000-006200000042"
},
{
"conversation_role": "_b64sbeqrp8ou_09bincmxn3",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000000d-0000-0027-0000-002300000072"
+ },
"id": "0000000d-0000-0027-0000-002300000072"
},
{
"conversation_role": "vm28h5wk3tzyeng8e0_kge5k61ws2ab21l5hl5fhf63n2171lxrnibaju6wy9oqhy9804c5sry_0xqw6_hb4ebrddbb1i3huqtz_cbdudqglp3yap74qjol1m2vtx",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000005a-0000-004f-0000-00700000006b"
+ },
"id": "0000005a-0000-004f-0000-00700000006b"
},
{
"conversation_role": "jstp31co2rpas6er_oyazeow51_1aho0uqvdvu6uqv2",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000001-0000-0015-0000-000a0000000c"
+ },
"id": "00000001-0000-0015-0000-000a0000000c"
},
{
"conversation_role": "0gk_nlyr50ot8v0s39c1wgjry6z3e78hcjtv2wmcb397ojix5l8p47tlmsvw",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000005f-0000-003c-0000-007600000061"
+ },
"id": "0000005f-0000-003c-0000-007600000061"
},
{
"conversation_role": "cm",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000076-0000-002c-0000-002d00000016"
+ },
"id": "00000076-0000-002c-0000-002d00000016"
},
{
"conversation_role": "e9kre1i15j22d",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000074-0000-001e-0000-007400000071"
+ },
"id": "00000074-0000-001e-0000-007400000071"
},
{
"conversation_role": "nb8blnutjrsntylz671x",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000051-0000-007c-0000-007b00000025"
+ },
"id": "00000051-0000-007c-0000-007b00000025"
},
{
"conversation_role": "sq3if4ijdg5pndfza05zyqz5u6ae3oa2u23bazsc870ijzlsvgj41s2b88zu2d3gvi7h3s_byd35y2izjlblss3v712a3_7v",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000046-0000-0031-0000-004400000018"
+ },
"id": "00000046-0000-0031-0000-004400000018"
}
],
diff --git a/libs/wire-api/test/golden/testObject_SimpleMembers_user_7.json b/libs/wire-api/test/golden/testObject_SimpleMembers_user_7.json
index c154b385c7d..419ca63fb14 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMembers_user_7.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMembers_user_7.json
@@ -2,122 +2,242 @@
"users": [
{
"conversation_role": "eldahjnjgyux49p6u4qxz9a0q7e",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000054-0000-0012-0000-005900000011"
+ },
"id": "00000054-0000-0012-0000-005900000011"
},
{
"conversation_role": "yw31a4ikpn_zfb5fd0vee3e1536ak74rqp_qtok7xrhsn5pa",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000000e-0000-004f-0000-003b0000004e"
+ },
"id": "0000000e-0000-004f-0000-003b0000004e"
},
{
"conversation_role": "9unyjjpklvq_r33t7qkqerx02wummtzrlrscqdm7gyi3vp4t9elyttg0rob3cv3lz8gni_fqr_df3rvt2o7gv",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000062-0000-0055-0000-00600000003d"
+ },
"id": "00000062-0000-0055-0000-00600000003d"
},
{
"conversation_role": "810s8rqja",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000049-0000-0058-0000-005000000000"
+ },
"id": "00000049-0000-0058-0000-005000000000"
},
{
"conversation_role": "yb41udiftgjzo36lbvwtw9xj5qlohvljde90frfx0r26jzgpq08xeo4xw2tepnvx",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000005a-0000-0020-0000-005400000016"
+ },
"id": "0000005a-0000-0020-0000-005400000016"
},
{
"conversation_role": "eyqn_qzpmumu9cvf_6zn8ya0eucpkdjjwwb41pce90xd4buem9o1tp4bprvjzkudtsyvphunmxanf0ej4uad7pbj48t5xemr93bcqb1j97owyuome59njkvznhlpew",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000018-0000-002f-0000-006b00000044"
+ },
"id": "00000018-0000-002f-0000-006b00000044"
},
{
"conversation_role": "zze7ew9qk8gurfh",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000007a-0000-0058-0000-001200000069"
+ },
"id": "0000007a-0000-0058-0000-001200000069"
},
{
"conversation_role": "n_roifhghi_l_9b_75beixjh703zyg806b1hin3fui2nj3nj040_7r3ijtyfox4o3o",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000006f-0000-0018-0000-006f00000076"
+ },
"id": "0000006f-0000-0018-0000-006f00000076"
},
{
"conversation_role": "pgdgmdhjek3iah8ywvcdue4k6yn43l5_zme6o6_yatkktrw5s_ovqmyrwgu_z_5rp9z97jgtv620f0_177cv1s3urmbx406w50dot_yojdsjncunk9hqnl8j",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000002d-0000-0074-0000-003f00000059"
+ },
"id": "0000002d-0000-0074-0000-003f00000059"
},
{
"conversation_role": "kue8gimoaxn3wdbzwb2l9ygk0",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000057-0000-0078-0000-005700000001"
+ },
"id": "00000057-0000-0078-0000-005700000001"
},
{
"conversation_role": "jzojg1avl60svt8cnpecbdisbcd6eq9ru2nql33f9ccivtelrga_ls_3iao_dlfh9vj8jrne_nbwmb_73ay2bs3qoyamx5qgd",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000078-0000-0080-0000-002b0000005c"
+ },
"id": "00000078-0000-0080-0000-002b0000005c"
},
{
"conversation_role": "oz_bg293nizgs3gz2bm2mnulgpnb9jm_pd8uox2qiok86rndsgyqpj5c6l4iqrh4sj8y5ifgo23lja2tq6kwj9e9m07s59112xdypqgaxzf9py_muyd9",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000004-0000-007f-0000-007c00000022"
+ },
"id": "00000004-0000-007f-0000-007c00000022"
},
{
"conversation_role": "hdirky_2tz3se2ehu7by2csj7a_jy7qyo1oghueqc_4h118v79xz49olrwkojns",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000006f-0000-0011-0000-004100000002"
+ },
"id": "0000006f-0000-0011-0000-004100000002"
},
{
"conversation_role": "9122iz2g941gnqs08mcaqa33l58irkmohj5r",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000042-0000-002f-0000-00030000007d"
+ },
"id": "00000042-0000-002f-0000-00030000007d"
},
{
"conversation_role": "0lyosg4pvc5q1dazb5z1v59plf2nqgs",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000027-0000-0014-0000-00250000003b"
+ },
"id": "00000027-0000-0014-0000-00250000003b"
},
{
"conversation_role": "01yernwfzht9wjtfyi_jr3mq9cjcsobvwlenbkhpqlmhu9clagiiyoaw2b48mfzx_mgqib99ol3vezr0t0qeu",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000000e-0000-0055-0000-002200000000"
+ },
"id": "0000000e-0000-0055-0000-002200000000"
},
{
"conversation_role": "qb122l4lbi0oy7n5jsv1brin8k4gn1c_5_w0dq4avhnbvd32flikjynd_s0myf3sn2l1c7freo1uvflhcvjuvtrtpwumg5h8atn933stgizpnrc_1kfo0",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000006a-0000-0009-0000-003d0000007f"
+ },
"id": "0000006a-0000-0009-0000-003d0000007f"
},
{
"conversation_role": "mzqpku2_1n5f5_c8_zcmv4tejpe4ny41dkg1n067dupdvy7snm24y8syoe2agwc1h8yts_lp59v1aj4dr4sna8cpsgpd2td66xlw1hj_rm27lpiqn",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000018-0000-000d-0000-006d00000072"
+ },
"id": "00000018-0000-000d-0000-006d00000072"
},
{
"conversation_role": "hcy75iscpnouf9aqpon3edkh4uln4gma0niecrde5",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000031-0000-0023-0000-005f0000004a"
+ },
"id": "00000031-0000-0023-0000-005f0000004a"
},
{
"conversation_role": "3jw7u98t20zwu57swxs82genekuvg_hol6pcq5597l858iwgx8vs6anpiguoxetm8_l2e18ww09_xeiytzs64m5dadcmzpn5okzf35moy271z",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000063-0000-0061-0000-006900000077"
+ },
"id": "00000063-0000-0061-0000-006900000077"
},
{
"conversation_role": "y0u0avpt3orbo6xcee13613ik0sb8xcz308vkb5u33q9np2ws_pvhakw3gjbtihe3",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000002e-0000-0003-0000-00790000001d"
+ },
"id": "0000002e-0000-0003-0000-00790000001d"
},
{
"conversation_role": "ltula3ev2qfdixfbbpspfniw6xgmt4nmn0l1omcihhhkezinnxivgv81d13juourjrc0uqyl7gia0igc4keazm2avjra_ncnbfwy34uv95nbqopikwtb8d2",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000052-0000-0054-0000-003e0000005b"
+ },
"id": "00000052-0000-0054-0000-003e0000005b"
},
{
"conversation_role": "3n2q64e9ea8hxbcwm9n4mlyy330f1zoiaq_ao1d_t90kr4sahr365ji7svmbr6k58bx7o0bjeqij",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000062-0000-0049-0000-005000000022"
+ },
"id": "00000062-0000-0049-0000-005000000022"
},
{
"conversation_role": "x7shwqzfrj3qnlvus111ufwgzstnmmob_xhzern6niel5pahgi1_",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000007-0000-0069-0000-000f00000032"
+ },
"id": "00000007-0000-0069-0000-000f00000032"
},
{
"conversation_role": "1tv5og06r1a2al4kc",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000003-0000-0057-0000-00680000003c"
+ },
"id": "00000003-0000-0057-0000-00680000003c"
},
{
"conversation_role": "l_qq7b7wyz3ulnpim8dbd9g9bfv89yo_ioq9txnktyl81tkyvw0kx35u658o2_xuiuabbdslo9gxvb7p3i93nc7_tqm",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000047-0000-006c-0000-003200000031"
+ },
"id": "00000047-0000-006c-0000-003200000031"
},
{
"conversation_role": "uvsqtx_7v0_odhu95uke30sh454iruq9",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000074-0000-0059-0000-006800000037"
+ },
"id": "00000074-0000-0059-0000-006800000037"
},
{
"conversation_role": "gs2uzo7gnwot5qm61hyvd7n12n3mra138j0wex17zdhp01hwewiklyvz39e554xf_8us1abd_pysw_rjso9ujz35prg5g68omtevrtb7n1pcp9io681k77jpvj474tkw",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000050-0000-0053-0000-006f0000005d"
+ },
"id": "00000050-0000-0053-0000-006f0000005d"
},
{
"conversation_role": "ccqqr3w57f9exl7xuhqnr305fqteeziw7hr374is9pkpjtt_z",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000037-0000-004d-0000-003e00000004"
+ },
"id": "00000037-0000-004d-0000-003e00000004"
},
{
"conversation_role": "cbowqop368_f44bm2whf8hhkcu5ljs7u930a2lpwirkq4k3sgl56hvj4t8xj33sikbtxznli2ireniu5zvcm4",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000039-0000-0024-0000-003500000002"
+ },
"id": "00000039-0000-0024-0000-003500000002"
}
],
diff --git a/libs/wire-api/test/golden/testObject_SimpleMembers_user_8.json b/libs/wire-api/test/golden/testObject_SimpleMembers_user_8.json
index d8fa68906c8..5c9aef25354 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMembers_user_8.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMembers_user_8.json
@@ -2,42 +2,82 @@
"users": [
{
"conversation_role": "bmgvnfheg7304j1af2ha8kzlrdsd94sla01p8e32cfuchc4n4d4j_1",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000067-0000-003f-0000-003300000052"
+ },
"id": "00000067-0000-003f-0000-003300000052"
},
{
"conversation_role": "3z44kbvkfmhwt3cxvztk91xwigzfsqgmwx43rsi2ew7_663q5kd04afdhwes23ea8_7nn4j6hol2k1o",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000066-0000-001c-0000-005900000006"
+ },
"id": "00000066-0000-001c-0000-005900000006"
},
{
"conversation_role": "_5xab39_3f5_n1jexf9q06jn5c0kx2wszftbp77dq3p5wxon_cg0sgxn38hr4p28i1u20rtg01mhf_xjn3tradschh7vm2ek6hpp788h4w47cnmzwo17lp56h5k",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000013-0000-0072-0000-005000000019"
+ },
"id": "00000013-0000-0072-0000-005000000019"
},
{
"conversation_role": "qann8z5wp43fncbnzkxuqeskdrnxclmj1qoiri6zb4ro8jzbsewewgi27xi6pnc",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000006d-0000-003f-0000-007f00000025"
+ },
"id": "0000006d-0000-003f-0000-007f00000025"
},
{
"conversation_role": "552en9ubk7gjrv",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000019-0000-003a-0000-006500000036"
+ },
"id": "00000019-0000-003a-0000-006500000036"
},
{
"conversation_role": "4fq8ylocoheanwuq9kg6amnrks",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000005d-0000-0008-0000-006000000011"
+ },
"id": "0000005d-0000-0008-0000-006000000011"
},
{
"conversation_role": "9ptg6nyzbr58czopzu0a26w3d1kvnl1zbyqij9j2p10o75869aargj9p3b5vxl9r27eryt6z5o85rlhgvrb4l50tb3jfil3hrlylru05",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000007b-0000-0072-0000-00690000000e"
+ },
"id": "0000007b-0000-0072-0000-00690000000e"
},
{
"conversation_role": "tq",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000032-0000-0039-0000-007700000022"
+ },
"id": "00000032-0000-0039-0000-007700000022"
},
{
"conversation_role": "as91oohpdy",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000001a-0000-003b-0000-001e00000080"
+ },
"id": "0000001a-0000-003b-0000-001e00000080"
},
{
"conversation_role": "0jr8eycubw7cut6ukuegnxp5b2obst6ry8y76fe2qjro3xpp3bjvxg4c707rs1jlf",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000056-0000-005b-0000-007c00000078"
+ },
"id": "00000056-0000-005b-0000-007c00000078"
}
],
diff --git a/libs/wire-api/test/golden/testObject_SimpleMembers_user_9.json b/libs/wire-api/test/golden/testObject_SimpleMembers_user_9.json
index de993083ee0..061568889f3 100644
--- a/libs/wire-api/test/golden/testObject_SimpleMembers_user_9.json
+++ b/libs/wire-api/test/golden/testObject_SimpleMembers_user_9.json
@@ -2,78 +2,154 @@
"users": [
{
"conversation_role": "85wkc4m6uzi3t_s5sb488cxhjl7i_av_erwfdtgya58oc",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000001a-0000-0051-0000-001100000064"
+ },
"id": "0000001a-0000-0051-0000-001100000064"
},
{
"conversation_role": "34mo_jfmsyatdcjbdl_o0hpvrc8uutf8ni3vukdy6bozbmf5itp3wsy502jw3b3y9oudqo2lh71ro8id7yvebq_4pxi98i3jdzyrx",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000053-0000-007f-0000-00140000007a"
+ },
"id": "00000053-0000-007f-0000-00140000007a"
},
{
"conversation_role": "pc12u7vhdqloizph96i1elxofyps02qanrr2z6_kdvl3zakyappxu7nksvj6oe6yz_ygrgii0v",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000001b-0000-0042-0000-003800000032"
+ },
"id": "0000001b-0000-0042-0000-003800000032"
},
{
"conversation_role": "3v2qmyq9xk_tk7nywcco8dz4s_hgsd99cyu73lq8imj7xi09i8ha6nxj0mid6meivcq5wanubww2kpdpiousiel3ea7g5g7e1dggpuctjvbo9n5",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000032-0000-0032-0000-002000000005"
+ },
"id": "00000032-0000-0032-0000-002000000005"
},
{
"conversation_role": "jcr0d3sv5pm89mkbhinm7aw5njyj0oft6vh8ste7xfn6feqkmx176x93ie9lc58kcik7",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000001-0000-0021-0000-007000000010"
+ },
"id": "00000001-0000-0021-0000-007000000010"
},
{
"conversation_role": "x2qhpqv6n90nosm7tt6xs_zwathnk0l2jgp3om52fmpsz7x54y9061oxncf4v_3_10tlvx11vi57riuxn25gotw7",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000001b-0000-004b-0000-00480000000a"
+ },
"id": "0000001b-0000-004b-0000-00480000000a"
},
{
"conversation_role": "xeyznxh248frdc3jix0_32kir1jobft0g60b75rx9c0x4wk171xseai9irwra9eypmmbplmw6hckha8f0i6zz",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000012-0000-0000-0000-00690000005d"
+ },
"id": "00000012-0000-0000-0000-00690000005d"
},
{
"conversation_role": "40d1mp1rlpq_toli_xsrzzp6azj7abwn9kwyyexu8mzqanezqlkwgzs_maqszagustta7197hluh",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000024-0000-002c-0000-000a0000002b"
+ },
"id": "00000024-0000-002c-0000-000a0000002b"
},
{
"conversation_role": "bcxu51qy7gzxryiesnjqirt5dn7kb0lz0nsdf2fbgxjatcf486n210ndta21lli8b64ub3xnerb84atj9_1xjz4kaowbda_rhxgz5qq264g6ikd6r7m1",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000000e-0000-002f-0000-006c0000007e"
+ },
"id": "0000000e-0000-002f-0000-006c0000007e"
},
{
"conversation_role": "8qz3xbrnjl34e24fvc96wl34jw",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000001d-0000-000c-0000-001200000072"
+ },
"id": "0000001d-0000-000c-0000-001200000072"
},
{
"conversation_role": "3mcna2fo1fuhmz50gevjyc5iacna3hon9fylu4o9u48",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000065-0000-0005-0000-00160000007a"
+ },
"id": "00000065-0000-0005-0000-00160000007a"
},
{
"conversation_role": "q451zrfmym52a86mm41yg1zhb3hgv38i_3qe5l4uhjlz0cum77qlytubryh5s8oya7ql_s5cnseh27vi1rzzcow",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000002e-0000-0028-0000-00240000001f"
+ },
"id": "0000002e-0000-0028-0000-00240000001f"
},
{
"conversation_role": "cxx03t4219b0e3b7u5lwxb4ua_3qif069vharpluygxmxq5vd1hcx4_3yjmtgw99yz",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000061-0000-0035-0000-005100000068"
+ },
"id": "00000061-0000-0035-0000-005100000068"
},
{
"conversation_role": "p5ro0m08a808fnqrib4ikm2bz71wvwxs_qa0b7xeneh6q38ucu8n3nq6uw3w3yelajevfdbsw64vqbsbvx0fsmpis3zbwr73pm7srdls_8nrdr4urapsui3goem5zy",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000003-0000-0034-0000-00520000005a"
+ },
"id": "00000003-0000-0034-0000-00520000005a"
},
{
"conversation_role": "op23mrkoau967yyy74znf7smfsr1j46m",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000007-0000-0049-0000-004c00000028"
+ },
"id": "00000007-0000-0049-0000-004c00000028"
},
{
"conversation_role": "zzcga0i7uawt0riq_mknqyn98zmawbd__zaf1s0hihhmp3o8vucuv3hlmeem5247e_1i2vml18qcoez3epg9kpnufn_w704s3t74u4yc27d0hkg3a6flr",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "0000006c-0000-0074-0000-004100000005"
+ },
"id": "0000006c-0000-0074-0000-004100000005"
},
{
"conversation_role": "66z2l9nijttcg_yu5krtv_llxbwwkdyosut9qmra_3bpeithetio5snkbicofi58z6gr4a_benvx87km99ffgi320rz454xd9s_42kzu8h8g3x0rx98xymh_3",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000000-0000-004d-0000-002a00000020"
+ },
"id": "00000000-0000-004d-0000-002a00000020"
},
{
"conversation_role": "uw5x_u9rn2zu0nc6f7eb_v40",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000058-0000-005d-0000-00100000000a"
+ },
"id": "00000058-0000-005d-0000-00100000000a"
},
{
"conversation_role": "u0pnq5ipcjjbf2tdndpn5pt0hic9tj6hjrzbscf9_mr5dimm_5e1i1xvo1lppo3ccvlh610tldxpgg2tjpf_nhhqz_gff",
+ "qualified_id": {
+ "domain": "faraway.example.com",
+ "id": "00000045-0000-0044-0000-00070000005c"
+ },
"id": "00000045-0000-0044-0000-00070000005c"
}
],
diff --git a/libs/wire-api/test/unit/Test/Wire/API/Golden/FromJSON.hs b/libs/wire-api/test/unit/Test/Wire/API/Golden/FromJSON.hs
index 925f74c1c0d..f5df12de783 100644
--- a/libs/wire-api/test/unit/Test/Wire/API/Golden/FromJSON.hs
+++ b/libs/wire-api/test/unit/Test/Wire/API/Golden/FromJSON.hs
@@ -20,7 +20,9 @@ module Test.Wire.API.Golden.FromJSON where
import Imports
import Test.Tasty
import Test.Tasty.HUnit
+import Test.Wire.API.Golden.Generated.NewConvUnmanaged_user
import Test.Wire.API.Golden.Generated.NewOtrMessage_user
+import Test.Wire.API.Golden.Generated.SimpleMember_user
import Test.Wire.API.Golden.Runner
tests :: TestTree
@@ -29,5 +31,11 @@ tests =
"FromJSON golden tests"
[ testCase ("NewOtrMessage") $
testFromJSONObjects
- [(testObject_NewOtrMessage_user_1, "testObject_NewOtrMessage_user_1.json")]
+ [(testObject_NewOtrMessage_user_1, "testObject_NewOtrMessage_user_1.json")],
+ testCase "SimpleMember" $
+ testFromJSONObjects
+ [(testObject_SimpleMember_user_1, "testObject_SimpleMember_user_1.json")],
+ testCase "NewConv" $
+ testFromJSONObjects
+ [(testObject_NewConvUnmanaged_user_1, "testObject_NewConvUnmanaged_user_1.json")]
]
diff --git a/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/AddBotResponse_user.hs b/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/AddBotResponse_user.hs
index 6f87f962d23..202b309446b 100644
--- a/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/AddBotResponse_user.hs
+++ b/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/AddBotResponse_user.hs
@@ -20,8 +20,10 @@ module Test.Wire.API.Golden.Generated.AddBotResponse_user where
import Data.Code (Key (Key, asciiKey), Value (Value, asciiValue))
import Data.Coerce (coerce)
+import Data.Domain
import Data.Id (BotId (BotId), ClientId (ClientId, client), Id (Id))
import Data.Misc (HttpsUrl (HttpsUrl), Milliseconds (Ms, ms))
+import Data.Qualified
import Data.Range (unsafeRange)
import Data.Text.Ascii (AsciiChars (validate))
import qualified Data.UUID as UUID (fromString)
@@ -135,7 +137,7 @@ import Wire.API.Event.Conversation
misOtrMutedStatus,
misTarget
),
- SimpleMember (SimpleMember, smConvRoleName, smId),
+ SimpleMember (..),
SimpleMembers (SimpleMembers, mMembers),
UserIdList (UserIdList, mUsers),
)
@@ -162,8 +164,8 @@ testObject_AddBotResponse_user_1 =
rsAddBotEvent =
( Event
(ConvRename)
- ((Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000200000003"))))
- ((Id (fromJust (UUID.fromString "00000004-0000-0004-0000-000400000004"))))
+ (Qualified (Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000200000003"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00000004-0000-0004-0000-000400000004"))) (Domain "faraway.example.com"))
(read "1864-05-12 19:20:22.286 UTC")
((EdConvRename (ConversationRename {cupName = "6"})))
)
@@ -184,8 +186,8 @@ testObject_AddBotResponse_user_2 =
rsAddBotEvent =
( Event
(Typing)
- ((Id (fromJust (UUID.fromString "00000000-0000-0002-0000-000300000001"))))
- ((Id (fromJust (UUID.fromString "00000004-0000-0000-0000-000300000001"))))
+ (Qualified (Id (fromJust (UUID.fromString "00000000-0000-0002-0000-000300000001"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00000004-0000-0000-0000-000300000001"))) (Domain "faraway.example.com"))
(read "1864-05-08 19:02:58.6 UTC")
((EdTyping (TypingData {tdStatus = StartedTyping})))
)
@@ -211,8 +213,8 @@ testObject_AddBotResponse_user_3 =
rsAddBotEvent =
( Event
(ConvCreate)
- ((Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000003"))))
- ((Id (fromJust (UUID.fromString "00000000-0000-0004-0000-000400000004"))))
+ (Qualified (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000003"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00000000-0000-0004-0000-000400000004"))) (Domain "faraway.example.com"))
(read "1864-05-10 11:22:13.523 UTC")
( ( EdConversation
( Conversation
@@ -264,8 +266,8 @@ testObject_AddBotResponse_user_4 =
rsAddBotEvent =
( Event
(ConvDelete)
- ((Id (fromJust (UUID.fromString "00000003-0000-0002-0000-000300000003"))))
- ((Id (fromJust (UUID.fromString "00000001-0000-0002-0000-000300000001"))))
+ (Qualified (Id (fromJust (UUID.fromString "00000003-0000-0002-0000-000300000003"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00000001-0000-0002-0000-000300000001"))) (Domain "faraway.example.com"))
(read "1864-05-06 03:03:10.788 UTC")
(EdConvDelete)
)
@@ -290,8 +292,8 @@ testObject_AddBotResponse_user_5 =
rsAddBotEvent =
( Event
(ConvCreate)
- ((Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000001"))))
- ((Id (fromJust (UUID.fromString "00000001-0000-0004-0000-000000000002"))))
+ (Qualified (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000001"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00000001-0000-0004-0000-000000000002"))) (Domain "faraway.example.com"))
(read "1864-05-13 21:19:26.488 UTC")
( ( EdConversation
( Conversation
@@ -356,8 +358,8 @@ testObject_AddBotResponse_user_6 =
rsAddBotEvent =
( Event
(ConvCodeUpdate)
- ((Id (fromJust (UUID.fromString "00000003-0000-0001-0000-000000000003"))))
- ((Id (fromJust (UUID.fromString "00000002-0000-0004-0000-000300000001"))))
+ (Qualified (Id (fromJust (UUID.fromString "00000003-0000-0001-0000-000000000003"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00000002-0000-0004-0000-000300000001"))) (Domain "faraway.example.com"))
(read "1864-05-14 23:40:44.551 UTC")
( ( EdConvCodeUpdate
( ConversationCode
@@ -405,8 +407,8 @@ testObject_AddBotResponse_user_7 =
rsAddBotEvent =
( Event
(ConvReceiptModeUpdate)
- ((Id (fromJust (UUID.fromString "00000000-0000-0004-0000-000300000004"))))
- ((Id (fromJust (UUID.fromString "00000003-0000-0001-0000-000400000001"))))
+ (Qualified (Id (fromJust (UUID.fromString "00000000-0000-0004-0000-000300000004"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00000003-0000-0001-0000-000400000001"))) (Domain "faraway.example.com"))
(read "1864-05-07 22:30:05.775 UTC")
((EdConvReceiptModeUpdate (ConversationReceiptModeUpdate {cruReceiptMode = ReceiptMode {unReceiptMode = -4}})))
)
@@ -427,8 +429,8 @@ testObject_AddBotResponse_user_8 =
rsAddBotEvent =
( Event
(ConvAccessUpdate)
- ((Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000200000003"))))
- ((Id (fromJust (UUID.fromString "00000004-0000-0000-0000-000400000000"))))
+ (Qualified (Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000200000003"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00000004-0000-0000-0000-000400000000"))) (Domain "faraway.example.com"))
(read "1864-05-05 09:04:05.078 UTC")
( ( EdConvAccessUpdate
(ConversationAccessUpdate {cupAccess = [LinkAccess, PrivateAccess], cupAccessRole = ActivatedAccessRole})
@@ -453,8 +455,8 @@ testObject_AddBotResponse_user_9 =
rsAddBotEvent =
( Event
(MemberStateUpdate)
- ((Id (fromJust (UUID.fromString "00000002-0000-0004-0000-000000000004"))))
- ((Id (fromJust (UUID.fromString "00000002-0000-0002-0000-000100000002"))))
+ (Qualified (Id (fromJust (UUID.fromString "00000002-0000-0004-0000-000000000004"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00000002-0000-0002-0000-000100000002"))) (Domain "faraway.example.com"))
(read "1864-05-07 17:13:06.966 UTC")
( ( EdMemberUpdate
( MemberUpdateData
@@ -489,8 +491,8 @@ testObject_AddBotResponse_user_10 =
rsAddBotEvent =
( Event
(MemberLeave)
- ((Id (fromJust (UUID.fromString "00000002-0000-0004-0000-000400000001"))))
- ((Id (fromJust (UUID.fromString "00000002-0000-0001-0000-000200000000"))))
+ (Qualified (Id (fromJust (UUID.fromString "00000002-0000-0004-0000-000400000001"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00000002-0000-0001-0000-000200000000"))) (Domain "faraway.example.com"))
(read "1864-05-04 10:22:33.842 UTC")
((EdMembersLeave (UserIdList {mUsers = []})))
)
@@ -511,8 +513,8 @@ testObject_AddBotResponse_user_11 =
rsAddBotEvent =
( Event
(ConvAccessUpdate)
- ((Id (fromJust (UUID.fromString "00000000-0000-0003-0000-000300000004"))))
- ((Id (fromJust (UUID.fromString "00000003-0000-0003-0000-000100000000"))))
+ (Qualified (Id (fromJust (UUID.fromString "00000000-0000-0003-0000-000300000004"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00000003-0000-0003-0000-000100000000"))) (Domain "faraway.example.com"))
(read "1864-05-04 14:10:34.032 UTC")
( ( EdConvAccessUpdate
(ConversationAccessUpdate {cupAccess = [CodeAccess], cupAccessRole = ActivatedAccessRole})
@@ -532,8 +534,8 @@ testObject_AddBotResponse_user_12 =
rsAddBotEvent =
( Event
(ConvConnect)
- ((Id (fromJust (UUID.fromString "00000003-0000-0000-0000-000200000000"))))
- ((Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000002"))))
+ (Qualified (Id (fromJust (UUID.fromString "00000003-0000-0000-0000-000200000000"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000002"))) (Domain "faraway.example.com"))
(read "1864-05-05 01:06:47.245 UTC")
( ( EdConnect
( Connect
@@ -569,8 +571,8 @@ testObject_AddBotResponse_user_13 =
rsAddBotEvent =
( Event
(ConvReceiptModeUpdate)
- ((Id (fromJust (UUID.fromString "00000000-0000-0003-0000-000100000001"))))
- ((Id (fromJust (UUID.fromString "00000004-0000-0001-0000-000400000002"))))
+ (Qualified (Id (fromJust (UUID.fromString "00000000-0000-0003-0000-000100000001"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00000004-0000-0001-0000-000400000002"))) (Domain "faraway.example.com"))
(read "1864-05-13 05:09:37.371 UTC")
((EdConvReceiptModeUpdate (ConversationReceiptModeUpdate {cruReceiptMode = ReceiptMode {unReceiptMode = 3}})))
)
@@ -591,8 +593,8 @@ testObject_AddBotResponse_user_14 =
rsAddBotEvent =
( Event
(MemberStateUpdate)
- ((Id (fromJust (UUID.fromString "00000001-0000-0004-0000-000000000004"))))
- ((Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000200000001"))))
+ (Qualified (Id (fromJust (UUID.fromString "00000001-0000-0004-0000-000000000004"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000200000001"))) (Domain "faraway.example.com"))
(read "1864-05-13 06:48:06.601 UTC")
( ( EdMemberUpdate
( MemberUpdateData
@@ -632,14 +634,14 @@ testObject_AddBotResponse_user_15 =
rsAddBotEvent =
( Event
(MemberJoin)
- ((Id (fromJust (UUID.fromString "00000002-0000-0001-0000-000100000000"))))
- ((Id (fromJust (UUID.fromString "00000003-0000-0001-0000-000200000003"))))
+ (Qualified (Id (fromJust (UUID.fromString "00000002-0000-0001-0000-000100000000"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00000003-0000-0001-0000-000200000003"))) (Domain "faraway.example.com"))
(read "1864-05-11 04:21:51.377 UTC")
( ( EdMembersJoin
( SimpleMembers
{ mMembers =
[ SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000002-0000-0002-0000-000200000001"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000002-0000-0002-0000-000200000001"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -648,7 +650,7 @@ testObject_AddBotResponse_user_15 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000001"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000001"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -657,12 +659,12 @@ testObject_AddBotResponse_user_15 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000002"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000002"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "jf7f75hkum6_zxqiabxu8zix2_1kutsjijedcjckapwmymcxx11"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000000000002"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000000000002"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -693,8 +695,8 @@ testObject_AddBotResponse_user_16 =
rsAddBotEvent =
( Event
(ConvRename)
- ((Id (fromJust (UUID.fromString "00000004-0000-0003-0000-000200000000"))))
- ((Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000200000001"))))
+ (Qualified (Id (fromJust (UUID.fromString "00000004-0000-0003-0000-000200000000"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000200000001"))) (Domain "faraway.example.com"))
(read "1864-05-07 11:54:38.133 UTC")
((EdConvRename (ConversationRename {cupName = "\72291)@\16969"})))
)
@@ -711,8 +713,8 @@ testObject_AddBotResponse_user_17 =
rsAddBotEvent =
( Event
(ConvAccessUpdate)
- ((Id (fromJust (UUID.fromString "00000003-0000-0002-0000-000100000002"))))
- ((Id (fromJust (UUID.fromString "00000003-0000-0000-0000-000400000001"))))
+ (Qualified (Id (fromJust (UUID.fromString "00000003-0000-0002-0000-000100000002"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00000003-0000-0000-0000-000400000001"))) (Domain "faraway.example.com"))
(read "1864-05-09 16:18:32.395 UTC")
( ( EdConvAccessUpdate
( ConversationAccessUpdate
@@ -746,8 +748,8 @@ testObject_AddBotResponse_user_18 =
rsAddBotEvent =
( Event
(ConvRename)
- ((Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000001"))))
- ((Id (fromJust (UUID.fromString "00000001-0000-0002-0000-000000000003"))))
+ (Qualified (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000001"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00000001-0000-0002-0000-000000000003"))) (Domain "faraway.example.com"))
(read "1864-05-13 04:14:10.186 UTC")
((EdConvRename (ConversationRename {cupName = "+S\994417x1"})))
)
@@ -764,8 +766,8 @@ testObject_AddBotResponse_user_19 =
rsAddBotEvent =
( Event
(ConvCodeDelete)
- ((Id (fromJust (UUID.fromString "00000003-0000-0003-0000-000000000000"))))
- ((Id (fromJust (UUID.fromString "00000004-0000-0003-0000-000400000002"))))
+ (Qualified (Id (fromJust (UUID.fromString "00000003-0000-0003-0000-000000000000"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00000004-0000-0003-0000-000400000002"))) (Domain "faraway.example.com"))
(read "1864-05-14 03:03:50.569 UTC")
(EdConvCodeDelete)
)
@@ -786,8 +788,8 @@ testObject_AddBotResponse_user_20 =
rsAddBotEvent =
( Event
(ConvMessageTimerUpdate)
- ((Id (fromJust (UUID.fromString "00000003-0000-0002-0000-000200000004"))))
- ((Id (fromJust (UUID.fromString "00000002-0000-0003-0000-000200000004"))))
+ (Qualified (Id (fromJust (UUID.fromString "00000003-0000-0002-0000-000200000004"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00000002-0000-0003-0000-000200000004"))) (Domain "faraway.example.com"))
(read "1864-05-08 05:48:34.348 UTC")
( ( EdConvMessageTimerUpdate
(ConversationMessageTimerUpdate {cupMessageTimer = Just (Ms {ms = 3346692440762670})})
diff --git a/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/ClientMismatch_user.hs b/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/ClientMismatch_user.hs
index 0a7e13ff632..8e6c984842e 100644
--- a/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/ClientMismatch_user.hs
+++ b/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/ClientMismatch_user.hs
@@ -19,6 +19,7 @@
module Test.Wire.API.Golden.Generated.ClientMismatch_user where
import Data.Id (ClientId (ClientId, client), Id (Id))
+import Data.Json.Util (toUTCTimeMillis)
import qualified Data.UUID as UUID (fromString)
import GHC.Exts (IsList (fromList))
import Imports (fromJust, read)
@@ -27,7 +28,7 @@ import Wire.API.Message (ClientMismatch (ClientMismatch), UserClients (UserClien
testObject_ClientMismatch_user_1 :: ClientMismatch
testObject_ClientMismatch_user_1 =
( ClientMismatch
- (read "1864-04-12 12:22:43.673 UTC")
+ (toUTCTimeMillis (read "1864-04-12 12:22:43.673 UTC"))
( UserClients
{ userClients =
fromList
@@ -88,7 +89,7 @@ testObject_ClientMismatch_user_1 =
testObject_ClientMismatch_user_2 :: ClientMismatch
testObject_ClientMismatch_user_2 =
( ClientMismatch
- (read "1864-04-19 08:06:54.492 UTC")
+ (toUTCTimeMillis (read "1864-04-19 08:06:54.492 UTC"))
( UserClients
{ userClients =
fromList
@@ -151,7 +152,7 @@ testObject_ClientMismatch_user_2 =
testObject_ClientMismatch_user_3 :: ClientMismatch
testObject_ClientMismatch_user_3 =
( ClientMismatch
- (read "1864-05-18 16:25:29.722 UTC")
+ (toUTCTimeMillis (read "1864-05-18 16:25:29.722 UTC"))
( UserClients
{ userClients =
fromList
@@ -211,7 +212,7 @@ testObject_ClientMismatch_user_3 =
testObject_ClientMismatch_user_4 :: ClientMismatch
testObject_ClientMismatch_user_4 =
( ClientMismatch
- (read "1864-04-20 07:47:05.133 UTC")
+ (toUTCTimeMillis (read "1864-04-20 07:47:05.133 UTC"))
( UserClients
{ userClients =
fromList
@@ -249,7 +250,7 @@ testObject_ClientMismatch_user_4 =
testObject_ClientMismatch_user_5 :: ClientMismatch
testObject_ClientMismatch_user_5 =
( ClientMismatch
- (read "1864-04-26 19:31:21.478 UTC")
+ (toUTCTimeMillis (read "1864-04-26 19:31:21.478 UTC"))
( UserClients
{ userClients =
fromList
@@ -339,7 +340,7 @@ testObject_ClientMismatch_user_5 =
testObject_ClientMismatch_user_6 :: ClientMismatch
testObject_ClientMismatch_user_6 =
( ClientMismatch
- (read "1864-05-28 18:24:35.996 UTC")
+ (toUTCTimeMillis (read "1864-05-28 18:24:35.996 UTC"))
( UserClients
{ userClients =
fromList
@@ -363,7 +364,7 @@ testObject_ClientMismatch_user_6 =
testObject_ClientMismatch_user_7 :: ClientMismatch
testObject_ClientMismatch_user_7 =
( ClientMismatch
- (read "1864-05-26 02:38:01.741 UTC")
+ (toUTCTimeMillis (read "1864-05-26 02:38:01.741 UTC"))
(UserClients {userClients = fromList []})
( UserClients
{ userClients =
@@ -427,7 +428,7 @@ testObject_ClientMismatch_user_7 =
testObject_ClientMismatch_user_8 :: ClientMismatch
testObject_ClientMismatch_user_8 =
( ClientMismatch
- (read "1864-04-11 13:11:44.951 UTC")
+ (toUTCTimeMillis (read "1864-04-11 13:11:44.951 UTC"))
( UserClients
{ userClients =
fromList
@@ -475,7 +476,7 @@ testObject_ClientMismatch_user_8 =
testObject_ClientMismatch_user_9 :: ClientMismatch
testObject_ClientMismatch_user_9 =
( ClientMismatch
- (read "1864-04-20 09:37:09.767 UTC")
+ (toUTCTimeMillis (read "1864-04-20 09:37:09.767 UTC"))
( UserClients
{ userClients =
fromList
@@ -512,7 +513,7 @@ testObject_ClientMismatch_user_9 =
testObject_ClientMismatch_user_10 :: ClientMismatch
testObject_ClientMismatch_user_10 =
( ClientMismatch
- (read "1864-06-08 05:23:30.672 UTC")
+ (toUTCTimeMillis (read "1864-06-08 05:23:30.672 UTC"))
( UserClients
{ userClients =
fromList
@@ -555,7 +556,7 @@ testObject_ClientMismatch_user_10 =
testObject_ClientMismatch_user_11 :: ClientMismatch
testObject_ClientMismatch_user_11 =
( ClientMismatch
- (read "1864-04-14 22:55:33.894 UTC")
+ (toUTCTimeMillis (read "1864-04-14 22:55:33.894 UTC"))
( UserClients
{ userClients =
fromList
@@ -615,7 +616,7 @@ testObject_ClientMismatch_user_11 =
testObject_ClientMismatch_user_12 :: ClientMismatch
testObject_ClientMismatch_user_12 =
( ClientMismatch
- (read "1864-05-08 01:07:14.883 UTC")
+ (toUTCTimeMillis (read "1864-05-08 01:07:14.883 UTC"))
(UserClients {userClients = fromList []})
( UserClients
{ userClients =
@@ -669,7 +670,7 @@ testObject_ClientMismatch_user_12 =
testObject_ClientMismatch_user_13 :: ClientMismatch
testObject_ClientMismatch_user_13 =
( ClientMismatch
- (read "1864-05-09 16:28:56.647 UTC")
+ (toUTCTimeMillis (read "1864-05-09 16:28:56.647 UTC"))
( UserClients
{ userClients =
fromList
@@ -747,7 +748,7 @@ testObject_ClientMismatch_user_13 =
testObject_ClientMismatch_user_14 :: ClientMismatch
testObject_ClientMismatch_user_14 =
( ClientMismatch
- (read "1864-05-08 01:02:42.968 UTC")
+ (toUTCTimeMillis (read "1864-05-08 01:02:42.968 UTC"))
( UserClients
{ userClients =
fromList
@@ -799,7 +800,7 @@ testObject_ClientMismatch_user_14 =
testObject_ClientMismatch_user_15 :: ClientMismatch
testObject_ClientMismatch_user_15 =
( ClientMismatch
- (read "1864-06-02 22:04:34.496 UTC")
+ (toUTCTimeMillis (read "1864-06-02 22:04:34.496 UTC"))
( UserClients
{ userClients =
fromList
@@ -850,7 +851,7 @@ testObject_ClientMismatch_user_15 =
testObject_ClientMismatch_user_16 :: ClientMismatch
testObject_ClientMismatch_user_16 =
( ClientMismatch
- (read "1864-06-01 16:55:21.151 UTC")
+ (toUTCTimeMillis (read "1864-06-01 16:55:21.151 UTC"))
( UserClients
{ userClients =
fromList
@@ -910,7 +911,7 @@ testObject_ClientMismatch_user_16 =
testObject_ClientMismatch_user_17 :: ClientMismatch
testObject_ClientMismatch_user_17 =
( ClientMismatch
- (read "1864-04-23 21:23:53.493 UTC")
+ (toUTCTimeMillis (read "1864-04-23 21:23:53.493 UTC"))
( UserClients
{ userClients =
fromList
@@ -965,7 +966,7 @@ testObject_ClientMismatch_user_17 =
testObject_ClientMismatch_user_18 :: ClientMismatch
testObject_ClientMismatch_user_18 =
( ClientMismatch
- (read "1864-05-14 18:56:29.815 UTC")
+ (toUTCTimeMillis (read "1864-05-14 18:56:29.815 UTC"))
( UserClients
{ userClients =
fromList
@@ -1039,7 +1040,7 @@ testObject_ClientMismatch_user_18 =
testObject_ClientMismatch_user_19 :: ClientMismatch
testObject_ClientMismatch_user_19 =
( ClientMismatch
- (read "1864-06-06 11:59:12.981 UTC")
+ (toUTCTimeMillis (read "1864-06-06 11:59:12.981 UTC"))
( UserClients
{ userClients =
fromList
@@ -1119,7 +1120,7 @@ testObject_ClientMismatch_user_19 =
testObject_ClientMismatch_user_20 :: ClientMismatch
testObject_ClientMismatch_user_20 =
( ClientMismatch
- (read "1864-05-20 02:14:30.091 UTC")
+ (toUTCTimeMillis (read "1864-05-20 02:14:30.091 UTC"))
( UserClients
{ userClients =
fromList
diff --git a/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/Client_user.hs b/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/Client_user.hs
index 082398da79e..0b28dbb04c1 100644
--- a/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/Client_user.hs
+++ b/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/Client_user.hs
@@ -21,10 +21,13 @@ module Test.Wire.API.Golden.Generated.Client_user where
import Data.Id (ClientId (ClientId, client))
import Data.Json.Util (readUTCTimeMillis)
import Data.Misc (Latitude (Latitude), Longitude (Longitude), location)
+import Data.Set as Set
import Imports (Maybe (Just, Nothing), fromJust)
import Wire.API.User.Auth (CookieLabel (CookieLabel, cookieLabelText))
import Wire.API.User.Client
( Client (..),
+ ClientCapability (ClientSupportsLegalholdImplicitConsent),
+ ClientCapabilityList (ClientCapabilityList),
ClientClass
( DesktopClient,
LegalHoldClient,
@@ -48,7 +51,8 @@ testObject_Client_user_1 =
clientLabel = Just "%*",
clientCookie = Nothing,
clientLocation = Nothing,
- clientModel = Just "\995802;\1081067"
+ clientModel = Just "\995802;\1081067",
+ clientCapabilities = ClientCapabilityList Set.empty
}
testObject_Client_user_2 :: Client
@@ -61,7 +65,8 @@ testObject_Client_user_2 =
clientLabel = Nothing,
clientCookie = Just (CookieLabel {cookieLabelText = "\1112890c\1065129"}),
clientLocation = Just (location (Latitude (0.6919026326441752)) (Longitude (1.18215529547942))),
- clientModel = Nothing
+ clientModel = Nothing,
+ clientCapabilities = ClientCapabilityList Set.empty
}
testObject_Client_user_3 :: Client
@@ -74,7 +79,8 @@ testObject_Client_user_3 =
clientLabel = Just "pi",
clientCookie = Just (CookieLabel {cookieLabelText = ""}),
clientLocation = Just (location (Latitude (-0.31865405026910076)) (Longitude (6.859482454480745e-2))),
- clientModel = Nothing
+ clientModel = Nothing,
+ clientCapabilities = ClientCapabilityList Set.empty
}
testObject_Client_user_4 :: Client
@@ -87,7 +93,8 @@ testObject_Client_user_4 =
clientLabel = Nothing,
clientCookie = Just (CookieLabel {cookieLabelText = "j"}),
clientLocation = Just (location (Latitude (0.43019316470477537)) (Longitude (-2.1994844230432533))),
- clientModel = Just ""
+ clientModel = Just "",
+ clientCapabilities = ClientCapabilityList Set.empty
}
testObject_Client_user_5 :: Client
@@ -100,7 +107,8 @@ testObject_Client_user_5 =
clientLabel = Nothing,
clientCookie = Just (CookieLabel {cookieLabelText = ""}),
clientLocation = Just (location (Latitude (-1.505966289957799)) (Longitude (-2.516893825541776))),
- clientModel = Just "\9015o"
+ clientModel = Just "\9015o",
+ clientCapabilities = ClientCapabilityList Set.empty
}
testObject_Client_user_6 :: Client
@@ -113,7 +121,8 @@ testObject_Client_user_6 =
clientLabel = Nothing,
clientCookie = Just (CookieLabel {cookieLabelText = "l\STX"}),
clientLocation = Just (location (Latitude (0.3764380360505919)) (Longitude (1.3619562593325738))),
- clientModel = Just ""
+ clientModel = Just "",
+ clientCapabilities = ClientCapabilityList Set.empty
}
testObject_Client_user_7 :: Client
@@ -126,7 +135,8 @@ testObject_Client_user_7 =
clientLabel = Just "",
clientCookie = Nothing,
clientLocation = Nothing,
- clientModel = Just ""
+ clientModel = Just "",
+ clientCapabilities = ClientCapabilityList Set.empty
}
testObject_Client_user_8 :: Client
@@ -139,7 +149,8 @@ testObject_Client_user_8 =
clientLabel = Just "",
clientCookie = Just (CookieLabel {cookieLabelText = "\NAKp`"}),
clientLocation = Just (location (Latitude (0.8626148594727595)) (Longitude (-1.971023301844283))),
- clientModel = Just "\1113929"
+ clientModel = Just "\1113929",
+ clientCapabilities = ClientCapabilityList Set.empty
}
testObject_Client_user_9 :: Client
@@ -152,7 +163,8 @@ testObject_Client_user_9 =
clientLabel = Just "v\DEL",
clientCookie = Just (CookieLabel {cookieLabelText = "G"}),
clientLocation = Just (location (Latitude (-0.3086524641730466)) (Longitude (1.72690152811777))),
- clientModel = Just "\13056m"
+ clientModel = Just "\13056m",
+ clientCapabilities = ClientCapabilityList Set.empty
}
testObject_Client_user_10 :: Client
@@ -165,7 +177,8 @@ testObject_Client_user_10 =
clientLabel = Nothing,
clientCookie = Just (CookieLabel {cookieLabelText = "L"}),
clientLocation = Just (location (Latitude (-2.6734377548386075)) (Longitude (-1.40544074714727))),
- clientModel = Just "\CAN"
+ clientModel = Just "\CAN",
+ clientCapabilities = ClientCapabilityList Set.empty
}
testObject_Client_user_11 :: Client
@@ -178,7 +191,8 @@ testObject_Client_user_11 =
clientLabel = Just "\USb",
clientCookie = Just (CookieLabel {cookieLabelText = "5"}),
clientLocation = Just (location (Latitude (0.44311730892815937)) (Longitude (0.6936233843789369))),
- clientModel = Just "ML"
+ clientModel = Just "ML",
+ clientCapabilities = ClientCapabilityList Set.empty
}
testObject_Client_user_12 :: Client
@@ -191,7 +205,8 @@ testObject_Client_user_12 =
clientLabel = Just "",
clientCookie = Just (CookieLabel {cookieLabelText = "0"}),
clientLocation = Just (location (Latitude (-2.502416826395783)) (Longitude (1.4712334862249388))),
- clientModel = Just ""
+ clientModel = Just "",
+ clientCapabilities = ClientCapabilityList Set.empty
}
testObject_Client_user_13 :: Client
@@ -204,7 +219,8 @@ testObject_Client_user_13 =
clientLabel = Just "\1064061",
clientCookie = Just (CookieLabel {cookieLabelText = "\f^\1012431"}),
clientLocation = Just (location (Latitude (-2.3798205243177692)) (Longitude (-2.619240132398651))),
- clientModel = Just "\ETB\68772"
+ clientModel = Just "\ETB\68772",
+ clientCapabilities = ClientCapabilityList Set.empty
}
testObject_Client_user_14 :: Client
@@ -217,7 +233,8 @@ testObject_Client_user_14 =
clientLabel = Just "x\SO",
clientCookie = Nothing,
clientLocation = Just (location (Latitude (2.459582010332432)) (Longitude (-1.2286910026214775))),
- clientModel = Just "\1052175\r\917608"
+ clientModel = Just "\1052175\r\917608",
+ clientCapabilities = ClientCapabilityList Set.empty
}
testObject_Client_user_15 :: Client
@@ -230,7 +247,8 @@ testObject_Client_user_15 =
clientLabel = Just "\EOTG",
clientCookie = Just (CookieLabel {cookieLabelText = "\1100343N"}),
clientLocation = Nothing,
- clientModel = Just "zAI"
+ clientModel = Just "zAI",
+ clientCapabilities = ClientCapabilityList Set.empty
}
testObject_Client_user_16 :: Client
@@ -243,7 +261,8 @@ testObject_Client_user_16 =
clientLabel = Just "=E",
clientCookie = Just (CookieLabel {cookieLabelText = "U"}),
clientLocation = Nothing,
- clientModel = Just ""
+ clientModel = Just "",
+ clientCapabilities = ClientCapabilityList Set.empty
}
testObject_Client_user_17 :: Client
@@ -256,7 +275,8 @@ testObject_Client_user_17 =
clientLabel = Nothing,
clientCookie = Just (CookieLabel {cookieLabelText = ""}),
clientLocation = Just (location (Latitude (-1.6915872714820337)) (Longitude (2.1128949838723656))),
- clientModel = Just ""
+ clientModel = Just "",
+ clientCapabilities = ClientCapabilityList Set.empty
}
testObject_Client_user_18 :: Client
@@ -269,7 +289,8 @@ testObject_Client_user_18 =
clientLabel = Just "\996666",
clientCookie = Just (CookieLabel {cookieLabelText = "PG:"}),
clientLocation = Just (location (Latitude (-1.2949675488134762)) (Longitude (0.43717421775412324))),
- clientModel = Just "\DEL\1071737"
+ clientModel = Just "\DEL\1071737",
+ clientCapabilities = ClientCapabilityList Set.empty
}
testObject_Client_user_19 :: Client
@@ -282,7 +303,8 @@ testObject_Client_user_19 =
clientLabel = Just "\1098224l",
clientCookie = Nothing,
clientLocation = Just (location (Latitude (-1.4630309786758076)) (Longitude (-0.5295690632216867))),
- clientModel = Just ""
+ clientModel = Just "",
+ clientCapabilities = ClientCapabilityList Set.empty
}
testObject_Client_user_20 :: Client
@@ -295,5 +317,6 @@ testObject_Client_user_20 =
clientLabel = Just "-\1032867v",
clientCookie = Just (CookieLabel {cookieLabelText = ""}),
clientLocation = Just (location (Latitude (2.8672347564452996)) (Longitude (-0.9990390825956594))),
- clientModel = Nothing
+ clientModel = Nothing,
+ clientCapabilities = ClientCapabilityList (Set.fromList [ClientSupportsLegalholdImplicitConsent])
}
diff --git a/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/Event_user.hs b/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/Event_user.hs
index 3decf1acaca..8ff7e219437 100644
--- a/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/Event_user.hs
+++ b/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/Event_user.hs
@@ -64,7 +64,7 @@ import Wire.API.Event.Conversation
otrRecipient,
otrSender
),
- SimpleMember (SimpleMember, smConvRoleName, smId),
+ SimpleMember (..),
SimpleMembers (SimpleMembers, mMembers),
UserIdList (UserIdList, mUsers),
)
@@ -77,8 +77,8 @@ testObject_Event_user_1 :: Event
testObject_Event_user_1 =
( Event
(ConvDelete)
- ((Id (fromJust (UUID.fromString "00005d81-0000-0d71-0000-1d8f00007d32"))))
- ((Id (fromJust (UUID.fromString "00003b8b-0000-3395-0000-076a00007830"))))
+ (Qualified (Id (fromJust (UUID.fromString "00005d81-0000-0d71-0000-1d8f00007d32"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00003b8b-0000-3395-0000-076a00007830"))) (Domain "faraway.example.com"))
(read "1864-05-22 09:51:07.104 UTC")
(EdConvDelete)
)
@@ -87,8 +87,8 @@ testObject_Event_user_2 :: Event
testObject_Event_user_2 =
( Event
(ConvAccessUpdate)
- ((Id (fromJust (UUID.fromString "0000064d-0000-7a7f-0000-5749000029e1"))))
- ((Id (fromJust (UUID.fromString "00006a88-0000-2acb-0000-6aa0000061b2"))))
+ (Qualified (Id (fromJust (UUID.fromString "0000064d-0000-7a7f-0000-5749000029e1"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00006a88-0000-2acb-0000-6aa0000061b2"))) (Domain "faraway.example.com"))
(read "1864-06-05 23:01:18.769 UTC")
( ( EdConvAccessUpdate
( ConversationAccessUpdate
@@ -104,8 +104,8 @@ testObject_Event_user_3 :: Event
testObject_Event_user_3 =
( Event
(OtrMessageAdd)
- ((Id (fromJust (UUID.fromString "00006f8c-0000-00d6-0000-1568000001e9"))))
- ((Id (fromJust (UUID.fromString "00004b11-0000-5504-0000-55d800002188"))))
+ (Qualified (Id (fromJust (UUID.fromString "00006f8c-0000-00d6-0000-1568000001e9"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00004b11-0000-5504-0000-55d800002188"))) (Domain "faraway.example.com"))
(read "1864-04-27 15:44:23.844 UTC")
( ( EdOtrMessage
( OtrMessage
@@ -123,8 +123,8 @@ testObject_Event_user_4 :: Event
testObject_Event_user_4 =
( Event
(ConvCodeDelete)
- ((Id (fromJust (UUID.fromString "00004f04-0000-3939-0000-472d0000316b"))))
- ((Id (fromJust (UUID.fromString "00007c90-0000-766a-0000-01b700002ab7"))))
+ (Qualified (Id (fromJust (UUID.fromString "00004f04-0000-3939-0000-472d0000316b"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00007c90-0000-766a-0000-01b700002ab7"))) (Domain "faraway.example.com"))
(read "1864-05-12 00:59:09.2 UTC")
(EdConvCodeDelete)
)
@@ -133,8 +133,8 @@ testObject_Event_user_5 :: Event
testObject_Event_user_5 =
( Event
(MemberStateUpdate)
- ((Id (fromJust (UUID.fromString "00003c8c-0000-6394-0000-294b0000098b"))))
- ((Id (fromJust (UUID.fromString "00002a12-0000-73e1-0000-71f700002ec9"))))
+ (Qualified (Id (fromJust (UUID.fromString "00003c8c-0000-6394-0000-294b0000098b"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00002a12-0000-73e1-0000-71f700002ec9"))) (Domain "faraway.example.com"))
(read "1864-04-12 03:04:00.298 UTC")
( ( EdMemberUpdate
( MemberUpdateData
@@ -161,8 +161,8 @@ testObject_Event_user_6 :: Event
testObject_Event_user_6 =
( Event
(ConvMessageTimerUpdate)
- ((Id (fromJust (UUID.fromString "00001fdb-0000-3127-0000-23ef00007183"))))
- ((Id (fromJust (UUID.fromString "0000705a-0000-0b62-0000-425c000049c8"))))
+ (Qualified (Id (fromJust (UUID.fromString "00001fdb-0000-3127-0000-23ef00007183"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "0000705a-0000-0b62-0000-425c000049c8"))) (Domain "faraway.example.com"))
(read "1864-05-09 05:44:41.382 UTC")
((EdConvMessageTimerUpdate (ConversationMessageTimerUpdate {cupMessageTimer = Just (Ms {ms = 5029817038083912})})))
)
@@ -171,8 +171,8 @@ testObject_Event_user_7 :: Event
testObject_Event_user_7 =
( Event
(Typing)
- ((Id (fromJust (UUID.fromString "00006ac1-0000-543e-0000-7c8f00000be7"))))
- ((Id (fromJust (UUID.fromString "0000355a-0000-2979-0000-083000002d5e"))))
+ (Qualified (Id (fromJust (UUID.fromString "00006ac1-0000-543e-0000-7c8f00000be7"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "0000355a-0000-2979-0000-083000002d5e"))) (Domain "faraway.example.com"))
(read "1864-04-18 05:01:13.761 UTC")
((EdTyping (TypingData {tdStatus = StoppedTyping})))
)
@@ -181,8 +181,8 @@ testObject_Event_user_8 :: Event
testObject_Event_user_8 =
( Event
(ConvCodeDelete)
- ((Id (fromJust (UUID.fromString "00000892-0000-53c7-0000-0c870000027a"))))
- ((Id (fromJust (UUID.fromString "000008e8-0000-43fa-0000-4dd1000034cc"))))
+ (Qualified (Id (fromJust (UUID.fromString "00000892-0000-53c7-0000-0c870000027a"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "000008e8-0000-43fa-0000-4dd1000034cc"))) (Domain "faraway.example.com"))
(read "1864-06-08 15:19:01.916 UTC")
(EdConvCodeDelete)
)
@@ -191,8 +191,8 @@ testObject_Event_user_9 :: Event
testObject_Event_user_9 =
( Event
(ConvAccessUpdate)
- ((Id (fromJust (UUID.fromString "00004847-0000-1eb9-0000-2973000039ca"))))
- ((Id (fromJust (UUID.fromString "000044e3-0000-1c36-0000-42fd00006e01"))))
+ (Qualified (Id (fromJust (UUID.fromString "00004847-0000-1eb9-0000-2973000039ca"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "000044e3-0000-1c36-0000-42fd00006e01"))) (Domain "faraway.example.com"))
(read "1864-05-21 16:22:14.886 UTC")
( ( EdConvAccessUpdate
( ConversationAccessUpdate
@@ -209,8 +209,8 @@ testObject_Event_user_10 :: Event
testObject_Event_user_10 =
( Event
(ConvCreate)
- ((Id (fromJust (UUID.fromString "000019e1-0000-1dc6-0000-68de0000246d"))))
- ((Id (fromJust (UUID.fromString "00000457-0000-0689-0000-77a00000021c"))))
+ (Qualified (Id (fromJust (UUID.fromString "000019e1-0000-1dc6-0000-68de0000246d"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00000457-0000-0689-0000-77a00000021c"))) (Domain "faraway.example.com"))
(read "1864-05-29 19:31:31.226 UTC")
( ( EdConversation
( Conversation
@@ -326,8 +326,8 @@ testObject_Event_user_11 :: Event
testObject_Event_user_11 =
( Event
(MemberStateUpdate)
- ((Id (fromJust (UUID.fromString "000031c2-0000-108c-0000-10a500000882"))))
- ((Id (fromJust (UUID.fromString "00005335-0000-2983-0000-46460000082f"))))
+ (Qualified (Id (fromJust (UUID.fromString "000031c2-0000-108c-0000-10a500000882"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00005335-0000-2983-0000-46460000082f"))) (Domain "faraway.example.com"))
(read "1864-05-03 06:49:41.178 UTC")
( ( EdMemberUpdate
( MemberUpdateData
@@ -350,8 +350,8 @@ testObject_Event_user_12 :: Event
testObject_Event_user_12 =
( Event
(ConvDelete)
- ((Id (fromJust (UUID.fromString "00007474-0000-2a7b-0000-125900006ac9"))))
- ((Id (fromJust (UUID.fromString "00000795-0000-709d-0000-11270000007a"))))
+ (Qualified (Id (fromJust (UUID.fromString "00007474-0000-2a7b-0000-125900006ac9"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00000795-0000-709d-0000-11270000007a"))) (Domain "faraway.example.com"))
(read "1864-05-23 17:16:29.326 UTC")
(EdConvDelete)
)
@@ -360,8 +360,8 @@ testObject_Event_user_13 :: Event
testObject_Event_user_13 =
( Event
(OtrMessageAdd)
- ((Id (fromJust (UUID.fromString "00006355-0000-5f6e-0000-592c0000680c"))))
- ((Id (fromJust (UUID.fromString "000029eb-0000-06f8-0000-514100000a84"))))
+ (Qualified (Id (fromJust (UUID.fromString "00006355-0000-5f6e-0000-592c0000680c"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "000029eb-0000-06f8-0000-514100000a84"))) (Domain "faraway.example.com"))
(read "1864-05-21 03:22:42.926 UTC")
( ( EdOtrMessage
( OtrMessage
@@ -379,8 +379,8 @@ testObject_Event_user_14 :: Event
testObject_Event_user_14 =
( Event
(ConvReceiptModeUpdate)
- ((Id (fromJust (UUID.fromString "00000b98-0000-618d-0000-19e200004651"))))
- ((Id (fromJust (UUID.fromString "00004bee-0000-45a0-0000-2c0300005726"))))
+ (Qualified (Id (fromJust (UUID.fromString "00000b98-0000-618d-0000-19e200004651"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00004bee-0000-45a0-0000-2c0300005726"))) (Domain "faraway.example.com"))
(read "1864-05-01 11:57:35.123 UTC")
((EdConvReceiptModeUpdate (ConversationReceiptModeUpdate {cruReceiptMode = ReceiptMode {unReceiptMode = -10505}})))
)
@@ -389,8 +389,8 @@ testObject_Event_user_15 :: Event
testObject_Event_user_15 =
( Event
(ConvConnect)
- ((Id (fromJust (UUID.fromString "00005e43-0000-3b56-0000-7c270000538c"))))
- ((Id (fromJust (UUID.fromString "00007f28-0000-40b1-0000-56ab0000748d"))))
+ (Qualified (Id (fromJust (UUID.fromString "00005e43-0000-3b56-0000-7c270000538c"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00007f28-0000-40b1-0000-56ab0000748d"))) (Domain "faraway.example.com"))
(read "1864-05-25 01:31:49.802 UTC")
( ( EdConnect
( Connect
@@ -408,8 +408,8 @@ testObject_Event_user_16 :: Event
testObject_Event_user_16 =
( Event
(ConvAccessUpdate)
- ((Id (fromJust (UUID.fromString "00004b59-0000-55d6-0000-5aad00007373"))))
- ((Id (fromJust (UUID.fromString "0000211e-0000-0b37-0000-563100003a5d"))))
+ (Qualified (Id (fromJust (UUID.fromString "00004b59-0000-55d6-0000-5aad00007373"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "0000211e-0000-0b37-0000-563100003a5d"))) (Domain "faraway.example.com"))
(read "1864-05-24 00:49:37.413 UTC")
((EdConvAccessUpdate (ConversationAccessUpdate {cupAccess = [], cupAccessRole = ActivatedAccessRole})))
)
@@ -418,8 +418,8 @@ testObject_Event_user_17 :: Event
testObject_Event_user_17 =
( Event
(Typing)
- ((Id (fromJust (UUID.fromString "00006ac8-0000-1342-0000-76880000021d"))))
- ((Id (fromJust (UUID.fromString "0000145f-0000-2ce0-0000-4ca800006c72"))))
+ (Qualified (Id (fromJust (UUID.fromString "00006ac8-0000-1342-0000-76880000021d"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "0000145f-0000-2ce0-0000-4ca800006c72"))) (Domain "faraway.example.com"))
(read "1864-04-17 07:39:54.846 UTC")
((EdTyping (TypingData {tdStatus = StoppedTyping})))
)
@@ -428,8 +428,8 @@ testObject_Event_user_18 :: Event
testObject_Event_user_18 =
( Event
(MemberLeave)
- ((Id (fromJust (UUID.fromString "0000303b-0000-23a9-0000-25de00002f80"))))
- ((Id (fromJust (UUID.fromString "000043a6-0000-1627-0000-490300002017"))))
+ (Qualified (Id (fromJust (UUID.fromString "0000303b-0000-23a9-0000-25de00002f80"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "000043a6-0000-1627-0000-490300002017"))) (Domain "faraway.example.com"))
(read "1864-04-12 01:28:25.705 UTC")
( ( EdMembersLeave
( UserIdList
@@ -470,18 +470,18 @@ testObject_Event_user_19 :: Event
testObject_Event_user_19 =
( Event
(MemberJoin)
- ((Id (fromJust (UUID.fromString "00000838-0000-1bc6-0000-686d00003565"))))
- ((Id (fromJust (UUID.fromString "0000114a-0000-7da8-0000-40cb00007fcf"))))
+ (Qualified (Id (fromJust (UUID.fromString "00000838-0000-1bc6-0000-686d00003565"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "0000114a-0000-7da8-0000-40cb00007fcf"))) (Domain "faraway.example.com"))
(read "1864-05-12 20:29:47.483 UTC")
( ( EdMembersJoin
( SimpleMembers
{ mMembers =
[ SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000055-0000-004d-0000-005100000037"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000055-0000-004d-0000-005100000037"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "dlkagbmicz0f95d"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000004c-0000-0051-0000-00220000005c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000004c-0000-0051-0000-00220000005c"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -490,7 +490,7 @@ testObject_Event_user_19 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000014-0000-0027-0000-003400000023"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000014-0000-0027-0000-003400000023"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -499,15 +499,15 @@ testObject_Event_user_19 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000001-0000-001f-0000-001500000009"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000001-0000-001f-0000-001500000009"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "2e"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000005d-0000-0064-0000-00590000007d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000005d-0000-0064-0000-00590000007d"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "f3nxp18px4kup3nrarx5wsp1o_eh69"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000068-0000-007a-0000-005a0000006c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000068-0000-007a-0000-005a0000006c"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -516,32 +516,32 @@ testObject_Event_user_19 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000074-0000-0036-0000-00780000007d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000074-0000-0036-0000-00780000007d"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "f9i5d2wd01ijp53en5bq8lch__jlnu8_v2xsgkctpin98byh1009f_v63"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000001c-0000-000a-0000-004800000063"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000001c-0000-000a-0000-004800000063"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "o_oqigzovv9oc2uxckvk5eofmc"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000056-0000-0028-0000-004f00000079"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000056-0000-0028-0000-004f00000079"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
(parseRoleName "5snj8s5t7nicihwspcp4sg4ny1pa1yb2s6601vjyxhksbciotoi_rvivybk1iviuz8buw")
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000001e-0000-0054-0000-002300000053"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000001e-0000-0054-0000-002300000053"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "73e9u2hpffjb5ids29tbtcceg0i9v2"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000004d-0000-0027-0000-007500000042"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000004d-0000-0027-0000-007500000042"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "d2s4mc_qt1cc2rox8c9gak_qivlha7q259lsz7y5bz6dxsv8igx9r"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000050-0000-006e-0000-007000000057"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000050-0000-006e-0000-007000000057"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -550,11 +550,11 @@ testObject_Event_user_19 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000007a-0000-003c-0000-005300000013"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000007a-0000-003c-0000-005300000013"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "v7ldb8mov4an62t6"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000001-0000-0064-0000-005e00000072"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000001-0000-0064-0000-005e00000072"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -563,7 +563,7 @@ testObject_Event_user_19 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000020-0000-0012-0000-000500000036"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000020-0000-0012-0000-000500000036"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "s6creybsl300lqkhu0wv_ikgattm3bd1r"))
}
]
@@ -577,8 +577,8 @@ testObject_Event_user_20 :: Event
testObject_Event_user_20 =
( Event
(MemberLeave)
- ((Id (fromJust (UUID.fromString "00000c88-0000-433f-0000-669100006374"))))
- ((Id (fromJust (UUID.fromString "00007547-0000-26d8-0000-52280000157c"))))
+ (Qualified (Id (fromJust (UUID.fromString "00000c88-0000-433f-0000-669100006374"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00007547-0000-26d8-0000-52280000157c"))) (Domain "faraway.example.com"))
(read "1864-04-21 23:40:54.462 UTC")
( ( EdMembersLeave
( UserIdList
diff --git a/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/NewClient_user.hs b/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/NewClient_user.hs
index 0c89173f8d2..8b59044e0f7 100644
--- a/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/NewClient_user.hs
+++ b/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/NewClient_user.hs
@@ -19,10 +19,12 @@
module Test.Wire.API.Golden.Generated.NewClient_user where
import Data.Misc (PlainTextPassword (PlainTextPassword))
+import qualified Data.Set as Set
import Imports (Maybe (Just, Nothing))
import Wire.API.User.Auth (CookieLabel (CookieLabel, cookieLabelText))
import Wire.API.User.Client
- ( ClientClass
+ ( ClientCapability (ClientSupportsLegalholdImplicitConsent),
+ ClientClass
( DesktopClient,
LegalHoldClient,
PhoneClient,
@@ -34,6 +36,7 @@ import Wire.API.User.Client
TemporaryClientType
),
NewClient (..),
+ newClientCapabilities,
)
import Wire.API.User.Client.Prekey (Prekey (Prekey, prekeyId, prekeyKey), PrekeyId (PrekeyId, keyId), lastPrekey)
@@ -51,7 +54,8 @@ testObject_NewClient_user_1 =
( PlainTextPassword
"]?S`bO\DEL,%\foV\1058249\\+\1085138\&1\188945Hc\a\STX\34506\vH\STX<^e\1069270(\FS(UsaJ\74984\EOT\ETXJ\NUL~K\174557\53413\1102650\CANB\21894wP\RSb?^\152318!\ACK\989862z\EOTD\57439\DLEV\1014962n\5537/W\93061\"\ESC\44216mnq_t\ESCCR\1099860\SICq\1032826\1113138\DC3\a\CAN\n\14612\177521\GSr'\tA\1105228\DC2\1008406\1015229\28799VW\ETBK~\SYN>\DC1J\SO;UFO\ACKd\DC4\ESC\SO\992594\53528\ACK\SI`\156081RxHJVQ]:s\a\188379IY\54049~H6,80\1061902\1030969\SYNwx_\34144\97145q\ENQ:N\150078\1101779\1108114\157366\NULF\ETXAs\156773\993458bf\t\151541|\ACK\1027577\DC2{\"\DLE\9157\1046357>\187020,i.L\SOH\146676>\131459g\32728\1027802\188157\1055365\&6\983541\t+\166512\&8\SOv\f\NUL\b\RSv)\38280\DC1J\n\STX\1076642\36253\DC4&R\ESC:~\92995\nO\37177\&5\GS!3V\1080913\147100,\1069133n\CAN\986564\1045245^\881\CAN\17703\GS!\SUB\30722\172478\1013987;8{\ttB[A\GSH\1092438\nv\24007\1006563z|\CANS^.@\FS\140955#P\1108759V6:%7|\DC2o\99270\1060103)V\161010H\37080e\NAK\43917/\1084962\aSv\121274?\CANW7\ETBIOc~E\1063561\1028266b\ETB\155485\92278?\26363\1039530e\EM.|DXlNA-\1081928\178499E\ESC+\1041616SZ\97929\989365hRm/^{\64076\1090370\1036645=*)\1087615\1080072\1018318HjR\1015837\14527Vg\1041636!g\169151\147136\121037\DC3PY[=2\DC2!0\1005141\SYN\"\1113389\1021455-<2]`6:\1044580'&8\ETX\"LL\97181*^:\ENQ\EM\1047774 T\1036505e[8%J#~u\1104342\32511j=hX66)+\SYN\aU\SO\SOH\1070386j\1085132\1090312\166954Td^\1078796\983350\ESC:\GS\98354\1075395@\1100827X\DC3\SO\49895\EM%`\38791\1108632\65179\1086075\STX\a-\EM+_\163560:H4o\DC3HS6`\ENQ\181063M\61286\ETBV?.>\1007053CO-\182201\1034506bf}\127013ZM\1034664\rNp\1095318\1036312\DEL\172193%\ACK/WNZ\62756t\39633HnO\1078611=xm\1095149\44536@$Z\1041048\DELdEZ_1\NUL\fw\983252\DC1Iq\EMa&\1105414\1092447\ESCe\DC2:\1104318#\STXe\STX\993655\&1S\1082850v=A\994881\NUL.\3929\1107033<1 \135839\132170$>M6\23716\1096562\1004254&t_6\EM\138609q\NAK\44158ZTX\\\DC3\1051437\19070a:ip\\]\145930y\135361\32810:b\DC2\28839\txB;!\1036389\1094377\57346HmV\ETBv\95479v\SOH6(/ph\186487.\18345\ETB\NULC\140113\1063283)R5\1107380a\DLE%^\1081815X\164772$D9i\1069264\DC26wM\1045336\SOI%x\1101120+\DELYT\145956\170673\142731H\185687L\rU3\v.+<\rT\1009564\996048_v\94297]H(\"\NULJ\21218C\ru\DC1-\1096638Q\EM\"\"\169384r\EOT7P\164640 \FS\1054047R\1032087\1053518\133154dz\1019877\1061911D\148027Z\ETB\1045958\54215Sb7\152587CL\\"
),
- newClientModel = Just ""
+ newClientModel = Just "",
+ newClientCapabilities = Nothing
}
testObject_NewClient_user_2 :: NewClient
@@ -68,7 +72,8 @@ testObject_NewClient_user_2 =
( PlainTextPassword
"I\1065423\995547oIC\by\1045956\&1\13659&w>S~z\35967\a{2Dj\v|Z\"\f\1060612*[\65357V\1086491kS\145031A\1106044\1056321(2\DLE\48205\SOi\SI(\1032525\168748f?q\SO5\146557d\1068952^nI\1103535_?\1019210H\119099\SUBf\995865\n\1004095x\ACKdZ\1053945^N\fa\SYN\SUBb=\1112183SP\128516aTd\EM\186127\DC3\ACK\ETB!\1011808\142127o{uoN\CANqL\NAK\ESCc=\v@o2\1043826\EOT\142486\US\1079334\&5v\STX\GS_k,\DC3mAV>$\1029013\1061276\RS\1089843\n\8980-\60552ea}G`r? \DEL\1004551\SOH\US\132757\&9\brl\155069}u\120967\1080794\1062392@M6M\155107\98552\167588|E5Ud\1051152tLjQ\1022837\6734\RS\v\DC1jE\ACK'~f\SIR\1010717\NAKd}}\1059960q\1031766\DC1\151174\&9\160469\RS\100592\ETX\186780\DEL\r\FS\US\36812\14285\NAK/\GS\25526\1090814\61061\NUL(:\1054313n#m9x \1078109\183480}\1052622\54486\GS\991929\b`\1087609G#T\DC2-8\NAK\18310\134655\tp/!\STX4C\SUB'DP'.\a\1110090\&8<9\SYN\NAKEq\168018Ep]\ajZ%\1025589\4170O\35069>\CAN\ACKw*f<\1102303\SOjzpjY\US\SUB\19086\DC1\DC1\ACK|\SO\1064500;\135633F!f\19971b%\1048714t9\DC2\f\121106X! \133247C\RS\1029038\162320C!\20923H(/\GSV)e\SYN2\NUL#H$BAJy\ETB\162654X\137014\FS\SUB\DEL~\f\ESC;\n<\GSf~{\b_"
),
- newClientModel = Just "om"
+ newClientModel = Just "om",
+ newClientCapabilities = Just (Set.fromList [ClientSupportsLegalholdImplicitConsent])
}
testObject_NewClient_user_3 :: NewClient
@@ -85,7 +90,8 @@ testObject_NewClient_user_3 =
( PlainTextPassword
"\1052368\1027374`7\1059315\1007093\1113589ml\140833\"\SOH\SO\DC1*\DC3Q\RS\14805.I\f\1049837\DLEXHyy\155377%\NAK\STXygG`H;\95385\SOH}U\177626\137231\EOTcX\1040316X\34265!|v@\SO\CAN\fp\996755\127817d\1054362e\135350\vr`c\1007164\1023752\142611 \DC2\58317h\ENQOS\FS_X$\181753\GS\74118g\1066468x%\b\1015412YzZ\1069885\&2`^h^R\1101423\62761b\1095153BOyj\1040477!|E \58547\US\62210!Bu`5I$\EM,Jt\DC37\78371L)\70459Z\SYN/pl\172834Xb\DC3\a\STX\1091299\SO!\1078114\SUB#\170440G6\162069m\CAN\1029459RI\187903\983334_\996859\1000036W^\ACKj\1070150\172043x\EM\1040352\&5BV\bVU\1001763\142747\rPt\1108970\36507C\78096\f\158701F>\vkqOC\n-_q\DLEc"
),
- newClientModel = Just "\1016506\DC3\134041"
+ newClientModel = Just "\1016506\DC3\134041",
+ newClientCapabilities = Nothing
}
testObject_NewClient_user_4 :: NewClient
@@ -105,7 +111,8 @@ testObject_NewClient_user_4 =
( PlainTextPassword
"\1039017\EM\188473SyAf\RS\1015867X\64605~RZ}\1062576\&6}\1040947\DELke\1039536v$By\137108\1063620,\STXy\138845~\1099492\&9sr\1072529\r_\1069502\ACK\50260\1078888\1006226\&7\1100806l\1078895\50212|\1009874\163356\n\1052573\ETB)u\1101000\1104322%\32265\ETB\991708P)e\DELE:sR\1084361\SOH@\1038853y\US\20921v\165504\1017085\DC1[9b|\ETXHNQ|=\1029089\&3Q7\40623\1103209\DLEnoh9\59006)J\FS\186747P\54437cN\SOV\41980\160251O4S\SO\GSR\SOH\USBn\"D\156034\&3c]Npy\94751\70717\174343k\157787<\ETB\1110191H\t\1061523\164945\DC1o\f/w\EM>%\119897\SOH\144896,\92174i\vZ[I\45897\SUBN1mZ\29435\32128H\1059019Imv#JR/;\ACKK\DLE\22913\9036{ZR\a96G\ENQy\152237+\1076894\65774=!\EM\v\DC4\GS\1027536\1041750<\1050841\78770\186899\146197$\1025274\CAN-;\ETX4v\RS\1084606\r\1075881q\1082834\&4_C\160530\NAKU.PD\DC4NYI\DLEv\b\r\FSxH\1013670%mxC\1046045\SUB\1048930\CAN#cE\1084200;j`\136585I[ABy\1009976f0\138223t\1014439\SI\153598?\SYN~(Z\v\DC3E!\EOT\1028681[TW\993095\USn\SO\21854+&\STX\183016\FS+\131838\FSv\RS\1069777D\1037076$<@\ETB\fCEb\152380&\1024980e\DC1\CANgN\DC2]\RS\\A:*\ACKtV\ajD3\1109955zBA4\EM5\SUB\SYN\993240\CAN:%\1006879\51029\SO\184148{\vT_~0\GS\DC2\EOTV0\1066531K7xI/\1034400\DEL\54219\STX\1060450\EM\132009\8421.m\75044\DLE\1020963a\44058|\SUB\6647R\ESCr~\191257\EOT\DLEFrvb\59948\111197\ETXz\bI\\\DELo+'Q\EOTl :?\71309r\US\146650\1086696o+\169525Jn\1042987.*\162403_7:\1042422\NAK3T\ENQ\t\155471b/q\SYN@OmA\97170xZ\52646\CAN\RSh\USg\aE_K\SI{\1052531\1053068&\1112242N )\157576zB\ETB\SI\1048564F\1082705\"\ACKB\66649\ENQ\182827S\GSX\27274\1060500yHAi\156511 \DC3\23109F8A/e.8$\99020\DLEOu-Mw\1008926\&4zb\165386BC\1078129\1070383\STXY%Bg\RS37v~2\ESC\CAN8\CAN\DC1\FSz#q?ADm4\vBK&Z\CANK\1112860f!*/\ACK\DC2iVTs\141472\1059086\1089418\154356OQ\154020-\46297[J\1041865\18234\1047221H1K\1102038\US\121032\1065005\&1\SIk+EB?\997749\1030694\1110639y,\CAN\NUL\USfT\DLE{\NAK\1032571\SYN\35342E,\SOHy\35156?\1031354$ll%\1046167\SOH`)\74445\1078638u\152196\1102759\148712A\SYNpDoz]L\19874/\1019344\1067340&f`AX=RA\v\1103061%,O\1049934\NAKB\119918@\EM\ENQ\1047236\1081280-\997855T\998310l6\NAK'\172576^\1053906('2AP&?\US\DLE\SI0f\2012\ESC$\DC2\31866\&5v_\1051689\ENQ\153046> [3h)?\50999\1039306\137233m\983891cBD\2027\1016974\1014463\SUB%X\NUL:Pib\\s\2743t\156273\1005692{Sz+O\GS\DC4\DLE\DC3.\EM;2S\SIF\1102512\DC4ftF}3G\NUL$wR]\USYpY\f\SO\141464;\ETXe\28203\38494\57722$\DC3(\SIw\ETBBp\128488\ENQ\DLE\154640\1040605\v:\51261V\983830.\SOHN<\60444$\tj3\51990\US\18582E,$\a\n\SYN\SUB%\1022574P\983622,\1034053k\NUL\134010E\82971\ENQ'\CANwrhj?9\1090612\1017377\CAN#\181249\58693\ENQ\DC3I\f\"\ENQ\53481^\144621}T\164393"
+ newClientModel = Just "Y5>",
+ newClientCapabilities = Nothing
}
testObject_NewClient_user_6 :: NewClient
@@ -131,7 +139,8 @@ testObject_NewClient_user_6 =
newClientClass = Nothing,
newClientCookie = Just (CookieLabel {cookieLabelText = ""}),
newClientPassword = Nothing,
- newClientModel = Nothing
+ newClientModel = Nothing,
+ newClientCapabilities = Nothing
}
testObject_NewClient_user_7 :: NewClient
@@ -148,7 +157,8 @@ testObject_NewClient_user_7 =
( PlainTextPassword
"\60526\139445l\988963\ESCQ\DLE8SH\f\GS\ETB-\SYN5\FSPy\1061623r/=\986816N3\1035659&N\15316w\67714X/Q*\150730\13744X\1000479+9=U\1024020\SOH\ENQ\STX}i\b\1097422\1069734\SUB\ENQZ(\168168\1009772o#Xm\CANM\1023654\&3\172872^Pn\7760crQc\DEL\986307\SOp\tr\\\986121t}\EOT`\177281M\66289\"\4716S\SI\1037221\SIQA\1106589\RSb\DEL\1021792\73060\&0\990076\68641 +~\991677\SUB8,b\162479g\1077282%\CAN\10999\SUB\ENQ)e\SOH\1015562\f9\1094666!yW\1033160${\1059712}\1110303\1078145\144829+q{\f\164278\1032027|g\ACK\SOH/`L~\1047083\1084997\158108\ETX!\ETX8\n\1025179\984212p\RS&\RS\32948\1096125+\1052271uft \1037257D\1055020\160376'\SOH\999953\NAKV\SYNn8x\29075\DLE\40118\CANP~l$f\7823\DC1\ENQ\1000273\8729\RSLx<@\DC2\EM\1027640H\181765s\1072018\&18\152448\137948\ETB \DC1I\f=\CAN=[V\1045990'\SOH\46563\995907G`6'\SUB!*;\EMJ\1075591\STXy\DC1\\\"!\aE\28556\146573B\1033154\178164AL\RS!\ENQ]\1050549?_t/\137535a\64278O G\RS\1102157\1087399{=\DC2<<\1046051[o\1021278Lz\a\ACK\1108792\n>.\DEL\ENQI|hlT;h\US\SOI\NAK\SO\SYN\1102001\1016492\DC2\1038033\FS\\P\FS\171319\v{Y\DLE ' q!\1045478g\1015953T\1017164^Lg:\1043756)~\183956&R{\1077936\188232X\ETB\1096513\1012477z\5029s.8n\n\58208\DC2|\SYNRP&\b\ETX7\ENQ\aU\1003317{\DLE0%v5 \18048\SO\94341EU\RS\3357\&8\SUB/\4793W['`J\1067364uv\163012\1062281\DC3\DLE\EM\1083526+P7\161606P_X\100493\1036346\&2\1060605\&54\184580xB\1054288g\180383\"\DC1\SOHrC)0^\SOHrQ\fpd\46566\1106204\59787\18367B\NAK\162313\42281s\1005088\&6gi6m7\144814\EOT\ENQL\DEL\f\100019f?\CAN0$\145372\ESC]>CV\nVPb:^e6\7528\ENQ\1049417\1094717$!L\1052468v\60090v\SO/:\SUB\"\1052989o\170175\fe{:\1002279\6755\DC4\SOs\a\DELz~\62543%H.xF\1041929\165988h\ETB\985392\ESC\1061053E\ESC\170298\&7\DEL\SOH\v8\97125:PS: \1092223:\1039879\&6c0\167650\1088174$\1102815{)|\GS\1053757K\f\RSa#GK\1046896\ETXm\STXbr@\6481\r'bv\b\996462\SYN\1018321dv\1000550\"\NAK\171594\ESC\1034597\DLE>\70305\b\164202\9087S/[\1093372];\10230A$\n5S\156734_y\188649\ESCP]e\CAN\180901\1110553\160289-\GS\1078851\GS\1098241\1048062&P$\GST\120646TLX[\49486\129523)\ETBP;\45799\SOm\1113854D\1061956\1043723>EI\NUL\NAKI\CAN\1058606\t[AtgxM\SYN\1038293^\1101184\b^V\143698+l)n\1029632\NUL\"\1104565y\a.\DLE"
),
- newClientModel = Just "\150744"
+ newClientModel = Just "\150744",
+ newClientCapabilities = Nothing
}
testObject_NewClient_user_8 :: NewClient
@@ -165,7 +175,8 @@ testObject_NewClient_user_8 =
( PlainTextPassword
"\b\RS\1083911F\v\"p\184881\161833(\CAN\STX\SUB{1.\STXSh1\EOT\141013J\68124\159527\52361?\16802\DC4hg^h\1058009&\SOH\a^\9060\1109232V\74979\r\DEL~\nHD\"\ETBTC#\152775\57858\DC1\1029658\f\9672D\SI\ACKX\CAN?T\STXQa\v:}\ACK\1043380\FSv\ENQ\EOT;F{\69424\168419P\24246SNm9yl\ACK\1039741zm\r\\ym\GS\NULVYm&H\ESC*t\181898p\988616Sb\46454\1041292od\175159\&3L\58851\1048155\SO$3\1079098\RS\r\12927\fy\173674B]\aj\DC3g:\GS\147498\&7\ESC\NUL1y79R\ETX\124968\NAKo\1030373\GS:C\EOT&3g\ETXPonO#u\DC4A5=z#7>+R\181936\34025Qk1\42728\FSF\DC12MY\1045697\1056260oqk\t/\fWI+1!]}r\DC3\ESC\NAKK\DC1*M\STX\1071403\SOH\EOTqe\CAN\134249\&1]bj/t\127036,\DC1jk\EM\1096906\DC4\US/\1018712\2562\SO3\119865k\DC3\1089523\FS~\EOT3\1017007{\46838Hf(x\SI/Q\1052752\nzgdyT\1112482\&4CE\1061698\FS\b\f=v\EM\1052769+C\142840\642f\1012060x8b\DLE/\ENQ\RS\rQ;Ec\181947\984650\SYNQM.)\EMk\EOT^\"&~EQmQ[\1109601GR\SYNz,\1112207kdR\SUB\DC1M\EOT!Nfn)\994438,oFb~\ETB\DC1#\DLE\\%\FS\EOTC\997002\1005097\8330\60433.\171514G\1104943,{\EOTR\174171=\1108508\135699\1049776g\DLE\142921\\r\DLEF~V\35187\47168\ACKP\10702&hC#R#z\DLE_\DC2\1080508\1089113dK\156671\22090\&2:M\1003119t\DC4\120237\\\995393:\v\ENQX\\\174898,\"\1018585:\28181l\FS\145440X\1061109hv,V$'\EOT0zk["
),
- newClientModel = Just ""
+ newClientModel = Just "",
+ newClientCapabilities = Nothing
}
testObject_NewClient_user_9 :: NewClient
@@ -183,7 +194,8 @@ testObject_NewClient_user_9 =
newClientClass = Nothing,
newClientCookie = Just (CookieLabel {cookieLabelText = "\SUB"}),
newClientPassword = Nothing,
- newClientModel = Just "m{"
+ newClientModel = Just "m{",
+ newClientCapabilities = Nothing
}
testObject_NewClient_user_10 :: NewClient
@@ -200,7 +212,8 @@ testObject_NewClient_user_10 =
( PlainTextPassword
"\SIxXs\STX\1049513\&2\1102168\59074\54772\53296X\v\1090222\EOTIk\176588E?ZQ\132276O3\63616X\992817!#B_\152795}\1084694y\1054376PM\ENQ{\DC3\157254Ln\ACKF\DC2\NUL\11009\&6g\36110\991920\25715$S\DC4\SI\1004033\&4'?z\1081305\&0\39720A\21773\RS\DLE:pg\ETB,_V\34799\27560\1040999U=S^$\1041670u:X\63822~/*\50548\DEL2\ENQd7pJ-\DEL^\NUL\a\67607\&6a\158943:%\1062864$\1077297\&69R\SUB/G?C\ab\SOHl$/K\ETX\DC3\142946g 4J3\NUL#\31582\986721\DLE2\ETB\175407vV2\EM6\96746\v\36097W\54750\176689+4tk!+Y\152620\1104329\NAKQ\1023294\1105747GHQU\bx;c_\SUB^\34594]S7\ru]\r\1034086!\1067469\150060\&2\ESCMg\1074803QE\NULm\NUL\STX\DC1P\155051\v(H\1011822\1031529|g\1082510\SYNb\DEL\FS\ESC?=f\1018763\54754\DC3\NUL\ACKg\US6\172308Q\52355q\t\"\190130b\DC2\138877\&3K\138660\&9IxGW(\t:\f$9\74183\SIU?(\177077\fgI;4\EM\147733\ENQ\"RGbgCf9\DEL\64069Io\1105067\51831\&8wb(]\US\1061721!\1061150\1078273u)t\DLEkO\NUL_\DC4\ETB-\RS 4\a,\f\19767Qgd\1078960\22989\1062074\DC3;\986351~\1092523Ui\FSH\1047632\\\1040094\DC2vmU\983357D\134769\EM\984607\ETB2D\20960\40111\ACK\SUBJ\SOI,\1085675TgXb\SOH\FS\1094376\ETX\1096029C2\127781\&7\1032517P \SUB\CAN(8htD\DC1\30721\162821\1067602WzoM\GS8\v.[zIG`T\164362<$s\1076321\165910\SYN0=f(\ENQ?v^?_\7818X>7\21617|EQz\1039534q3\tr\1080514nq$yq\137821\&5\987933i\1096583h3\ENQ\1079332\19161Ac\1003179s\155333\34595X,\EM\1089910>\47776\DEL2\ENQ}\\M\nd3%`>\24917?\rv\1053845\35041d>\twE\188031G\1031126\&7\1023454\ESC{G\1085555\SO\SIv\1055004,cP\1060712\1019814\"4\DLEM\SYN\178587\1021477\&8u\1078424n\SOw\ETXV\US\DLE\DC2\fXf,\1036282b\1101047\">&0xFqd]^\DC2\ENQ4@9\NUL\94493B\73906}L\v\n9S\EOT\SOH5im\DC2-\95629}\SUB'\98821#,\1034274c\ACK\ETXP$xc\137033tB\DC3\ETB\35422\SYN0\ESCu\\\1048883b\23147\"\EOT\1006915bwE:\b\DC4\1065071vp8\1022622\SOH\DC178$E\DC2JE>{?\6021I\NAKJ*7sI\ESCeu\DEL\1077673\10421\&1a\NAK)R\185144|PJ\1111255at\SUB)\ai2\1009821mY\46293~gdde>\1010509yG5b\653 \132576\1001031zJ\984798\DEL\NAKT?i\f\1064196F,]9\GS\46035\DEL%hB\128429h\34802\&9\168858=K\NAKrFVWO\CANVc\65362\1065989\1092750\NAK\1021958\ETB83\ESC4\"\1035406\DC2R\1073182\113709\&1UD\1055011Fi\1059797\&8\EM?j5j\998683\SOHW)\1057415fJ*m\1074569d\127187l\DC42\1086873\"Xgp\95800v\"\ETB6U\22131w\SO2*\174690\21425vl\CAN0\ACK;jh\3871IC\SO\1026262L;55\CAN\1031309\ACK`m3*. C)\70284\156404x>y<\1104362+>*\ESC\vs\SOH\1088826NSChj9\49235\"#M`?\54746i\ENQ\US\STX\GSlcS\1047710\EMC\ENQ\SOH_/u?/\38071\997094g\67263\175233*\STX_v\1060509\DC2C\DC3\ETB\174166J~637\DLE\t\DC1P\DLEy\66836'Wz\SYN8\1008920j4\1374d^7\22270JF&,\SUB\47147"
),
- newClientModel = Just "9FO"
+ newClientModel = Just "9FO",
+ newClientCapabilities = Nothing
}
testObject_NewClient_user_11 :: NewClient
@@ -221,7 +234,8 @@ testObject_NewClient_user_11 =
( PlainTextPassword
"\ENQWU#*\b\169584\ETXp\3616\DC4c\1009690e\47037m\1102567\r\t\1012852\7852\NAKz7\ENQ\r?I~\1054524;>$]I\SO\42203\DC1\164569T\1112610\48142\ACK\FS\179049y71,h\12763{\31082`\n\r\173936+N\1096448\GSlqmi\1099779\SOH\EM=T#%\1022504\ACK\fZ_UCe\SIk\"\n'%\1076023I\US\RSZR.n%\NUL'`u5NI\ETBx\ACK+\65807j\170243(x\DC1;\181398zyUe\f\191269\37010\DC2ow\1044794?-Wz\992114Z+\SOH\n\1083159\ESCly\1047059\&6l\SINn\SIT\ESC\EM*\1083747\&8&E.\b\DEL\148620\1070806\EM_\n{\188784\RS\158747\SO`\1019622\46413\990554^1\139313=3X\DC1K\NAK\RSc\SOHB\1078866B\GSm1$\FSDve\142150g\SI\1087970D<\SUBKo~X\SOH\1094699\1105344-3\1058387\"\9645\39804e6G\ETB\1011308\1111485\53599\&3eM\EM31[L\SO\DC4@\132573F\US,b\199\999413\v\STX\986195D\DC2V\vX\SI~\1097246 nS/\57971\169887RTD7cMd\DC1\EOTOw\994792yO[8\1082860\60631\100663[\185643>\99795\nA<=PE]'\DC2\986494\&7I Q\1069408\17420E=\SYN\ETX;v\CAN\SUB\163809!ek\DC2B4\STX\EOT\1100350\178438\13392b\t@{0\1052374\181553\151609\"\ETX\ETX9/R\v\133430n\DEL\1028100\ACKA\DC3\n:\126546\46116\45354\189672%Z\US\r[EYo\DEL0N\RS\ENQKR\1033145\1099889~c?\EMJ?`\DC1\1024319\99332\t\1037670h.n|\SUB2\987386{'\144962\1081004/^ut\100480\134584--\DEL\1054725\SI\52605\1034550T?\1000376\177692\1007643?\DLE\1065650\&1\179681*6\SOti6\134585_Z7\DLE\NAK\ESC\SUB\a/M,\r\1013726\27638\1049228lg\GSjH.uC$\50698^1h)\b,\ETB\166565a\1092454R/\1035274_\SI.tx\ETX >\40781\984980%B\ETB\1009320\14711\&5\STX\153557\f\121255\136884\57749}-j\ESCyO\CAN\47414^\1051627F\51571J\EM\SUBE}&9w\78649u\r\19329nY\369q\195010\DEL*3\b\FS\140122\1051712~Rx\SI\n\ACK\n\62186a?S3\25573\bn\"gf\1113756\57969F&\16122V\ETB\1024468W\1015855\SYNN\ACK\SYN|r,;.w$\FS\b\DEL.ro[V\987558\1015707&\SO\17069+\16921=\ACK\\M\EMYpXZ|Jnnr\1052032\\\1004404\23525A}H\164247\ETB:_\DC3[\SOH2%\rh\FS\ESC]\92372\DC3\190770;\f\1105269#\r*0\92455Z\58095\141267\153723\1031644\1084692\&2\148115\\ 7\25709\176908\NAK\49305\37904\179314p\1099243]P\93039q\v\RSi\NAK3FX\74130\&8\SUB\33013J\DC3i\22841#\239K\1037511%5j\179793\&6\40198\175557\1113225\82980]B\US\1045028\&3\1024025-o;9>\1009214A3~\ACK\FSR\41975\140632\SOHa\fiFV\1002037e\b\1035521\96860n*\1008115\ENQ\149608\ETX\GSnp\DC4m,\r\1058496hb\DC3\1069244\34144,&d"
),
- newClientModel = Nothing
+ newClientModel = Nothing,
+ newClientCapabilities = Nothing
}
testObject_NewClient_user_12 :: NewClient
@@ -245,7 +259,8 @@ testObject_NewClient_user_12 =
( PlainTextPassword
"T\166327fq\DELdA\18130k3\1105137[\rK\n\f^\1090853\&0\98095\DC1\ETX'\97298M+\ETX\a\ETB\nw\SOH\1797K\161329\11176z~qp\153477\ETB\GSW4m@CYG(\49998ob:\CANP~My\ESC\175657\v\135887\"\34075&\71209X\1070912r\DC1\131932\1022615v-p+))\ENQ\STX9i {&p\164508[\r\US&;b>N\ESC~bu\DC3\v:W:S9\DLE1L|\\S+b0Q\STX\31885rs~;\USG;\986684o\1008621\3277%\STXm\DC25UTR\n\DC3X\ETB\1058418~\RS\62657\DLE;\SO/\US-\\mPc<:.HmK\175151o\f\SOH\1094091&B2\aR\24795\&51\GS&-\179688c\183928\988858t~rT\175316H?\60066\&6\155512~J\1111920q,\STX\RS#\1085689W.=\52405L\1073572-\1077186\ACK\173126\152012\&75*Oq!\33433{x\DLEt\1077477\993588X(w.\EOT\r\60577\1111873}kp/\th,z\\\27305\ACKn+=\23672jL~F\NAK\32287\&7\a\44660 ,0\517b\ESC\DC1\USoA/\ACK\v\1005276\EOT[o(z+c\SOH\35973s\35678C\31719\\W6\NAK'\STX\1065586\&6Xb~TO\1106854\1078560U\36772\SOH\"H2\1071196\DC1\1111474\1070801O#\SIu\SI\70804\198B,4\917939\1103645L\1098719Mt>I)U?p\SIrQ\991975\1024380\&7\170637\1058486\DC4\ESC\n\3616\n$\1019615\abhXT\DC3w\36477 x\7606\ESC?p\b\ACK\1019692N\1047942=j<\156592=)\n6\25048ZF=*`(\DC3\SI\1084532\ESC\FS\172082\ETXyR},\1088502r\GS\US hY_\1059030\1053557\31965eHsWj\1065305N\51163H6dd\DC1%g\1020583\&6\166285=KX:V5l\1011535\&0\r6K6?\1031425\CAN\1016917:,x\994043_{rgc\bI\SUB\65378\1094330`\\\EMl/`\t\US\DLE`r\1030112+E\2755\93034L\991483\EM\2608d\1049231\153892O\SYN\1095453.\GSom\28655v\1086471\DEL\154279d@\1076849.O,*\151457K\NUL[)H|Y\ape\ax\\P\77938 N\1012329\1083848|\f\1026650c\SUB\n\\+\SOH\SUBWCk\SOHe\ETXG.B\NUL&g@L\ETX?B\SOC\1035168\ESC.\r\GSv(uz)\EM\19888\1027956Y?/R\DLE`]g1o1\NAK:\US5\1018150\1027769\SYNKS\CAN?\110635\1069040\998938\1073365#bf\ESC\1047091mc\54492Q"
),
- newClientModel = Nothing
+ newClientModel = Nothing,
+ newClientCapabilities = Nothing
}
testObject_NewClient_user_14 :: NewClient
@@ -283,7 +299,8 @@ testObject_NewClient_user_14 =
newClientClass = Just PhoneClient,
newClientCookie = Just (CookieLabel {cookieLabelText = "\185625 "}),
newClientPassword = Nothing,
- newClientModel = Just "\1108879"
+ newClientModel = Just "\1108879",
+ newClientCapabilities = Nothing
}
testObject_NewClient_user_15 :: NewClient
@@ -306,7 +323,8 @@ testObject_NewClient_user_15 =
( PlainTextPassword
"0\EMM~K\162154\45005\164305\1004413\172280y\1003918*\174546\1042834\rE\DC1(\1088983\1005968+\176459\DC1\1007952\DC2\1078947\n:\1032500Ig\SUB\74639l\EM\43295p\EOTx\1094422#\b\1043804~\1003046f\SO\168069L\14137\&5\1072066x[#&\DC31qH\132531eA\b\36117\1000509\1112836U\DEL\tb'\125239\&0\DC4\1099298\46462iELNQ\ba\RSb\v=\7552G}s\GSN\1025484$L\DC1\ETB\\\1082834\98732\151628\"\998010\&4\CAN\DC1H\SUBt\1105766|\FS6\137171\SIqu/k/b\1002207e4/\FSD\1084290\v\1047428R\38606tL)\1018496\&6Q\60194\RSd\46542\146946qzqbC\f\EOTpS\DLEY/\990314\1108440T\1044302\21442,tw~)k6\b\USZc\1000679\ETB\1053814\FS\142098Z;6'\9971\DC4e8)4YT\1049079\&7gm@AD\178053\b{\175557\SUB'x5\US\n\DEL\nv\138741\1106818L[\35688\46936N\979N \61464\36923O\50044\&8\1023723\33945\DC1\GS\EM\STX\1011210\b\DEL\180649\990944yX)\139946WmBo\t\ETXFj=5n\1088694\US\170101*\NULWuDo7\vLt]\1041373ff\52366O\1109893\DLE4%SqD\136650][v \DLE\NAK\SUB\SI\1107671\171196\r.Z\RS\54261\US\FS\1032146"
),
- newClientModel = Nothing
+ newClientModel = Nothing,
+ newClientCapabilities = Nothing
}
testObject_NewClient_user_16 :: NewClient
@@ -319,7 +337,8 @@ testObject_NewClient_user_16 =
newClientClass = Just LegalHoldClient,
newClientCookie = Just (CookieLabel {cookieLabelText = "q\43234\185884"}),
newClientPassword = Nothing,
- newClientModel = Just "\ACK;\143320"
+ newClientModel = Just "\ACK;\143320",
+ newClientCapabilities = Nothing
}
testObject_NewClient_user_17 :: NewClient
@@ -337,7 +356,8 @@ testObject_NewClient_user_17 =
newClientClass = Just PhoneClient,
newClientCookie = Just (CookieLabel {cookieLabelText = "k"}),
newClientPassword = Nothing,
- newClientModel = Just "\ESC\15411c"
+ newClientModel = Just "\ESC\15411c",
+ newClientCapabilities = Nothing
}
testObject_NewClient_user_18 :: NewClient
@@ -354,7 +374,8 @@ testObject_NewClient_user_18 =
( PlainTextPassword
"\"5\RS\160225\60188\9236\1112830Y\1031107]\DLEqDD\1045338g7G\992982\75047\DEL\39442\133937ceE\189589 Nx}\172612\tF\21563a\ACKN\f\DLE\1040194)U\58059\1021976v\"\SO#\1063353Cb/Dc`;k:qA\USQcsw[uG~\29313\&2i\ETB\157089RB\t\1078526\r(\f\10102\1060258&\SOHd\1105089\155519f>e2~\999553u=\1059210\10136/\49832\1099898Q\65153qT\1092626\148106Sa@k#(\ETB\a>!m=5vN~_#\1060173B\184540N\a\94462\64032.K4\152591s\156905zT\172441vv7/\EM\"#1*'q| \157195\DC2R`\ACKO>P\152635Ga \rQ\DC1s\1083048nh+q\150871 N\GS\CAN\RS\1054551\\;\12308\vMyY\\w\ETB+8bJ&2?D\1069890Y\1008350#x\162454\&6A\1106082\&85\64170\183343\NAK\145787Chh\22172VJ^R\CAN'F\160371[\ACK\n:%\1087606~}\ESCEAS@Wn5@\1096877&a2\1088377`\120586\8472\&5\CAN^\EOT5W\EM\SUB\DC1R\DC1D\RSL2m5XofV?Y\155649\SOH\DC3\1019336g\ENQq;\1001052\12738oP\SYN,\DC1\1073001\994800>\14799\7333\165461\1051770W&lHVm\1008163B\vl@%\1039130U#\NUL\1093381$XbM%q\1045263\FS0z\1088980\DEL\ETXr%.\1073307\32341\SYNZ0\fB+_@\DC1tcO\185752\1093870|\186862&~\136183N\45192v\155081\137844`\44461\&1\1014483\1108323\1093550\9737vf\SYNEJb\984223\DC3\nG}\SUB#_$D\1066925\1020860/\RS\vf\DC1\160209\DC3 \v8'GP\rb\DC4\48192\984278\30001?r \135911\1100614\n\160781d\bVStN\"\SYN[G\1586\ESC\US\1011464g\135744_\SOHY1?\USdu\1059402\74951\995455=\1074262})J\1072682$X]Y\DC3\1061380h\DC1H0\STX\66830\1027029\19815\b\11503C\DC4\DEL\a\1074263\1024496\RS|oTc!eU\992641Pr\138670\NAK\RS4\185988DG\136753x\995479FG0P\50986/F\RS\STX\ETX\171779*\ETB\SYN\17831Gw]\1015631\1080624\1069774\1061830m}tg6\166134\SI*\173230\\Wi\EMOa'\FS\1063048\1000576sh\1107838\143056lE+&\ng`\1004945\&3kY\GSY=\DC14`\1062494l\RS\1086111!r\20132\NULw\120334\1077517?\ETB\STX\1082825I\SYN\ngWcrN_)7f+#\ESC\178930\1076598jt\DLE\71890hAx\\-{\92491^4rf\DLE\189136\&6MJb__\48310`n\70443\&4oZA\43460\92533H.-Y {\a\FS\USu[4@6\1045998\94370\3461\SYN\DC2/|,]\NAK\1012422E!\DLE\rZ\178451Q\181143\1090192\&8.X\fwZ\DC4\DEL-{KWit\53903 >\175517L_Y5\GS\SYNaE\40086\&0Bf'M\DC4\1015266\35448\ESC\1009511\&2tF)\1092249&xf9|>K\EM%z\a\1015215\fH\120884\RS\b\1063192\vJCmoJ\t$\r\40361S0\134532k\ETB\DEL\995717z\1023854\b7\59291\DC2ACx\1092749p\45751-e\128647\GS&? \SOHSt}S'\1027538qm\1092436\54380\EM\\c\ESC\40780;\1004615\31215Y8mz3\STXROJbY\\\6422\1067862w\147906 in\SI\NAKnm\189428r\1066116\"\DEL\1004774%*U\66603\DC1C\1106018h\a\DC1MIQWC\172322{%\SI\118975\54512<_$\\d=\SOH)-4~x\179098Ov\NUL\DC4\185898p\1052353\24161c\999358,\23825o\SI\1027216N\STX\1037726\158923B'd\1006564`\1061268W%\1067374\1106444@hx\SYN.\GS\164392cJ\10971M\1033250\983450,I\DC2\GS\ACK!\18551\163976\564\EOT\DC4:\t\DC4\v\\\1109669]\1075069\139170r\CAN\155314%\STX\SIp\96570r\3758\DELR\FS3X\fsW\23719]L\155502\&1`J\ESC\SI\19051\&9gM"
),
- newClientModel = Just "\1077465\1056032S"
+ newClientModel = Just "\1077465\1056032S",
+ newClientCapabilities = Nothing
}
testObject_NewClient_user_19 :: NewClient
@@ -375,7 +396,8 @@ testObject_NewClient_user_19 =
( PlainTextPassword
"#\1113416~\nk\1060135J\188666POZ\f\SUB\ACKJJ\RS$\11618\EOT\42121lL[\FS]h,Xf\1042350\CANd\165029\US\65203\1079326t\ETX]\195015\155339e\195099e\DC30q\44188\189121[WSGO{\b=Hs\1089312;6\SUB\19036\"\SO\134697&\1112323]Xmx\143526\SOHi\9442j\v\SYN\NAK\1040391.\f\ETB\SO8E\55091i\1044526P`o\1033279i\137855\SUB\ESC\71171\1104725}8\143576\1001544\39043\113800\ESC\1016284,v\1025287\t\169739^\n\190789\1032336yOX@F \EOT17uq\58778G\10534\SI\GS9?8+O(\1034807v4,[k \14191--Qk\159337\ETXA\EM\164962\&9\138800\984565@\18866\1099020\147346\134891Q\US4;\DLE\r\f0\92231fz\aDC\157549\&3\1045725|Sn&i\EOTj\1051374\128703KS7\146137\fAx\n\ACK\vx\1111954\&5\129488MWIp$m\1060695\118857m\991735\185401\118823m\16484\DC4\SO9SDb-b\a=F\78227\25535\DC47/{\1063538d#X9\SYN\44105\45323/{=\1020413Z\DLE\164180xh\38176A\t\STXJ\a\1022997\&9Bk_\1091831H^\166229g[38&\ACK\150410\1081436\&74\96537Dwdu~r\70508\1013335\1071132&\184387Z^\rl|\f\ETBA\STX~2r>\EOTsM\171720iX~`\ACKzK\EOTW\1099149\1028596ezCh6O\SO%\SUB\SUB=\149997:\1070391DQ\NULc`\SI\aDmG)M)Th\190731\NUL[u\ETB\SYN\"?\EM03\188014xi{h\fy~??)w\NULy-IaG\v\NUL\52252\157648J\1047554g(\n\DC1\SO\t+p\ENQSl\78666\1079369\1018476\"N=Q:/\9007[\161661\r\NAK\147739B\37332@kA\USwi\NAK,p=\40491l6\66318'\132639\120855t\RSWq\985403W\SUB\STX\ETX\r\74459\156671C!j\DLE\SI>\\W\1004807\ETX\152436q+\"\10840|o\990028\RS\1031899\162664$\1033161\43937B_\NUL\1076842DV\DC3\177090W?rH5Xi\v\SO\ACK\187975N-\164527PQ2`\174057r\ETBga\156\FShT#ay}^\ESCP\1089292yp{\DC2#^/5X-D\NUL\ACK\171851\RS\60072\1033438\"}Z`x{\1055488TT_ Z0\20103\1039639\30357k\DC3Q.NT2/\1095308O\SOHP^G\SYN\60717/%d.\172353\986193P\f2iYS@\t\t\DC4.o\11544'?-0a]\97289)f9w4\136279X\182987\181688\DC1\a=\1087084T\142663g\RSb7\GS@\1070005V/,\1029412K\189653\DC3MP\n?W\1008141\&5/z\1046740\ACK\62886u\98299nKB\US\36186\DC3U\NAKh\USr#\1078423\"\1072189-\142719(K\183599\SYN6\170953\1098238+b\1088379s\1111208\STXk\1077908\149688\1103504~Rl\SUB\RS\f\"X3\163579\SYNOQRpJ\1037359\SOHWvE~\48192I5\993324\22741j\v=PK\135321%#\ETB\fN2\19120\181456vz9\1012476nY\DLE\SYN=F\STX\EOT\3416\&9W\13458\DC2^C)ZX\ETBJN2P\1003841\SI|\\\1004102h}P1V\1113257\&7\ESC\v\DLEl\181234\FSz\EM\DC2\1093528IM\993293\SOH/NBiM\170360~;1x\49216Md\EM1\52727\14564e#\"\US\94465eV?i{\1112978\1104388\1010293\151662&\FS\SYN\3436\153277#"
),
- newClientModel = Just "\CAN\1030222g"
+ newClientModel = Just "\CAN\1030222g",
+ newClientCapabilities = Nothing
}
testObject_NewClient_user_20 :: NewClient
@@ -388,5 +410,6 @@ testObject_NewClient_user_20 =
newClientClass = Nothing,
newClientCookie = Just (CookieLabel {cookieLabelText = ""}),
newClientPassword = Nothing,
- newClientModel = Nothing
+ newClientModel = Nothing,
+ newClientCapabilities = Nothing
}
diff --git a/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/NewConvManaged_user.hs b/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/NewConvManaged_user.hs
index ee0c0afee55..8711efb108f 100644
--- a/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/NewConvManaged_user.hs
+++ b/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/NewConvManaged_user.hs
@@ -18,8 +18,10 @@
-- with this program. If not, see .
module Test.Wire.API.Golden.Generated.NewConvManaged_user where
+import Data.Domain
import Data.Id (Id (Id))
import Data.Misc (Milliseconds (Ms, ms))
+import Data.Qualified
import qualified Data.Set as Set (fromList)
import qualified Data.UUID as UUID (fromString)
import Imports (Bool (True), Maybe (Just, Nothing), fromJust)
@@ -38,6 +40,7 @@ import Wire.API.Conversation
newConvAccessRole,
newConvMessageTimer,
newConvName,
+ newConvQualifiedUsers,
newConvReceiptMode,
newConvTeam,
newConvUsers,
@@ -48,11 +51,15 @@ import Wire.API.Conversation
)
import Wire.API.Conversation.Role (parseRoleName)
+testDomain :: Domain
+testDomain = Domain "test.example.com"
+
testObject_NewConvManaged_user_1 :: NewConvManaged
testObject_NewConvManaged_user_1 =
NewConvManaged
( NewConv
{ newConvUsers = [],
+ newConvQualifiedUsers = [],
newConvName = Nothing,
newConvAccess = Set.fromList [],
newConvAccessRole = Just ActivatedAccessRole,
@@ -74,6 +81,7 @@ testObject_NewConvManaged_user_2 =
NewConvManaged
( NewConv
{ newConvUsers = [(Id (fromJust (UUID.fromString "00000002-0000-0001-0000-000400000000")))],
+ newConvQualifiedUsers = [Qualified (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000001"))) testDomain],
newConvName = Just "\995491\SUB5",
newConvAccess = Set.fromList [PrivateAccess, InviteAccess, LinkAccess],
newConvAccessRole = Just NonActivatedAccessRole,
@@ -98,6 +106,7 @@ testObject_NewConvManaged_user_3 =
[ (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000000"))),
(Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000100000001")))
],
+ newConvQualifiedUsers = [Qualified (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000001"))) testDomain],
newConvName = Just "NwK",
newConvAccess = Set.fromList [CodeAccess],
newConvAccessRole = Just TeamAccessRole,
@@ -128,6 +137,7 @@ testObject_NewConvManaged_user_4 =
(Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000100000001"))),
(Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000000")))
],
+ newConvQualifiedUsers = [],
newConvName = Just "k\61561-",
newConvAccess = Set.fromList [PrivateAccess, LinkAccess],
newConvAccessRole = Just TeamAccessRole,
@@ -159,6 +169,7 @@ testObject_NewConvManaged_user_5 =
(Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000001"))),
(Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000000")))
],
+ newConvQualifiedUsers = [],
newConvName = Just "v",
newConvAccess = Set.fromList [],
newConvAccessRole = Nothing,
@@ -184,6 +195,7 @@ testObject_NewConvManaged_user_6 =
(Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000000"))),
(Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000001")))
],
+ newConvQualifiedUsers = [],
newConvName = Just "P\1098873\r",
newConvAccess = Set.fromList [InviteAccess, CodeAccess],
newConvAccessRole = Just PrivateAccessRole,
@@ -213,6 +225,7 @@ testObject_NewConvManaged_user_7 =
[ (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000000"))),
(Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000000000001")))
],
+ newConvQualifiedUsers = [],
newConvName = Just "\CAN",
newConvAccess = Set.fromList [],
newConvAccessRole = Just ActivatedAccessRole,
@@ -242,6 +255,7 @@ testObject_NewConvManaged_user_8 =
[ (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000000"))),
(Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000000000000")))
],
+ newConvQualifiedUsers = [],
newConvName = Just "&",
newConvAccess = Set.fromList [],
newConvAccessRole = Just ActivatedAccessRole,
@@ -263,6 +277,7 @@ testObject_NewConvManaged_user_9 =
NewConvManaged
( NewConv
{ newConvUsers = [],
+ newConvQualifiedUsers = [],
newConvName = Nothing,
newConvAccess = Set.fromList [PrivateAccess, InviteAccess, LinkAccess],
newConvAccessRole = Just NonActivatedAccessRole,
@@ -284,6 +299,7 @@ testObject_NewConvManaged_user_10 =
NewConvManaged
( NewConv
{ newConvUsers = [(Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000001")))],
+ newConvQualifiedUsers = [],
newConvName = Just "z\1112901",
newConvAccess = Set.fromList [LinkAccess],
newConvAccessRole = Nothing,
@@ -310,6 +326,7 @@ testObject_NewConvManaged_user_11 =
NewConvManaged
( NewConv
{ newConvUsers = [],
+ newConvQualifiedUsers = [],
newConvName = Just "r",
newConvAccess = Set.fromList [InviteAccess],
newConvAccessRole = Just NonActivatedAccessRole,
@@ -334,6 +351,7 @@ testObject_NewConvManaged_user_12 =
[ (Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000001"))),
(Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000000")))
],
+ newConvQualifiedUsers = [],
newConvName = Just "",
newConvAccess = Set.fromList [PrivateAccess, CodeAccess],
newConvAccessRole = Just TeamAccessRole,
@@ -360,6 +378,7 @@ testObject_NewConvManaged_user_13 =
NewConvManaged
( NewConv
{ newConvUsers = [],
+ newConvQualifiedUsers = [],
newConvName = Just "\tB",
newConvAccess = Set.fromList [PrivateAccess, InviteAccess, LinkAccess],
newConvAccessRole = Just NonActivatedAccessRole,
@@ -397,6 +416,7 @@ testObject_NewConvManaged_user_14 =
(Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000000"))),
(Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000100000001")))
],
+ newConvQualifiedUsers = [],
newConvName = Just "",
newConvAccess = Set.fromList [CodeAccess],
newConvAccessRole = Nothing,
@@ -424,6 +444,7 @@ testObject_NewConvManaged_user_15 =
(Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000001"))),
(Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000000")))
],
+ newConvQualifiedUsers = [],
newConvName = Nothing,
newConvAccess = Set.fromList [PrivateAccess, CodeAccess],
newConvAccessRole = Just NonActivatedAccessRole,
@@ -445,6 +466,7 @@ testObject_NewConvManaged_user_16 =
NewConvManaged
( NewConv
{ newConvUsers = [],
+ newConvQualifiedUsers = [],
newConvName = Just "",
newConvAccess = Set.fromList [InviteAccess, CodeAccess],
newConvAccessRole = Nothing,
@@ -478,6 +500,7 @@ testObject_NewConvManaged_user_17 =
(Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000000"))),
(Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000000")))
],
+ newConvQualifiedUsers = [],
newConvName = Just "",
newConvAccess = Set.fromList [LinkAccess],
newConvAccessRole = Just TeamAccessRole,
@@ -499,6 +522,7 @@ testObject_NewConvManaged_user_18 =
NewConvManaged
( NewConv
{ newConvUsers = [],
+ newConvQualifiedUsers = [],
newConvName = Just "\36412\tJ",
newConvAccess = Set.fromList [],
newConvAccessRole = Just ActivatedAccessRole,
@@ -520,6 +544,7 @@ testObject_NewConvManaged_user_19 =
NewConvManaged
( NewConv
{ newConvUsers = [(Id (fromJust (UUID.fromString "00000003-0000-0004-0000-000400000002")))],
+ newConvQualifiedUsers = [],
newConvName = Just "",
newConvAccess = Set.fromList [PrivateAccess],
newConvAccessRole = Just ActivatedAccessRole,
@@ -546,6 +571,7 @@ testObject_NewConvManaged_user_20 =
(Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000001"))),
(Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000000")))
],
+ newConvQualifiedUsers = [],
newConvName = Just "\SOH?(",
newConvAccess = Set.fromList [PrivateAccess, InviteAccess],
newConvAccessRole = Just NonActivatedAccessRole,
diff --git a/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/NewConvUnmanaged_user.hs b/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/NewConvUnmanaged_user.hs
index a135ebb6385..b7ff8111f6f 100644
--- a/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/NewConvUnmanaged_user.hs
+++ b/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/NewConvUnmanaged_user.hs
@@ -18,8 +18,10 @@
-- with this program. If not, see .
module Test.Wire.API.Golden.Generated.NewConvUnmanaged_user where
+import Data.Domain (Domain (Domain))
import Data.Id (Id (Id))
import Data.Misc (Milliseconds (Ms, ms))
+import Data.Qualified (Qualified (Qualified))
import qualified Data.Set as Set (fromList)
import qualified Data.UUID as UUID (fromString)
import Imports (Bool (False), Maybe (Just, Nothing), fromJust)
@@ -38,6 +40,7 @@ import Wire.API.Conversation
newConvAccessRole,
newConvMessageTimer,
newConvName,
+ newConvQualifiedUsers,
newConvReceiptMode,
newConvTeam,
newConvUsers,
@@ -48,6 +51,9 @@ import Wire.API.Conversation
)
import Wire.API.Conversation.Role (parseRoleName)
+testDomain :: Domain
+testDomain = Domain "testdomain.example.com"
+
testObject_NewConvUnmanaged_user_1 :: NewConvUnmanaged
testObject_NewConvUnmanaged_user_1 =
NewConvUnmanaged
@@ -56,6 +62,7 @@ testObject_NewConvUnmanaged_user_1 =
[ (Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000001"))),
(Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000000")))
],
+ newConvQualifiedUsers = [],
newConvName = Nothing,
newConvAccess = Set.fromList [PrivateAccess, InviteAccess],
newConvAccessRole = Just ActivatedAccessRole,
@@ -77,6 +84,7 @@ testObject_NewConvUnmanaged_user_2 =
NewConvUnmanaged
( NewConv
{ newConvUsers = [],
+ newConvQualifiedUsers = [Qualified (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000001"))) testDomain],
newConvName = Just "\128527\1061495",
newConvAccess = Set.fromList [],
newConvAccessRole = Just PrivateAccessRole,
@@ -103,6 +111,7 @@ testObject_NewConvUnmanaged_user_3 =
NewConvUnmanaged
( NewConv
{ newConvUsers = [],
+ newConvQualifiedUsers = [],
newConvName = Just "f",
newConvAccess = Set.fromList [InviteAccess, LinkAccess, CodeAccess],
newConvAccessRole = Just ActivatedAccessRole,
@@ -129,6 +138,7 @@ testObject_NewConvUnmanaged_user_4 =
NewConvUnmanaged
( NewConv
{ newConvUsers = [],
+ newConvQualifiedUsers = [],
newConvName = Just "\135359\70751z",
newConvAccess = Set.fromList [CodeAccess],
newConvAccessRole = Nothing,
@@ -159,6 +169,7 @@ testObject_NewConvUnmanaged_user_5 =
(Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000001"))),
(Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000001")))
],
+ newConvQualifiedUsers = [],
newConvName = Just "X9",
newConvAccess = Set.fromList [InviteAccess, LinkAccess],
newConvAccessRole = Nothing,
@@ -185,6 +196,7 @@ testObject_NewConvUnmanaged_user_6 =
NewConvUnmanaged
( NewConv
{ newConvUsers = [],
+ newConvQualifiedUsers = [],
newConvName = Just "`3",
newConvAccess = Set.fromList [LinkAccess],
newConvAccessRole = Just TeamAccessRole,
@@ -200,6 +212,7 @@ testObject_NewConvUnmanaged_user_7 =
NewConvUnmanaged
( NewConv
{ newConvUsers = [(Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000400000004")))],
+ newConvQualifiedUsers = [],
newConvName = Just "\1038759\b\1057989'",
newConvAccess = Set.fromList [],
newConvAccessRole = Just ActivatedAccessRole,
@@ -221,6 +234,7 @@ testObject_NewConvUnmanaged_user_8 =
NewConvUnmanaged
( NewConv
{ newConvUsers = [(Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000000")))],
+ newConvQualifiedUsers = [],
newConvName = Just "",
newConvAccess = Set.fromList [InviteAccess],
newConvAccessRole = Just ActivatedAccessRole,
@@ -250,6 +264,7 @@ testObject_NewConvUnmanaged_user_9 =
[ (Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000100000001"))),
(Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000100000000")))
],
+ newConvQualifiedUsers = [],
newConvName = Just "L",
newConvAccess = Set.fromList [PrivateAccess, InviteAccess, LinkAccess],
newConvAccessRole = Just ActivatedAccessRole,
@@ -268,6 +283,7 @@ testObject_NewConvUnmanaged_user_10 =
[ (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000001"))),
(Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000000")))
],
+ newConvQualifiedUsers = [],
newConvName = Just "",
newConvAccess = Set.fromList [PrivateAccess, CodeAccess],
newConvAccessRole = Just ActivatedAccessRole,
@@ -289,6 +305,7 @@ testObject_NewConvUnmanaged_user_11 =
[ (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000000"))),
(Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000000")))
],
+ newConvQualifiedUsers = [],
newConvName = Nothing,
newConvAccess = Set.fromList [],
newConvAccessRole = Nothing,
@@ -310,6 +327,7 @@ testObject_NewConvUnmanaged_user_12 =
NewConvUnmanaged
( NewConv
{ newConvUsers = [],
+ newConvQualifiedUsers = [],
newConvName = Just ">+",
newConvAccess = Set.fromList [],
newConvAccessRole = Nothing,
@@ -330,6 +348,7 @@ testObject_NewConvUnmanaged_user_13 =
(Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000001"))),
(Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000100000001")))
],
+ newConvQualifiedUsers = [],
newConvName = Just ".L'",
newConvAccess = Set.fromList [],
newConvAccessRole = Nothing,
@@ -359,6 +378,7 @@ testObject_NewConvUnmanaged_user_14 =
[ (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000001"))),
(Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000000")))
],
+ newConvQualifiedUsers = [],
newConvName = Just "",
newConvAccess = Set.fromList [CodeAccess],
newConvAccessRole = Just NonActivatedAccessRole,
@@ -374,6 +394,7 @@ testObject_NewConvUnmanaged_user_15 =
NewConvUnmanaged
( NewConv
{ newConvUsers = [(Id (fromJust (UUID.fromString "00000002-0000-0003-0000-000300000003")))],
+ newConvQualifiedUsers = [],
newConvName = Just "b\1008988",
newConvAccess = Set.fromList [PrivateAccess, InviteAccess, CodeAccess],
newConvAccessRole = Nothing,
@@ -395,6 +416,7 @@ testObject_NewConvUnmanaged_user_16 =
NewConvUnmanaged
( NewConv
{ newConvUsers = [],
+ newConvQualifiedUsers = [],
newConvName = Just "!",
newConvAccess = Set.fromList [PrivateAccess, CodeAccess],
newConvAccessRole = Just PrivateAccessRole,
@@ -426,6 +448,7 @@ testObject_NewConvUnmanaged_user_17 =
(Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000000000000"))),
(Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000000")))
],
+ newConvQualifiedUsers = [],
newConvName = Just "\ETXB\119338",
newConvAccess = Set.fromList [PrivateAccess, LinkAccess, CodeAccess],
newConvAccessRole = Nothing,
@@ -457,6 +480,7 @@ testObject_NewConvUnmanaged_user_18 =
(Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000000000001"))),
(Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000000")))
],
+ newConvQualifiedUsers = [],
newConvName = Just "sd\ACK",
newConvAccess = Set.fromList [PrivateAccess, CodeAccess],
newConvAccessRole = Nothing,
@@ -473,6 +497,7 @@ testObject_NewConvUnmanaged_user_19 =
NewConvUnmanaged
( NewConv
{ newConvUsers = [(Id (fromJust (UUID.fromString "00000002-0000-0001-0000-000000000002")))],
+ newConvQualifiedUsers = [],
newConvName = Just "Cu\DC1",
newConvAccess = Set.fromList [InviteAccess, LinkAccess],
newConvAccessRole = Nothing,
@@ -489,6 +514,7 @@ testObject_NewConvUnmanaged_user_20 =
NewConvUnmanaged
( NewConv
{ newConvUsers = [],
+ newConvQualifiedUsers = [],
newConvName = Just "\SI\1070774",
newConvAccess = Set.fromList [PrivateAccess],
newConvAccessRole = Nothing,
diff --git a/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/RemoveBotResponse_user.hs b/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/RemoveBotResponse_user.hs
index 72431d7ad0e..ee3cffb3dec 100644
--- a/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/RemoveBotResponse_user.hs
+++ b/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/RemoveBotResponse_user.hs
@@ -20,8 +20,10 @@ module Test.Wire.API.Golden.Generated.RemoveBotResponse_user where
import Data.Code (Key (Key, asciiKey), Value (Value, asciiValue))
import Data.Coerce (coerce)
+import Data.Domain
import Data.Id (ClientId (ClientId, client), Id (Id))
import Data.Misc (HttpsUrl (HttpsUrl), Milliseconds (Ms, ms))
+import Data.Qualified
import Data.Range (unsafeRange)
import Data.Text.Ascii (AsciiChars (validate))
import qualified Data.UUID as UUID (fromString)
@@ -94,7 +96,7 @@ import Wire.API.Event.Conversation
otrRecipient,
otrSender
),
- SimpleMember (SimpleMember, smConvRoleName, smId),
+ SimpleMember (..),
SimpleMembers (SimpleMembers, mMembers),
UserIdList (UserIdList, mUsers),
)
@@ -105,8 +107,8 @@ testObject_RemoveBotResponse_user_1 =
{ rsRemoveBotEvent =
( Event
(MemberLeave)
- ((Id (fromJust (UUID.fromString "00003ab8-0000-0cff-0000-427f000000df"))))
- ((Id (fromJust (UUID.fromString "00004166-0000-1e32-0000-52cb0000428d"))))
+ (Qualified (Id (fromJust (UUID.fromString "00003ab8-0000-0cff-0000-427f000000df"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00004166-0000-1e32-0000-52cb0000428d"))) (Domain "faraway.example.com"))
(read "1864-05-07 01:13:35.741 UTC")
( ( EdMembersLeave
( UserIdList
@@ -155,8 +157,8 @@ testObject_RemoveBotResponse_user_2 =
{ rsRemoveBotEvent =
( Event
(ConvDelete)
- ((Id (fromJust (UUID.fromString "00005a06-0000-10ab-0000-4999000058de"))))
- ((Id (fromJust (UUID.fromString "00004247-0000-0560-0000-07df00005850"))))
+ (Qualified (Id (fromJust (UUID.fromString "00005a06-0000-10ab-0000-4999000058de"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00004247-0000-0560-0000-07df00005850"))) (Domain "faraway.example.com"))
(read "1864-04-23 16:56:18.982 UTC")
(EdConvDelete)
)
@@ -168,22 +170,22 @@ testObject_RemoveBotResponse_user_3 =
{ rsRemoveBotEvent =
( Event
(MemberJoin)
- ((Id (fromJust (UUID.fromString "000031b6-0000-7f2c-0000-22ca000012a0"))))
- ((Id (fromJust (UUID.fromString "00005a35-0000-3751-0000-76fe000044c2"))))
+ (Qualified (Id (fromJust (UUID.fromString "000031b6-0000-7f2c-0000-22ca000012a0"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00005a35-0000-3751-0000-76fe000044c2"))) (Domain "faraway.example.com"))
(read "1864-04-23 02:07:23.62 UTC")
( ( EdMembersJoin
( SimpleMembers
{ mMembers =
[ SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000042-0000-0046-0000-005e0000001f"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000042-0000-0046-0000-005e0000001f"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "3jqe4rv30oxjs05p0vjx_gv"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000073-0000-003c-0000-005800000069"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000073-0000-003c-0000-005800000069"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "gv66owx6jn8"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000003a-0000-0056-0000-000e00000038"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000003a-0000-0056-0000-000e00000038"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -192,14 +194,14 @@ testObject_RemoveBotResponse_user_3 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000007c-0000-0075-0000-006500000036"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000007c-0000-0075-0000-006500000036"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
(parseRoleName "_6hbn84l_4xly84ic0hrz_m4unx_i2_5sfotmu2xjmylyly_qilavdw54n1reep")
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000031-0000-0004-0000-005e00000060"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000031-0000-0004-0000-005e00000060"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -208,12 +210,12 @@ testObject_RemoveBotResponse_user_3 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000007b-0000-0019-0000-002a00000000"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000007b-0000-0019-0000-002a00000000"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "4agujelz62r_o96qfxja1h60hqmsbuowdhmqb1zvrlhtru6b66vl1lu5oc1"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000000c-0000-0069-0000-006600000032"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000000c-0000-0069-0000-006600000032"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -222,11 +224,11 @@ testObject_RemoveBotResponse_user_3 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000001d-0000-0006-0000-00540000005a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000001d-0000-0006-0000-00540000005a"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "ii7eljki45zqe819xzx16tkvbgb85"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000007f-0000-005d-0000-000700000035"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000007f-0000-005d-0000-000700000035"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -235,7 +237,7 @@ testObject_RemoveBotResponse_user_3 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000006b-0000-005e-0000-003d0000007c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000006b-0000-005e-0000-003d0000007c"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -244,7 +246,7 @@ testObject_RemoveBotResponse_user_3 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000005e-0000-000e-0000-00300000005f"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000005e-0000-000e-0000-00300000005f"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -253,7 +255,7 @@ testObject_RemoveBotResponse_user_3 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000060-0000-0000-0000-001e0000003b"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000060-0000-0000-0000-001e0000003b"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -262,24 +264,24 @@ testObject_RemoveBotResponse_user_3 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000030-0000-000e-0000-006f0000001d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000030-0000-000e-0000-006f0000001d"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "rw50gu92raxvq87hqpf7r_xyl"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000003f-0000-005e-0000-003200000062"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000003f-0000-005e-0000-003200000062"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "5bizt8d567yjavituolq2unxfh0qyih7_9dep7cpix5bucbevifs2m0"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000002e-0000-007d-0000-005e0000004d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000002e-0000-007d-0000-005e0000004d"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "1kit803b528tmtyvlkespy"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000007c-0000-0013-0000-007100000049"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000007c-0000-0013-0000-007100000049"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "74l03am2b"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000012-0000-0058-0000-00500000004d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000012-0000-0058-0000-00500000004d"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -288,7 +290,7 @@ testObject_RemoveBotResponse_user_3 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000060-0000-007f-0000-003c0000001d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000060-0000-007f-0000-003c0000001d"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -297,7 +299,7 @@ testObject_RemoveBotResponse_user_3 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000034-0000-005a-0000-003600000063"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000034-0000-005a-0000-003600000063"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -306,7 +308,7 @@ testObject_RemoveBotResponse_user_3 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000006a-0000-007f-0000-006700000068"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000006a-0000-007f-0000-006700000068"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "wf0v8gr2oqqdm"))
}
]
@@ -323,8 +325,8 @@ testObject_RemoveBotResponse_user_4 =
{ rsRemoveBotEvent =
( Event
(ConvAccessUpdate)
- ((Id (fromJust (UUID.fromString "000057d8-0000-4ce9-0000-2a9a00001ced"))))
- ((Id (fromJust (UUID.fromString "00005b30-0000-0805-0000-116700000485"))))
+ (Qualified (Id (fromJust (UUID.fromString "000057d8-0000-4ce9-0000-2a9a00001ced"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00005b30-0000-0805-0000-116700000485"))) (Domain "faraway.example.com"))
(read "1864-05-21 00:12:51.49 UTC")
((EdConvAccessUpdate (ConversationAccessUpdate {cupAccess = [], cupAccessRole = ActivatedAccessRole})))
)
@@ -336,8 +338,8 @@ testObject_RemoveBotResponse_user_5 =
{ rsRemoveBotEvent =
( Event
(ConvAccessUpdate)
- ((Id (fromJust (UUID.fromString "00004615-0000-2e80-0000-552b0000353c"))))
- ((Id (fromJust (UUID.fromString "0000134e-0000-6a75-0000-470a00006537"))))
+ (Qualified (Id (fromJust (UUID.fromString "00004615-0000-2e80-0000-552b0000353c"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "0000134e-0000-6a75-0000-470a00006537"))) (Domain "faraway.example.com"))
(read "1864-04-14 01:56:55.057 UTC")
( ( EdConvAccessUpdate
( ConversationAccessUpdate
@@ -356,14 +358,14 @@ testObject_RemoveBotResponse_user_6 =
{ rsRemoveBotEvent =
( Event
(MemberJoin)
- ((Id (fromJust (UUID.fromString "00002aa8-0000-7a99-0000-660700000bd3"))))
- ((Id (fromJust (UUID.fromString "000036f7-0000-6d15-0000-0ff200006a4c"))))
+ (Qualified (Id (fromJust (UUID.fromString "00002aa8-0000-7a99-0000-660700000bd3"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "000036f7-0000-6d15-0000-0ff200006a4c"))) (Domain "faraway.example.com"))
(read "1864-05-31 11:11:10.792 UTC")
( ( EdMembersJoin
( SimpleMembers
{ mMembers =
[ SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000041-0000-004b-0000-002300000030"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000041-0000-004b-0000-002300000030"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -372,7 +374,7 @@ testObject_RemoveBotResponse_user_6 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000055-0000-0065-0000-005000000065"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000055-0000-0065-0000-005000000065"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -381,7 +383,7 @@ testObject_RemoveBotResponse_user_6 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000000e-0000-0042-0000-00580000000a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000000e-0000-0042-0000-00580000000a"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -390,26 +392,26 @@ testObject_RemoveBotResponse_user_6 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000006b-0000-0080-0000-00360000000c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000006b-0000-0080-0000-00360000000c"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
(parseRoleName "kf14jpkab__n0g0ssfw21_3q52t2op841s0zl8edy11acgb218rr4nmkodozdim")
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000031-0000-0006-0000-003f00000069"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000031-0000-0006-0000-003f00000069"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "06i5vil75hof_mqn8_7cuglrizks"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000006f-0000-0026-0000-006000000045"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000006f-0000-0026-0000-006000000045"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "cux_igluvokgr7z7ikcqcmm9dhskcimfufmsxwb11vfv"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000002a-0000-0080-0000-003e00000014"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000002a-0000-0080-0000-003e00000014"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "es0p"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000001d-0000-006f-0000-003e0000003d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000001d-0000-006f-0000-003e0000003d"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -418,15 +420,15 @@ testObject_RemoveBotResponse_user_6 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000004c-0000-0065-0000-007d00000026"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000004c-0000-0065-0000-007d00000026"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "4pgdip19fs0"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000028-0000-005b-0000-007d00000042"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000028-0000-005b-0000-007d00000042"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "x76ykqupchbjeozez7aqxynobvjd38xuqb"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000011-0000-0031-0000-001e00000055"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000011-0000-0031-0000-001e00000055"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -435,11 +437,11 @@ testObject_RemoveBotResponse_user_6 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000039-0000-003b-0000-002200000013"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000039-0000-003b-0000-002200000013"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "73c37ry1xfsx"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000007c-0000-0065-0000-001b00000046"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000007c-0000-0065-0000-001b00000046"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -448,7 +450,7 @@ testObject_RemoveBotResponse_user_6 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000034-0000-004c-0000-001c0000007c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000034-0000-004c-0000-001c0000007c"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -457,7 +459,7 @@ testObject_RemoveBotResponse_user_6 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000004-0000-0028-0000-002300000045"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000004-0000-0028-0000-002300000045"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -466,7 +468,7 @@ testObject_RemoveBotResponse_user_6 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000000f-0000-007d-0000-00650000006b"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000000f-0000-007d-0000-00650000006b"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -475,18 +477,18 @@ testObject_RemoveBotResponse_user_6 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000006c-0000-0028-0000-004000000067"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000006c-0000-0028-0000-004000000067"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "2130v11uf_bzjod2p35u_vhotitn"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000059-0000-007e-0000-00580000006c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000059-0000-007e-0000-00580000006c"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
(parseRoleName "6idgmk_1d_g5ii1sfpfcrenr8m2afbe2d71llw8xrlzdhxw_g7vn3foj5_abaul9j71_")
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000007f-0000-0057-0000-004c00000005"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000007f-0000-0057-0000-004c00000005"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -495,16 +497,16 @@ testObject_RemoveBotResponse_user_6 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000004a-0000-000b-0000-001000000004"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000004a-0000-000b-0000-001000000004"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "57guddz98hnzetk8xjme1h_gtmczis9jv3xt73rtjgz6jsentre2s7d2"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000000a-0000-0039-0000-005c00000048"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000000a-0000-0039-0000-005c00000048"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "x0qcthwpdmzimnfqh4rd4sf"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000006a-0000-000d-0000-006300000074"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000006a-0000-000d-0000-006300000074"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -513,11 +515,11 @@ testObject_RemoveBotResponse_user_6 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000003-0000-0078-0000-00310000002b"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000003-0000-0078-0000-00310000002b"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "9ble9wkz5sx4fof474zgb"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000006-0000-0042-0000-007e00000013"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000006-0000-0042-0000-007e00000013"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -526,7 +528,7 @@ testObject_RemoveBotResponse_user_6 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000002d-0000-0043-0000-004f00000078"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000002d-0000-0043-0000-004f00000078"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -535,12 +537,12 @@ testObject_RemoveBotResponse_user_6 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000015-0000-0069-0000-005400000018"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000015-0000-0069-0000-005400000018"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "jiyr52auzomq5ui457z209fcszalvj_wy09_zgc05pfp9x304nwxni"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000064-0000-007d-0000-006b00000055"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000064-0000-007d-0000-006b00000055"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -549,11 +551,11 @@ testObject_RemoveBotResponse_user_6 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000006d-0000-007a-0000-007a00000017"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000006d-0000-007a-0000-007a00000017"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "hcfut6_dj"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000005-0000-0065-0000-002600000007"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000005-0000-0065-0000-002600000007"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "q5_32a257neednc3"))
}
]
@@ -570,8 +572,8 @@ testObject_RemoveBotResponse_user_7 =
{ rsRemoveBotEvent =
( Event
(OtrMessageAdd)
- ((Id (fromJust (UUID.fromString "00006a93-0000-005c-0000-361e00000180"))))
- ((Id (fromJust (UUID.fromString "00007bb6-0000-07cc-0000-687c00002703"))))
+ (Qualified (Id (fromJust (UUID.fromString "00006a93-0000-005c-0000-361e00000180"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00007bb6-0000-07cc-0000-687c00002703"))) (Domain "faraway.example.com"))
(read "1864-04-25 18:08:10.735 UTC")
( ( EdOtrMessage
( OtrMessage
@@ -592,8 +594,8 @@ testObject_RemoveBotResponse_user_8 =
{ rsRemoveBotEvent =
( Event
(ConvConnect)
- ((Id (fromJust (UUID.fromString "000022d4-0000-6167-0000-519f0000134c"))))
- ((Id (fromJust (UUID.fromString "0000200d-0000-386f-0000-0de000003b71"))))
+ (Qualified (Id (fromJust (UUID.fromString "000022d4-0000-6167-0000-519f0000134c"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "0000200d-0000-386f-0000-0de000003b71"))) (Domain "faraway.example.com"))
(read "1864-05-29 09:46:28.943 UTC")
( ( EdConnect
( Connect
@@ -614,8 +616,8 @@ testObject_RemoveBotResponse_user_9 =
{ rsRemoveBotEvent =
( Event
(OtrMessageAdd)
- ((Id (fromJust (UUID.fromString "0000324b-0000-23a4-0000-0fbb00006c87"))))
- ((Id (fromJust (UUID.fromString "00006234-0000-7d47-0000-0b95000079f2"))))
+ (Qualified (Id (fromJust (UUID.fromString "0000324b-0000-23a4-0000-0fbb00006c87"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00006234-0000-7d47-0000-0b95000079f2"))) (Domain "faraway.example.com"))
(read "1864-05-18 05:11:02.885 UTC")
( ( EdOtrMessage
( OtrMessage
@@ -636,8 +638,8 @@ testObject_RemoveBotResponse_user_10 =
{ rsRemoveBotEvent =
( Event
(Typing)
- ((Id (fromJust (UUID.fromString "00005788-0000-327b-0000-7ef80000017e"))))
- ((Id (fromJust (UUID.fromString "0000588d-0000-6704-0000-153f00001692"))))
+ (Qualified (Id (fromJust (UUID.fromString "00005788-0000-327b-0000-7ef80000017e"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "0000588d-0000-6704-0000-153f00001692"))) (Domain "faraway.example.com"))
(read "1864-04-11 02:49:27.442 UTC")
((EdTyping (TypingData {tdStatus = StartedTyping})))
)
@@ -649,8 +651,8 @@ testObject_RemoveBotResponse_user_11 =
{ rsRemoveBotEvent =
( Event
(ConvRename)
- ((Id (fromJust (UUID.fromString "00001db4-0000-575c-0000-5b9200002c33"))))
- ((Id (fromJust (UUID.fromString "000009b3-0000-04dc-0000-310100002b5f"))))
+ (Qualified (Id (fromJust (UUID.fromString "00001db4-0000-575c-0000-5b9200002c33"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "000009b3-0000-04dc-0000-310100002b5f"))) (Domain "faraway.example.com"))
(read "1864-05-25 16:08:53.052 UTC")
( ( EdConvRename
( ConversationRename
@@ -668,8 +670,8 @@ testObject_RemoveBotResponse_user_12 =
{ rsRemoveBotEvent =
( Event
(ConvConnect)
- ((Id (fromJust (UUID.fromString "00004c29-0000-0214-0000-1d7300001cdc"))))
- ((Id (fromJust (UUID.fromString "00003ba8-0000-448c-0000-769e00004cdf"))))
+ (Qualified (Id (fromJust (UUID.fromString "00004c29-0000-0214-0000-1d7300001cdc"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00003ba8-0000-448c-0000-769e00004cdf"))) (Domain "faraway.example.com"))
(read "1864-04-23 00:31:51.842 UTC")
( ( EdConnect
( Connect
@@ -690,8 +692,8 @@ testObject_RemoveBotResponse_user_13 =
{ rsRemoveBotEvent =
( Event
(ConvMessageTimerUpdate)
- ((Id (fromJust (UUID.fromString "000062a2-0000-46ad-0000-0f8100005bbe"))))
- ((Id (fromJust (UUID.fromString "000065a2-0000-1aaa-0000-311000003d69"))))
+ (Qualified (Id (fromJust (UUID.fromString "000062a2-0000-46ad-0000-0f8100005bbe"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "000065a2-0000-1aaa-0000-311000003d69"))) (Domain "faraway.example.com"))
(read "1864-05-06 22:47:56.147 UTC")
((EdConvMessageTimerUpdate (ConversationMessageTimerUpdate {cupMessageTimer = Nothing})))
)
@@ -703,8 +705,8 @@ testObject_RemoveBotResponse_user_14 =
{ rsRemoveBotEvent =
( Event
(ConvCodeUpdate)
- ((Id (fromJust (UUID.fromString "0000060f-0000-6d7d-0000-33a800005d07"))))
- ((Id (fromJust (UUID.fromString "00005c4c-0000-226a-0000-04b70000100a"))))
+ (Qualified (Id (fromJust (UUID.fromString "0000060f-0000-6d7d-0000-33a800005d07"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00005c4c-0000-226a-0000-04b70000100a"))) (Domain "faraway.example.com"))
(read "1864-04-21 02:44:02.145 UTC")
( ( EdConvCodeUpdate
( ConversationCode
@@ -726,8 +728,8 @@ testObject_RemoveBotResponse_user_15 =
{ rsRemoveBotEvent =
( Event
(ConvMessageTimerUpdate)
- ((Id (fromJust (UUID.fromString "00006421-0000-0363-0000-192100003398"))))
- ((Id (fromJust (UUID.fromString "000005cd-0000-7897-0000-1fc700002d35"))))
+ (Qualified (Id (fromJust (UUID.fromString "00006421-0000-0363-0000-192100003398"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "000005cd-0000-7897-0000-1fc700002d35"))) (Domain "faraway.example.com"))
(read "1864-04-30 23:29:02.24 UTC")
( ( EdConvMessageTimerUpdate
(ConversationMessageTimerUpdate {cupMessageTimer = Just (Ms {ms = 8977358108702637})})
@@ -742,8 +744,8 @@ testObject_RemoveBotResponse_user_16 =
{ rsRemoveBotEvent =
( Event
(ConvCodeUpdate)
- ((Id (fromJust (UUID.fromString "0000067f-0000-0d9b-0000-039f0000033f"))))
- ((Id (fromJust (UUID.fromString "0000030b-0000-5943-0000-6cd900006eae"))))
+ (Qualified (Id (fromJust (UUID.fromString "0000067f-0000-0d9b-0000-039f0000033f"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "0000030b-0000-5943-0000-6cd900006eae"))) (Domain "faraway.example.com"))
(read "1864-04-27 19:16:49.866 UTC")
( ( EdConvCodeUpdate
( ConversationCode
@@ -782,8 +784,8 @@ testObject_RemoveBotResponse_user_17 =
{ rsRemoveBotEvent =
( Event
(ConvMessageTimerUpdate)
- ((Id (fromJust (UUID.fromString "00005994-0000-5c94-0000-519300002727"))))
- ((Id (fromJust (UUID.fromString "00003ddd-0000-21a2-0000-6a54000023c3"))))
+ (Qualified (Id (fromJust (UUID.fromString "00005994-0000-5c94-0000-519300002727"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00003ddd-0000-21a2-0000-6a54000023c3"))) (Domain "faraway.example.com"))
(read "1864-04-24 18:38:55.053 UTC")
( ( EdConvMessageTimerUpdate
(ConversationMessageTimerUpdate {cupMessageTimer = Just (Ms {ms = 3685837512701220})})
@@ -798,8 +800,8 @@ testObject_RemoveBotResponse_user_18 =
{ rsRemoveBotEvent =
( Event
(ConvConnect)
- ((Id (fromJust (UUID.fromString "000005bf-0000-3fdd-0000-089a0000544e"))))
- ((Id (fromJust (UUID.fromString "00003c0a-0000-3d64-0000-7f74000011e9"))))
+ (Qualified (Id (fromJust (UUID.fromString "000005bf-0000-3fdd-0000-089a0000544e"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00003c0a-0000-3d64-0000-7f74000011e9"))) (Domain "faraway.example.com"))
(read "1864-05-05 05:34:43.386 UTC")
( ( EdConnect
( Connect
@@ -820,8 +822,8 @@ testObject_RemoveBotResponse_user_19 =
{ rsRemoveBotEvent =
( Event
(ConvCodeDelete)
- ((Id (fromJust (UUID.fromString "00000c59-0000-51c7-0000-1b6500001384"))))
- ((Id (fromJust (UUID.fromString "00003046-0000-14df-0000-5a5900005ef2"))))
+ (Qualified (Id (fromJust (UUID.fromString "00000c59-0000-51c7-0000-1b6500001384"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00003046-0000-14df-0000-5a5900005ef2"))) (Domain "faraway.example.com"))
(read "1864-04-19 14:51:39.037 UTC")
(EdConvCodeDelete)
)
@@ -833,8 +835,8 @@ testObject_RemoveBotResponse_user_20 =
{ rsRemoveBotEvent =
( Event
(ConvMessageTimerUpdate)
- ((Id (fromJust (UUID.fromString "00004e98-0000-2ec5-0000-31870000098c"))))
- ((Id (fromJust (UUID.fromString "00006cb0-0000-6547-0000-1fe500000270"))))
+ (Qualified (Id (fromJust (UUID.fromString "00004e98-0000-2ec5-0000-31870000098c"))) (Domain "faraway.example.com"))
+ (Qualified (Id (fromJust (UUID.fromString "00006cb0-0000-6547-0000-1fe500000270"))) (Domain "faraway.example.com"))
(read "1864-05-18 03:54:11.412 UTC")
( ( EdConvMessageTimerUpdate
(ConversationMessageTimerUpdate {cupMessageTimer = Just (Ms {ms = 5776200192005000})})
diff --git a/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/SimpleMember_user.hs b/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/SimpleMember_user.hs
index cf7f5c597d4..01c9bde30eb 100644
--- a/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/SimpleMember_user.hs
+++ b/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/SimpleMember_user.hs
@@ -18,7 +18,9 @@
-- with this program. If not, see .
module Test.Wire.API.Golden.Generated.SimpleMember_user where
+import Data.Domain
import Data.Id (Id (Id))
+import Data.Qualified
import qualified Data.UUID as UUID (fromString)
import Imports (fromJust)
import Wire.API.Conversation.Role (parseRoleName)
@@ -27,11 +29,10 @@ import Wire.API.Event.Conversation (SimpleMember (..))
testObject_SimpleMember_user_1 :: SimpleMember
testObject_SimpleMember_user_1 =
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000003a-0000-0042-0000-007500000037"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000003a-0000-0042-0000-007500000037"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
- ( parseRoleName
- "qbyp4d5whcwd0owjlrr6oktss00oxflwtid8_ram9r3c2nywq7skew91tok1xxivpkbw6n5l8o5ww4zm220_3pozpvt0obaicadhku7f6e93"
+ ( parseRoleName "wire_admin"
)
)
}
@@ -39,11 +40,10 @@ testObject_SimpleMember_user_1 =
testObject_SimpleMember_user_2 :: SimpleMember
testObject_SimpleMember_user_2 =
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000046-0000-0027-0000-003c00000022"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000046-0000-0027-0000-003c00000022"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
- ( parseRoleName
- "ofyvdxbbaf291eyoxm1i16mv2wfa52snql2p9os7shshqpfiw7ivbstjt_nkdqt6_9lz3on3r1nnur8ydc4xae4xf8i2iuu7"
+ ( parseRoleName "wire_member"
)
)
}
@@ -51,28 +51,28 @@ testObject_SimpleMember_user_2 =
testObject_SimpleMember_user_3 :: SimpleMember
testObject_SimpleMember_user_3 =
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000039-0000-0070-0000-005700000019"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000039-0000-0070-0000-005700000019"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "7uzp7961dyf_666xqxwvq6uro"))
}
testObject_SimpleMember_user_4 :: SimpleMember
testObject_SimpleMember_user_4 =
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000007c-0000-0075-0000-005b00000049"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000007c-0000-0075-0000-005b00000049"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "4vr9oed4nvhs625ri_cz1cv5kodntk3edmkpu"))
}
testObject_SimpleMember_user_5 :: SimpleMember
testObject_SimpleMember_user_5 =
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000004c-0000-004e-0000-002400000009"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000004c-0000-004e-0000-002400000009"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "wst92x"))
}
testObject_SimpleMember_user_6 :: SimpleMember
testObject_SimpleMember_user_6 =
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000052-0000-0053-0000-000400000000"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000052-0000-0053-0000-000400000000"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "nkyx6ypx0p0b_fvx6mt6w5w6n2qpivv9svj2myn5n86isy7n2e07m92t7ostflj4lq1py50bqzdi4smzd"))
}
@@ -80,14 +80,14 @@ testObject_SimpleMember_user_6 =
testObject_SimpleMember_user_7 :: SimpleMember
testObject_SimpleMember_user_7 =
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000003d-0000-006f-0000-00480000006e"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000003d-0000-006f-0000-00480000006e"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "d8027w_w7pr9fj"))
}
testObject_SimpleMember_user_8 :: SimpleMember
testObject_SimpleMember_user_8 =
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000004c-0000-006c-0000-000800000044"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000004c-0000-006c-0000-000800000044"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -99,21 +99,21 @@ testObject_SimpleMember_user_8 =
testObject_SimpleMember_user_9 :: SimpleMember
testObject_SimpleMember_user_9 =
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000000a-0000-007a-0000-003f0000001b"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000000a-0000-007a-0000-003f0000001b"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "sr5pfubd0_cpdp"))
}
testObject_SimpleMember_user_10 :: SimpleMember
testObject_SimpleMember_user_10 =
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000001d-0000-000f-0000-002900000072"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000001d-0000-000f-0000-002900000072"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "paru"))
}
testObject_SimpleMember_user_11 :: SimpleMember
testObject_SimpleMember_user_11 =
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000007d-0000-0076-0000-001e00000019"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000007d-0000-0076-0000-001e00000019"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
(parseRoleName "e0u15rrzql4y8jymut86vv84l4tjzpfti0_b1w44gy13j3d0dq1y22ws75tkgd4n_9tju4pq34_ddk_g9qpypwu4z3b5")
@@ -123,7 +123,7 @@ testObject_SimpleMember_user_11 =
testObject_SimpleMember_user_12 :: SimpleMember
testObject_SimpleMember_user_12 =
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000003c-0000-0001-0000-004a00000014"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000003c-0000-0001-0000-004a00000014"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
(parseRoleName "telj17ej33ilgtqvqajp0ofng9qm6v9b1n32n_l6_vw_xxtk4o7n6r50ea3w1xgzh3eapah1jytfpz0f65utf9xqc4pv")
@@ -133,14 +133,14 @@ testObject_SimpleMember_user_12 =
testObject_SimpleMember_user_13 :: SimpleMember
testObject_SimpleMember_user_13 =
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000052-0000-002c-0000-004500000067"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000052-0000-002c-0000-004500000067"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "bfamau83n6sskso4rod8fz1tb4tf1zfz8mfd1v0ae1sx17po1"))
}
testObject_SimpleMember_user_14 :: SimpleMember
testObject_SimpleMember_user_14 =
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000018-0000-006d-0000-000600000017"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000018-0000-006d-0000-000600000017"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -152,28 +152,28 @@ testObject_SimpleMember_user_14 =
testObject_SimpleMember_user_15 :: SimpleMember
testObject_SimpleMember_user_15 =
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000006e-0000-0037-0000-00610000007e"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000006e-0000-0037-0000-00610000007e"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "rt25zies0df"))
}
testObject_SimpleMember_user_16 :: SimpleMember
testObject_SimpleMember_user_16 =
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000042-0000-006a-0000-000800000052"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000042-0000-006a-0000-000800000052"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "pknq1f2x"))
}
testObject_SimpleMember_user_17 :: SimpleMember
testObject_SimpleMember_user_17 =
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000020-0000-0000-0000-00500000005c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000020-0000-0000-0000-00500000005c"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "w1bcl23oz4ax6dg14h3y8nxqb77sx9ajonsvx7qd"))
}
testObject_SimpleMember_user_18 :: SimpleMember
testObject_SimpleMember_user_18 =
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000049-0000-000c-0000-004d00000043"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000049-0000-000c-0000-004d00000043"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -185,7 +185,7 @@ testObject_SimpleMember_user_18 =
testObject_SimpleMember_user_19 :: SimpleMember
testObject_SimpleMember_user_19 =
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000031-0000-003d-0000-003800000024"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000031-0000-003d-0000-003800000024"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -197,7 +197,7 @@ testObject_SimpleMember_user_19 =
testObject_SimpleMember_user_20 :: SimpleMember
testObject_SimpleMember_user_20 =
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000074-0000-0010-0000-001600000078"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000074-0000-0010-0000-001600000078"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
diff --git a/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/SimpleMembers_user.hs b/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/SimpleMembers_user.hs
index 5f6b74ecd3c..1ec3f17abfe 100644
--- a/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/SimpleMembers_user.hs
+++ b/libs/wire-api/test/unit/Test/Wire/API/Golden/Generated/SimpleMembers_user.hs
@@ -18,18 +18,20 @@
-- with this program. If not, see .
module Test.Wire.API.Golden.Generated.SimpleMembers_user where
+import Data.Domain
import Data.Id (Id (Id))
+import Data.Qualified
import qualified Data.UUID as UUID (fromString)
import Imports (fromJust)
import Wire.API.Conversation.Role (parseRoleName)
-import Wire.API.Event.Conversation (SimpleMember (SimpleMember, smConvRoleName, smId), SimpleMembers (..))
+import Wire.API.Event.Conversation (SimpleMember (..), SimpleMembers (..))
testObject_SimpleMembers_user_1 :: SimpleMembers
testObject_SimpleMembers_user_1 =
SimpleMembers
{ mMembers =
[ SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000048-0000-0011-0000-002300000050"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000048-0000-0011-0000-002300000050"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -38,7 +40,7 @@ testObject_SimpleMembers_user_1 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000030-0000-003d-0000-00620000002a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000030-0000-003d-0000-00620000002a"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -47,29 +49,29 @@ testObject_SimpleMembers_user_1 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000001f-0000-002b-0000-005500000013"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000001f-0000-002b-0000-005500000013"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
(parseRoleName "n0_wuagfmm6ltcjr0n2ib7l2mdg3i0zwtzmb6aribmg2107sirkgo17wjt9d2h66nj3lerw_blivsh6by09a")
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000062-0000-0017-0000-005a00000019"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000062-0000-0017-0000-005a00000019"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "4q8i3kin7cuo_xpa"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000065-0000-002e-0000-00730000002f"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000065-0000-002e-0000-00730000002f"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "e52wem88ym9kubyydku"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000048-0000-0068-0000-002300000042"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000048-0000-0068-0000-002300000042"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
(parseRoleName "yson37f_88qcp5chnwpjnwin427qoptb7bmlx5u2454vw95vvt241red8i1pkavlha4l9vx3cr1ajgklb")
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000021-0000-0057-0000-005d00000055"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000021-0000-0057-0000-005d00000055"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -78,19 +80,19 @@ testObject_SimpleMembers_user_1 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000080-0000-0071-0000-003400000066"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000080-0000-0071-0000-003400000066"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "0otsqpgjh2ctmp22nsof114767_vow59km_e"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000036-0000-0050-0000-002a0000005b"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000036-0000-0050-0000-002a0000005b"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "406ogeb8o68w"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000044-0000-0025-0000-002e00000026"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000044-0000-0025-0000-002e00000026"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "vgwq1mfqei0embh6msg2q0ucobreh9jl61ql0fge66e9xe"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000059-0000-0000-0000-002e0000002d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000059-0000-0000-0000-002e0000002d"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -99,15 +101,15 @@ testObject_SimpleMembers_user_1 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000014-0000-0003-0000-002600000025"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000014-0000-0003-0000-002600000025"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "9pryu0zv3nw_xtb3xr1naukqs0e"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000002c-0000-0006-0000-007300000061"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000002c-0000-0006-0000-007300000061"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "iy"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000003f-0000-002c-0000-00670000002f"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000003f-0000-002c-0000-00670000002f"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -116,15 +118,15 @@ testObject_SimpleMembers_user_1 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000006c-0000-0072-0000-00400000002d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000006c-0000-0072-0000-00400000002d"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "5v6_cttr3ctgrijw4h1_gsyi41f4t3dgyh64dhcgeoxvao1h68"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000064-0000-0070-0000-000800000049"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000064-0000-0070-0000-000800000049"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "x0"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000006a-0000-002e-0000-006000000070"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000006a-0000-002e-0000-006000000070"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -133,7 +135,7 @@ testObject_SimpleMembers_user_1 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000059-0000-005a-0000-002300000061"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000059-0000-005a-0000-002300000061"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "vb_ng523gxc0ci13cmxscmusff8uw12hvbsvfsa"))
}
]
@@ -144,7 +146,7 @@ testObject_SimpleMembers_user_2 =
SimpleMembers
{ mMembers =
[ SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000004c-0000-0022-0000-00720000007c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000004c-0000-0022-0000-00720000007c"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -153,15 +155,15 @@ testObject_SimpleMembers_user_2 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000002c-0000-0079-0000-004b0000002a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000002c-0000-0079-0000-004b0000002a"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "jhrez7dufl3ne050doxot1f7mhup7a0rr59472xmcvukln0cw"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000006d-0000-0033-0000-006d00000070"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000006d-0000-0033-0000-006d00000070"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "t9e5nbirc5uv1n4jda1bo8mwc72si1wi0_hngmo0sw"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000018-0000-0022-0000-001b00000048"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000018-0000-0022-0000-001b00000048"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -170,14 +172,14 @@ testObject_SimpleMembers_user_2 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000000-0000-004e-0000-002f00000038"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000000-0000-004e-0000-002f00000038"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
(parseRoleName "4o0ctcw73niokwjhjo8_65khxlxx_1o9ktctoq5kdmm39640gc2f3uc3nq99bq_93sgnhvd04wx3pgw1n1l")
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000003e-0000-005f-0000-004b00000038"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000003e-0000-005f-0000-004b00000038"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -186,11 +188,11 @@ testObject_SimpleMembers_user_2 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000008-0000-0030-0000-004000000062"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000008-0000-0030-0000-004000000062"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "g1lrkbr7ouvsrch981kwrz1k8un"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000071-0000-0074-0000-007500000032"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000071-0000-0074-0000-007500000032"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -199,15 +201,15 @@ testObject_SimpleMembers_user_2 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000025-0000-003f-0000-000c0000000a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000025-0000-003f-0000-000c0000000a"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "yqpt1iljztlmcsh2u3gt5s_gg1t7x81iwpp8ui501"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000006f-0000-007f-0000-007300000013"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000006f-0000-007f-0000-007300000013"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "vq6envh0bnegl9x1t"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000001-0000-006b-0000-00200000006a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000001-0000-006b-0000-00200000006a"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -216,7 +218,7 @@ testObject_SimpleMembers_user_2 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000000-0000-0009-0000-003f00000060"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000000-0000-0009-0000-003f00000060"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -225,18 +227,18 @@ testObject_SimpleMembers_user_2 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000071-0000-0077-0000-004600000064"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000071-0000-0077-0000-004600000064"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
(parseRoleName "191um99jwj93l_cv5zdb6op2a5j3tkismgxlv0jzf90zbw4hi9i611nilzp2i3dq16fj1naa0mdqou9")
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000005d-0000-0060-0000-000500000063"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000005d-0000-0060-0000-000500000063"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "b0oxc3cm4deaiuhqlip8cerktwoqbdp_z56h8jeyfc5any"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000074-0000-0013-0000-005700000074"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000074-0000-0013-0000-005700000074"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -245,15 +247,15 @@ testObject_SimpleMembers_user_2 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000063-0000-0078-0000-001000000046"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000063-0000-0078-0000-001000000046"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "5tsp_2"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000006c-0000-0062-0000-00440000002e"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000006c-0000-0062-0000-00440000002e"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "xn825hf3etf479oc7vjahb"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000070-0000-0030-0000-000e00000075"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000070-0000-0030-0000-000e00000075"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -262,7 +264,7 @@ testObject_SimpleMembers_user_2 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000007b-0000-0058-0000-003000000071"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000007b-0000-0058-0000-003000000071"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -271,7 +273,7 @@ testObject_SimpleMembers_user_2 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000007f-0000-0070-0000-007700000034"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000007f-0000-0070-0000-007700000034"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -280,11 +282,11 @@ testObject_SimpleMembers_user_2 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000042-0000-001c-0000-005900000054"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000042-0000-001c-0000-005900000054"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "2ogp8swsxisn1w2bohi8rcvl_1rtx0m34lr6x8sqkt9"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000000c-0000-0072-0000-001100000014"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000000c-0000-0072-0000-001100000014"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -293,16 +295,16 @@ testObject_SimpleMembers_user_2 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000073-0000-0042-0000-005a0000001c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000073-0000-0042-0000-005a0000001c"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "pma4ikgggpi_q0rvtdvjoff8fztnbolrl6oty_yvxm3qksaeg0l9bh8byrde5mto2f2a1rmn"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000029-0000-0042-0000-001a00000071"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000029-0000-0042-0000-001a00000071"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "njm9ltp6fr3yk2ke5skszy0xspo7blk"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000057-0000-0025-0000-002400000040"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000057-0000-0025-0000-002400000040"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -311,19 +313,19 @@ testObject_SimpleMembers_user_2 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000009-0000-0034-0000-006c00000009"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000009-0000-0034-0000-006c00000009"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "x61tv07e4higron1y"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000071-0000-0069-0000-00310000002b"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000071-0000-0069-0000-00310000002b"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "y7ttveh1qbqrww6el6rpjbz13kla3873tu2t"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000007a-0000-0016-0000-005a0000001f"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000007a-0000-0016-0000-005a0000001f"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "y2791q0e6ve5oof9ep"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000003b-0000-0075-0000-000500000008"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000003b-0000-0075-0000-000500000008"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "db051rwfps8foxf3bqqk8"))
}
]
@@ -334,7 +336,7 @@ testObject_SimpleMembers_user_3 =
SimpleMembers
{ mMembers =
[ SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000003-0000-001c-0000-004200000050"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000003-0000-001c-0000-004200000050"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -343,7 +345,7 @@ testObject_SimpleMembers_user_3 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000025-0000-000b-0000-005200000004"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000025-0000-000b-0000-005200000004"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -352,19 +354,19 @@ testObject_SimpleMembers_user_3 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000022-0000-0022-0000-00560000005b"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000022-0000-0022-0000-00560000005b"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "so7mgd7pd8f5bl2hc28161aqhqht1ii3ysfmj"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000065-0000-001d-0000-00530000001a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000065-0000-001d-0000-00530000001a"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "dcd0mqj2i4w35js"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000021-0000-003e-0000-00140000005d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000021-0000-003e-0000-00140000005d"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "0n5d40stfqajajw2_q70xrtjct7oursrdqbr"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000016-0000-001d-0000-002400000078"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000016-0000-001d-0000-002400000078"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "6xsb6xh6qstehp328c8pbh4z4nnkjqtv8"))
}
]
@@ -375,7 +377,7 @@ testObject_SimpleMembers_user_4 =
SimpleMembers
{ mMembers =
[ SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000007f-0000-007b-0000-002c00000013"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000007f-0000-007b-0000-002c00000013"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -384,7 +386,7 @@ testObject_SimpleMembers_user_4 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000079-0000-0021-0000-007000000061"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000079-0000-0021-0000-007000000061"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "2dpw9m0sf8_"))
}
]
@@ -395,7 +397,7 @@ testObject_SimpleMembers_user_5 =
SimpleMembers
{ mMembers =
[ SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000053-0000-0076-0000-00100000004e"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000053-0000-0076-0000-00100000004e"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -404,7 +406,7 @@ testObject_SimpleMembers_user_5 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000001-0000-0075-0000-006500000014"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000001-0000-0075-0000-006500000014"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -413,11 +415,11 @@ testObject_SimpleMembers_user_5 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000000a-0000-0068-0000-00100000000c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000000a-0000-0068-0000-00100000000c"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "48idob6a3qg0k4cyr4x5b3gvqafdeogqtnh_69ov347xwn54j"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000070-0000-000f-0000-002400000032"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000070-0000-000f-0000-002400000032"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -426,20 +428,20 @@ testObject_SimpleMembers_user_5 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000002c-0000-0000-0000-007500000037"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000002c-0000-0000-0000-007500000037"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "rc779wbhz5nabuxyzdrv6n9oiq06olf0"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000005c-0000-0009-0000-001700000033"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000005c-0000-0009-0000-001700000033"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "1yeunp1yvablh18tnsoaa8xnggbpbviyabkfh6abd9vw0mrcy3x3gu3m2jvnjhroe44y55c9"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000011-0000-004f-0000-007400000055"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000011-0000-004f-0000-007400000055"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "opmdgq5u1h4ersm_ydrpyydfv2cdlsj7uwkqaz892ajcmuwi28c197"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000005b-0000-0076-0000-007f00000034"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000005b-0000-0076-0000-007f00000034"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -448,7 +450,7 @@ testObject_SimpleMembers_user_5 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000044-0000-0065-0000-004f00000039"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000044-0000-0065-0000-004f00000039"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -457,11 +459,11 @@ testObject_SimpleMembers_user_5 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000000f-0000-0059-0000-002100000019"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000000f-0000-0059-0000-002100000019"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "d87qu8t82u8q8isnqw_0_55hpuuwnjfvra2ieaogqqdn6iwv8b"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000076-0000-0044-0000-00110000002b"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000076-0000-0044-0000-00110000002b"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -470,19 +472,19 @@ testObject_SimpleMembers_user_5 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000007c-0000-0016-0000-001200000027"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000007c-0000-0016-0000-001200000027"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "g43z_vqs1w3nubu3uuvq7eycshex3ug1mz7h50o8k4mu9q1tm_z"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000003b-0000-0005-0000-002a00000003"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000003b-0000-0005-0000-002a00000003"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "toatgflm9kmzec4xpbt596ti99yjqh96g4tp"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000002c-0000-005d-0000-005e0000005e"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000002c-0000-005d-0000-005e0000005e"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "xgpcwa43"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000004b-0000-004c-0000-001300000001"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000004b-0000-004c-0000-001300000001"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -498,7 +500,7 @@ testObject_SimpleMembers_user_6 =
SimpleMembers
{ mMembers =
[ SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000079-0000-002a-0000-004d0000001d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000079-0000-002a-0000-004d0000001d"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -507,11 +509,11 @@ testObject_SimpleMembers_user_6 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000001f-0000-0042-0000-00530000002e"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000001f-0000-0042-0000-00530000002e"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "99uoa6zruc85ailr9e9lu5537qrixoaq1ufioh4uepukbae"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000049-0000-0022-0000-003f00000045"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000049-0000-0022-0000-003f00000045"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -520,16 +522,16 @@ testObject_SimpleMembers_user_6 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000011-0000-002d-0000-004e0000003c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000011-0000-002d-0000-004e0000003c"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "xxj8_x7sgu_7j6fjxshorrc5pn_nwrx1_kft7yl8w2383w5eti15qiu0xzmaqa3913938f_"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000039-0000-0032-0000-006b0000007c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000039-0000-0032-0000-006b0000007c"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "jr8qjzzzoqzxh67eh8qsqp531s0a7ji3a7vtji48tcf56g_fnjn3nax"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000071-0000-0033-0000-006e00000063"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000071-0000-0033-0000-006e00000063"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -538,7 +540,7 @@ testObject_SimpleMembers_user_6 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000005e-0000-0062-0000-00220000003f"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000005e-0000-0062-0000-00220000003f"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -547,11 +549,11 @@ testObject_SimpleMembers_user_6 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000036-0000-003e-0000-00330000002b"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000036-0000-003e-0000-00330000002b"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "v3m955rl1j5st0fk3t8l0ist2rq5lefq_wt2uwd_h6b1obaa3mt115ph0ukx"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000026-0000-0070-0000-006f00000029"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000026-0000-0070-0000-006f00000029"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -560,23 +562,23 @@ testObject_SimpleMembers_user_6 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000059-0000-0053-0000-005e00000079"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000059-0000-0053-0000-005e00000079"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "l_1m2"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000001a-0000-0069-0000-002e00000072"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000001a-0000-0069-0000-002e00000072"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "e65xwo7h8khwqvfvmvj02jj2jkz3wa8bei_"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000006c-0000-000a-0000-00470000007f"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000006c-0000-000a-0000-00470000007f"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "ly60ylpqtqx3vqe00gw"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000001c-0000-0019-0000-007300000070"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000001c-0000-0019-0000-007300000070"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "0obmr5yj455s75alew"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000017-0000-0046-0000-003e0000005c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000017-0000-0046-0000-003e0000005c"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -585,7 +587,7 @@ testObject_SimpleMembers_user_6 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000000f-0000-005f-0000-006200000042"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000000f-0000-005f-0000-006200000042"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -594,11 +596,11 @@ testObject_SimpleMembers_user_6 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000000d-0000-0027-0000-002300000072"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000000d-0000-0027-0000-002300000072"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "_b64sbeqrp8ou_09bincmxn3"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000005a-0000-004f-0000-00700000006b"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000005a-0000-004f-0000-00700000006b"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -607,27 +609,27 @@ testObject_SimpleMembers_user_6 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000001-0000-0015-0000-000a0000000c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000001-0000-0015-0000-000a0000000c"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "jstp31co2rpas6er_oyazeow51_1aho0uqvdvu6uqv2"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000005f-0000-003c-0000-007600000061"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000005f-0000-003c-0000-007600000061"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "0gk_nlyr50ot8v0s39c1wgjry6z3e78hcjtv2wmcb397ojix5l8p47tlmsvw"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000076-0000-002c-0000-002d00000016"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000076-0000-002c-0000-002d00000016"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "cm"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000074-0000-001e-0000-007400000071"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000074-0000-001e-0000-007400000071"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "e9kre1i15j22d"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000051-0000-007c-0000-007b00000025"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000051-0000-007c-0000-007b00000025"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "nb8blnutjrsntylz671x"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000046-0000-0031-0000-004400000018"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000046-0000-0031-0000-004400000018"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -643,15 +645,15 @@ testObject_SimpleMembers_user_7 =
SimpleMembers
{ mMembers =
[ SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000054-0000-0012-0000-005900000011"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000054-0000-0012-0000-005900000011"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "eldahjnjgyux49p6u4qxz9a0q7e"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000000e-0000-004f-0000-003b0000004e"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000000e-0000-004f-0000-003b0000004e"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "yw31a4ikpn_zfb5fd0vee3e1536ak74rqp_qtok7xrhsn5pa"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000062-0000-0055-0000-00600000003d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000062-0000-0055-0000-00600000003d"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -660,16 +662,16 @@ testObject_SimpleMembers_user_7 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000049-0000-0058-0000-005000000000"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000049-0000-0058-0000-005000000000"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "810s8rqja"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000005a-0000-0020-0000-005400000016"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000005a-0000-0020-0000-005400000016"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "yb41udiftgjzo36lbvwtw9xj5qlohvljde90frfx0r26jzgpq08xeo4xw2tepnvx"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000018-0000-002f-0000-006b00000044"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000018-0000-002f-0000-006b00000044"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -678,16 +680,16 @@ testObject_SimpleMembers_user_7 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000007a-0000-0058-0000-001200000069"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000007a-0000-0058-0000-001200000069"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "zze7ew9qk8gurfh"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000006f-0000-0018-0000-006f00000076"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000006f-0000-0018-0000-006f00000076"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "n_roifhghi_l_9b_75beixjh703zyg806b1hin3fui2nj3nj040_7r3ijtyfox4o3o"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000002d-0000-0074-0000-003f00000059"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000002d-0000-0074-0000-003f00000059"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -696,11 +698,11 @@ testObject_SimpleMembers_user_7 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000057-0000-0078-0000-005700000001"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000057-0000-0078-0000-005700000001"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "kue8gimoaxn3wdbzwb2l9ygk0"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000078-0000-0080-0000-002b0000005c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000078-0000-0080-0000-002b0000005c"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -709,7 +711,7 @@ testObject_SimpleMembers_user_7 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000004-0000-007f-0000-007c00000022"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000004-0000-007f-0000-007c00000022"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -718,20 +720,20 @@ testObject_SimpleMembers_user_7 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000006f-0000-0011-0000-004100000002"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000006f-0000-0011-0000-004100000002"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "hdirky_2tz3se2ehu7by2csj7a_jy7qyo1oghueqc_4h118v79xz49olrwkojns"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000042-0000-002f-0000-00030000007d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000042-0000-002f-0000-00030000007d"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "9122iz2g941gnqs08mcaqa33l58irkmohj5r"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000027-0000-0014-0000-00250000003b"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000027-0000-0014-0000-00250000003b"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "0lyosg4pvc5q1dazb5z1v59plf2nqgs"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000000e-0000-0055-0000-002200000000"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000000e-0000-0055-0000-002200000000"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -740,7 +742,7 @@ testObject_SimpleMembers_user_7 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000006a-0000-0009-0000-003d0000007f"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000006a-0000-0009-0000-003d0000007f"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -749,7 +751,7 @@ testObject_SimpleMembers_user_7 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000018-0000-000d-0000-006d00000072"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000018-0000-000d-0000-006d00000072"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -758,11 +760,11 @@ testObject_SimpleMembers_user_7 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000031-0000-0023-0000-005f0000004a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000031-0000-0023-0000-005f0000004a"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "hcy75iscpnouf9aqpon3edkh4uln4gma0niecrde5"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000063-0000-0061-0000-006900000077"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000063-0000-0061-0000-006900000077"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -771,12 +773,12 @@ testObject_SimpleMembers_user_7 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000002e-0000-0003-0000-00790000001d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000002e-0000-0003-0000-00790000001d"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "y0u0avpt3orbo6xcee13613ik0sb8xcz308vkb5u33q9np2ws_pvhakw3gjbtihe3"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000052-0000-0054-0000-003e0000005b"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000052-0000-0054-0000-003e0000005b"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -785,20 +787,20 @@ testObject_SimpleMembers_user_7 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000062-0000-0049-0000-005000000022"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000062-0000-0049-0000-005000000022"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "3n2q64e9ea8hxbcwm9n4mlyy330f1zoiaq_ao1d_t90kr4sahr365ji7svmbr6k58bx7o0bjeqij"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000007-0000-0069-0000-000f00000032"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000007-0000-0069-0000-000f00000032"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "x7shwqzfrj3qnlvus111ufwgzstnmmob_xhzern6niel5pahgi1_"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000003-0000-0057-0000-00680000003c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000003-0000-0057-0000-00680000003c"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "1tv5og06r1a2al4kc"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000047-0000-006c-0000-003200000031"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000047-0000-006c-0000-003200000031"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -807,11 +809,11 @@ testObject_SimpleMembers_user_7 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000074-0000-0059-0000-006800000037"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000074-0000-0059-0000-006800000037"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "uvsqtx_7v0_odhu95uke30sh454iruq9"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000050-0000-0053-0000-006f0000005d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000050-0000-0053-0000-006f0000005d"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -820,11 +822,11 @@ testObject_SimpleMembers_user_7 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000037-0000-004d-0000-003e00000004"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000037-0000-004d-0000-003e00000004"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "ccqqr3w57f9exl7xuhqnr305fqteeziw7hr374is9pkpjtt_z"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000039-0000-0024-0000-003500000002"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000039-0000-0024-0000-003500000002"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -840,18 +842,18 @@ testObject_SimpleMembers_user_8 =
SimpleMembers
{ mMembers =
[ SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000067-0000-003f-0000-003300000052"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000067-0000-003f-0000-003300000052"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "bmgvnfheg7304j1af2ha8kzlrdsd94sla01p8e32cfuchc4n4d4j_1"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000066-0000-001c-0000-005900000006"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000066-0000-001c-0000-005900000006"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
(parseRoleName "3z44kbvkfmhwt3cxvztk91xwigzfsqgmwx43rsi2ew7_663q5kd04afdhwes23ea8_7nn4j6hol2k1o")
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000013-0000-0072-0000-005000000019"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000013-0000-0072-0000-005000000019"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -860,20 +862,20 @@ testObject_SimpleMembers_user_8 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000006d-0000-003f-0000-007f00000025"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000006d-0000-003f-0000-007f00000025"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "qann8z5wp43fncbnzkxuqeskdrnxclmj1qoiri6zb4ro8jzbsewewgi27xi6pnc"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000019-0000-003a-0000-006500000036"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000019-0000-003a-0000-006500000036"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "552en9ubk7gjrv"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000005d-0000-0008-0000-006000000011"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000005d-0000-0008-0000-006000000011"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "4fq8ylocoheanwuq9kg6amnrks"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000007b-0000-0072-0000-00690000000e"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000007b-0000-0072-0000-00690000000e"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -882,15 +884,15 @@ testObject_SimpleMembers_user_8 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000032-0000-0039-0000-007700000022"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000032-0000-0039-0000-007700000022"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "tq"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000001a-0000-003b-0000-001e00000080"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000001a-0000-003b-0000-001e00000080"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "as91oohpdy"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000056-0000-005b-0000-007c00000078"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000056-0000-005b-0000-007c00000078"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "0jr8eycubw7cut6ukuegnxp5b2obst6ry8y76fe2qjro3xpp3bjvxg4c707rs1jlf"))
}
@@ -902,11 +904,11 @@ testObject_SimpleMembers_user_9 =
SimpleMembers
{ mMembers =
[ SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000001a-0000-0051-0000-001100000064"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000001a-0000-0051-0000-001100000064"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "85wkc4m6uzi3t_s5sb488cxhjl7i_av_erwfdtgya58oc"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000053-0000-007f-0000-00140000007a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000053-0000-007f-0000-00140000007a"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -915,12 +917,12 @@ testObject_SimpleMembers_user_9 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000001b-0000-0042-0000-003800000032"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000001b-0000-0042-0000-003800000032"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "pc12u7vhdqloizph96i1elxofyps02qanrr2z6_kdvl3zakyappxu7nksvj6oe6yz_ygrgii0v"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000032-0000-0032-0000-002000000005"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000032-0000-0032-0000-002000000005"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -929,12 +931,12 @@ testObject_SimpleMembers_user_9 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000001-0000-0021-0000-007000000010"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000001-0000-0021-0000-007000000010"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "jcr0d3sv5pm89mkbhinm7aw5njyj0oft6vh8ste7xfn6feqkmx176x93ie9lc58kcik7"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000001b-0000-004b-0000-00480000000a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000001b-0000-004b-0000-00480000000a"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -943,7 +945,7 @@ testObject_SimpleMembers_user_9 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000012-0000-0000-0000-00690000005d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000012-0000-0000-0000-00690000005d"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -952,12 +954,12 @@ testObject_SimpleMembers_user_9 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000024-0000-002c-0000-000a0000002b"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000024-0000-002c-0000-000a0000002b"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "40d1mp1rlpq_toli_xsrzzp6azj7abwn9kwyyexu8mzqanezqlkwgzs_maqszagustta7197hluh"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000000e-0000-002f-0000-006c0000007e"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000000e-0000-002f-0000-006c0000007e"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -966,15 +968,15 @@ testObject_SimpleMembers_user_9 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000001d-0000-000c-0000-001200000072"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000001d-0000-000c-0000-001200000072"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "8qz3xbrnjl34e24fvc96wl34jw"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000065-0000-0005-0000-00160000007a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000065-0000-0005-0000-00160000007a"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "3mcna2fo1fuhmz50gevjyc5iacna3hon9fylu4o9u48"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000002e-0000-0028-0000-00240000001f"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000002e-0000-0028-0000-00240000001f"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -983,12 +985,12 @@ testObject_SimpleMembers_user_9 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000061-0000-0035-0000-005100000068"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000061-0000-0035-0000-005100000068"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "cxx03t4219b0e3b7u5lwxb4ua_3qif069vharpluygxmxq5vd1hcx4_3yjmtgw99yz"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000003-0000-0034-0000-00520000005a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000003-0000-0034-0000-00520000005a"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -997,11 +999,11 @@ testObject_SimpleMembers_user_9 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000007-0000-0049-0000-004c00000028"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000007-0000-0049-0000-004c00000028"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "op23mrkoau967yyy74znf7smfsr1j46m"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000006c-0000-0074-0000-004100000005"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000006c-0000-0074-0000-004100000005"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1010,7 +1012,7 @@ testObject_SimpleMembers_user_9 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000000-0000-004d-0000-002a00000020"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000000-0000-004d-0000-002a00000020"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1019,11 +1021,11 @@ testObject_SimpleMembers_user_9 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000058-0000-005d-0000-00100000000a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000058-0000-005d-0000-00100000000a"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "uw5x_u9rn2zu0nc6f7eb_v40"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000045-0000-0044-0000-00070000005c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000045-0000-0044-0000-00070000005c"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1039,7 +1041,7 @@ testObject_SimpleMembers_user_10 =
SimpleMembers
{ mMembers =
[ SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000047-0000-0044-0000-005c0000003c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000047-0000-0044-0000-005c0000003c"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1048,7 +1050,7 @@ testObject_SimpleMembers_user_10 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000068-0000-006a-0000-002c00000071"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000068-0000-006a-0000-002c00000071"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1057,7 +1059,7 @@ testObject_SimpleMembers_user_10 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000040-0000-0068-0000-001300000019"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000040-0000-0068-0000-001300000019"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1073,12 +1075,12 @@ testObject_SimpleMembers_user_11 =
SimpleMembers
{ mMembers =
[ SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000069-0000-0074-0000-006800000058"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000069-0000-0074-0000-006800000058"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "yprw788nm_1n_l3i6g1xn1xjokilmavqko9otxa26hobs7e7s1fgruka4iom01i00aoyui37so"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000001c-0000-007e-0000-003300000074"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000001c-0000-007e-0000-003300000074"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1087,7 +1089,7 @@ testObject_SimpleMembers_user_11 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000025-0000-0064-0000-007000000046"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000025-0000-0064-0000-007000000046"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1096,7 +1098,7 @@ testObject_SimpleMembers_user_11 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000037-0000-000a-0000-00140000001a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000037-0000-000a-0000-00140000001a"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1105,12 +1107,12 @@ testObject_SimpleMembers_user_11 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000004b-0000-004b-0000-005000000005"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000004b-0000-004b-0000-005000000005"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "tddu3qs3p60da19ibmx92unwy8mu9goocijbeamqw4bn3d5kt6_zkm2x1j2mawr_ygt"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000005b-0000-004a-0000-000f00000029"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000005b-0000-004a-0000-000f00000029"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1119,7 +1121,7 @@ testObject_SimpleMembers_user_11 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000069-0000-003e-0000-002300000076"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000069-0000-003e-0000-002300000076"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1128,44 +1130,44 @@ testObject_SimpleMembers_user_11 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000003e-0000-0047-0000-005e0000006a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000003e-0000-0047-0000-005e0000006a"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "ku38a6jk6fswgsgegqka_b33d6gqkwcy7egbx2rpr4pyravsymugig8l6flqxjyyl"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000003d-0000-0034-0000-001100000003"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000003d-0000-0034-0000-001100000003"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "x3th543fq4asgv"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000042-0000-0018-0000-006f00000063"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000042-0000-0018-0000-006f00000063"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "qc679x0r4twf5feu87fjf1dukbgbjil0otcoyim397"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000066-0000-0065-0000-00640000006c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000066-0000-0065-0000-00640000006c"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "neniwbge16i_igh4jj_02qflp698pz5xy6hv435ma6q2qlxn3dyz2oao0b43gg93m"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000005b-0000-0068-0000-006300000031"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000005b-0000-0068-0000-006300000031"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "mbsnb3cb9i2dxlbz6h0l9_ocpa6zdmtt6708g6bi6b5o59v3s"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000025-0000-000d-0000-000000000008"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000025-0000-000d-0000-000000000008"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "bv8e7m3xfc3bt639goa1tied4"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000010-0000-0007-0000-005700000011"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000010-0000-0007-0000-005700000011"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
(parseRoleName "oejuw0rpkxojd7lwdvvmypnw5jga0w0i7kf84ryviznjgm_3nd1ls5ykcij2b_xqx9dc36hafa1lvk4x_vo_")
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000059-0000-0001-0000-005e00000073"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000059-0000-0001-0000-005e00000073"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "nzqm5_qyv5uj1f47xveo_2hlkqt5n6jrb5o14invzlhe2ddo66"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000063-0000-0061-0000-005f00000045"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000063-0000-0061-0000-005f00000045"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1174,7 +1176,7 @@ testObject_SimpleMembers_user_11 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000002e-0000-001e-0000-00350000001a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000002e-0000-001e-0000-00350000001a"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1183,7 +1185,7 @@ testObject_SimpleMembers_user_11 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000013-0000-005c-0000-006100000023"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000013-0000-005c-0000-006100000023"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1192,11 +1194,11 @@ testObject_SimpleMembers_user_11 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000000e-0000-0031-0000-00300000000c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000000e-0000-0031-0000-00300000000c"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "c3ydrescfgmvsgks6xy866xluancois0b4vl6ypsl6810rlnu"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000007e-0000-002a-0000-007c0000004f"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000007e-0000-002a-0000-007c0000004f"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1205,11 +1207,11 @@ testObject_SimpleMembers_user_11 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000011-0000-001e-0000-001e00000054"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000011-0000-001e-0000-001e00000054"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "bhpymrq9y__8p"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000005f-0000-000d-0000-004e00000016"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000005f-0000-000d-0000-004e00000016"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1218,11 +1220,11 @@ testObject_SimpleMembers_user_11 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000001f-0000-005c-0000-00570000002a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000001f-0000-005c-0000-00570000002a"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "xyep0gej_kghofx50j3bbolxbm2i58wwp0t_l0pscq4"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000003-0000-0050-0000-00110000004d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000003-0000-0050-0000-00110000004d"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1231,18 +1233,18 @@ testObject_SimpleMembers_user_11 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000055-0000-006f-0000-002f0000007a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000055-0000-006f-0000-002f0000007a"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "04n6"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000005a-0000-0067-0000-00600000006b"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000005a-0000-0067-0000-00600000006b"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
(parseRoleName "m1vbstno19orwr77zwq8q8ak1xxdhotqyn30kdv9fq44n2zr0rn46gqfrw8lxp7mt7eywgku3gudup")
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000048-0000-007b-0000-001500000034"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000048-0000-007b-0000-001500000034"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "v_ahoalwm78dh_ggai7wusblsnlwhibegsuxe5w1ibm2cnj79a64r_s72hwigx1cw"))
}
@@ -1254,7 +1256,7 @@ testObject_SimpleMembers_user_12 =
SimpleMembers
{ mMembers =
[ SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000003b-0000-0016-0000-003600000008"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000003b-0000-0016-0000-003600000008"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1263,25 +1265,25 @@ testObject_SimpleMembers_user_12 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000056-0000-0050-0000-00040000006d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000056-0000-0050-0000-00040000006d"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
(parseRoleName "xf6x34y4hcbgklhrr9a7jkjiclu5dv89m59b5sn40ui8iof88mse47t57ti7zch5cf866tzqua171us")
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000006e-0000-004e-0000-005200000050"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000006e-0000-004e-0000-005200000050"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
(parseRoleName "07bbbbgwl0gkv1pfj719wn3z0n8nehby_fk3h6gs39csow68u4_3pbly54fqkng37jqxwr6ym6injx")
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000069-0000-001a-0000-005c0000007b"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000069-0000-001a-0000-005c0000007b"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "bj5m"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000004d-0000-0002-0000-005700000067"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000004d-0000-0002-0000-005700000067"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1290,14 +1292,14 @@ testObject_SimpleMembers_user_12 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000001b-0000-0076-0000-002800000008"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000001b-0000-0076-0000-002800000008"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
(parseRoleName "z3dbyprvipeu8kl4fabnh24fo77t7gqcs0chxw34ovuru0mxeu6e_jl3s744uggcnwqcyhuzkn1ueko_k0")
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000032-0000-0007-0000-000400000047"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000032-0000-0007-0000-000400000047"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1306,15 +1308,15 @@ testObject_SimpleMembers_user_12 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000054-0000-0055-0000-002800000002"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000054-0000-0055-0000-002800000002"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "dnrdny"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000001c-0000-004b-0000-002c00000037"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000001c-0000-004b-0000-002c00000037"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "gf52"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000034-0000-007e-0000-006100000061"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000034-0000-007e-0000-006100000061"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1323,7 +1325,7 @@ testObject_SimpleMembers_user_12 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000030-0000-000b-0000-00790000002a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000030-0000-000b-0000-00790000002a"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1332,24 +1334,24 @@ testObject_SimpleMembers_user_12 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000004f-0000-0043-0000-004c0000002b"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000004f-0000-0043-0000-004c0000002b"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "gnpcz5crw82yyqtlvvvfdps3b5uxqr0a"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000079-0000-004b-0000-005e00000033"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000079-0000-004b-0000-005e00000033"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "h84nu68fxxen4b8d5i8br4gixwyntx3o597v_ds147th29_vkuxblstg9af6x3p7"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000003b-0000-006c-0000-000f00000059"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000003b-0000-006c-0000-000f00000059"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "uboc5sab9w92"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000080-0000-001e-0000-002100000043"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000080-0000-001e-0000-002100000043"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "1mcw_zxelu_doxdkqrc5tf660toco4vdv99oecl106z1ygzfnqo6buoysg_s"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000069-0000-0047-0000-00000000004e"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000069-0000-0047-0000-00000000004e"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1358,7 +1360,7 @@ testObject_SimpleMembers_user_12 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000001d-0000-007e-0000-004300000036"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000001d-0000-007e-0000-004300000036"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1367,7 +1369,7 @@ testObject_SimpleMembers_user_12 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000061-0000-005f-0000-00280000002d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000061-0000-005f-0000-00280000002d"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "xyb65ic8vzt9x9k1i_a81f6cngzuoii"))
}
]
@@ -1378,7 +1380,7 @@ testObject_SimpleMembers_user_13 =
SimpleMembers
{ mMembers =
[ SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000050-0000-0074-0000-00260000002b"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000050-0000-0074-0000-00260000002b"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1387,7 +1389,7 @@ testObject_SimpleMembers_user_13 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000000-0000-001d-0000-005f00000055"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000000-0000-001d-0000-005f00000055"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1396,12 +1398,12 @@ testObject_SimpleMembers_user_13 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000005-0000-0010-0000-003b00000043"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000005-0000-0010-0000-003b00000043"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "4kkuwyima3ztybzpf3ccy2_mrgcz2sv0nvb29bxjm90dgk6ft_14r7p0qyy12crv_z"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000040-0000-0065-0000-007000000049"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000040-0000-0065-0000-007000000049"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1410,20 +1412,20 @@ testObject_SimpleMembers_user_13 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000015-0000-007f-0000-006c0000004d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000015-0000-007f-0000-006c0000004d"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "qylzcwu0dvtjvra93ocg8fyuyzzowac5yo5410wh4sveczmfq0t2y2e6cae4fux96q"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000000e-0000-007b-0000-003400000043"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000000e-0000-007b-0000-003400000043"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "z3id3idffe8rl53wpyrd3f2l0y56qxz"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000005d-0000-000d-0000-004500000021"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000005d-0000-000d-0000-004500000021"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "2voj8d_5ydou6phiassv9tzhnw185814n90y8rbx5i"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000002d-0000-003c-0000-003e0000000f"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000002d-0000-003c-0000-003e0000000f"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1432,7 +1434,7 @@ testObject_SimpleMembers_user_13 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000000b-0000-0042-0000-00740000003b"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000000b-0000-0042-0000-00740000003b"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1441,7 +1443,7 @@ testObject_SimpleMembers_user_13 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000048-0000-0039-0000-008000000004"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000048-0000-0039-0000-008000000004"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "nmhc57h6qa7lzv0d0scl8_53iwuitrlmmujkwf_vgjgn4s027b5i9hbt2nxhm1d"))
}
@@ -1453,27 +1455,27 @@ testObject_SimpleMembers_user_14 =
SimpleMembers
{ mMembers =
[ SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000049-0000-0018-0000-002200000071"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000049-0000-0018-0000-002200000071"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "5qxti74lbqe_tgvvnq7ub2xxn0e2w0"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000003f-0000-004e-0000-005800000030"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000003f-0000-004e-0000-005800000030"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "mwzccu_p4zazafbgnvf"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000017-0000-007e-0000-006f00000027"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000017-0000-007e-0000-006f00000027"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "rmr13bsn9lo1dil9j12jj31qdod3izckzpsrflf653suq328bmnd_kirumpr"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000005-0000-003f-0000-00280000005e"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000005-0000-003f-0000-00280000005e"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "_tae"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000002b-0000-0003-0000-004a0000002d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000002b-0000-0003-0000-004a0000002d"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "kaa5_qbk5nvvgx4jowierx"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000005d-0000-0054-0000-002500000028"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000005d-0000-0054-0000-002500000028"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1482,28 +1484,28 @@ testObject_SimpleMembers_user_14 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000005e-0000-0002-0000-004b00000045"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000005e-0000-0002-0000-004b00000045"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "o1bfk_p6xvxp7t1i6f3d57jv2_yl4nq5or1zy4vd2dh22ue895yoduwjo3wc5qzostuhbw369j"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000005e-0000-0027-0000-003d00000065"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000005e-0000-0027-0000-003d00000065"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "vf6s6yc5eavaytm7_6"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000035-0000-0035-0000-00010000001f"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000035-0000-0035-0000-00010000001f"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "n7r9vlgda6kn7ehvrz_hrl6t1p07xr42_rgp"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000054-0000-0069-0000-002e0000007c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000054-0000-0069-0000-002e0000007c"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "qtfn187ab22rzoan9jy9ug2qyjisshxdeo184e8cjm"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000056-0000-005a-0000-006a0000004c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000056-0000-005a-0000-006a0000004c"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "lxynbdsl575ahtb1fzz_0ucdcsmeiu4baq0ziei5"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000041-0000-0013-0000-007700000017"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000041-0000-0013-0000-007700000017"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1512,7 +1514,7 @@ testObject_SimpleMembers_user_14 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000022-0000-0071-0000-000f00000072"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000022-0000-0071-0000-000f00000072"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1521,28 +1523,28 @@ testObject_SimpleMembers_user_14 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000003b-0000-003c-0000-003500000028"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000003b-0000-003c-0000-003500000028"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "vitd82h50v"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000047-0000-0005-0000-001a0000007b"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000047-0000-0005-0000-001a0000007b"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "io1uuzbdi7sfvy93f6kgdq31xskuwc8mxphwwrpv9rxc4o8ycdu4l4_0_26hm1g03g2"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000000a-0000-0039-0000-00530000006f"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000000a-0000-0039-0000-00530000006f"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "cksijt3o36xuu324i61apsuwdi32k3l1x_oalfaqqtk"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000047-0000-0062-0000-007700000025"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000047-0000-0062-0000-007700000025"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "ru1kksg2ef5_yo7i5uwq"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000049-0000-000c-0000-002400000060"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000049-0000-000c-0000-002400000060"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "lyspep_wcyu0fegqwpmns9lzjpy49i_6ufmhkft3bbmf_yi76hzdacj7"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000028-0000-001d-0000-005000000045"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000028-0000-001d-0000-005000000045"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1551,7 +1553,7 @@ testObject_SimpleMembers_user_14 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000071-0000-0014-0000-002000000080"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000071-0000-0014-0000-002000000080"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1560,7 +1562,7 @@ testObject_SimpleMembers_user_14 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000037-0000-004c-0000-006800000015"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000037-0000-004c-0000-006800000015"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1569,22 +1571,22 @@ testObject_SimpleMembers_user_14 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000005a-0000-0014-0000-005c0000000b"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000005a-0000-0014-0000-005c0000000b"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
(parseRoleName "r7xed76rtgltedolcrxbq67tyo5u5arm9ip49bo5szs24skzui_3h65_2j0md66gjlz850waloiuiqsd")
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000000c-0000-0059-0000-004600000053"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000000c-0000-0059-0000-004600000053"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "80_0tuom0zml0hz7q8ioxscxusk7ghx63wp5o83lax5"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000050-0000-0080-0000-00350000007c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000050-0000-0080-0000-00350000007c"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "ae3h61opsksj5x5if1tt3a74ehzw02ds6dqisz_5l"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000014-0000-0046-0000-008000000048"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000014-0000-0046-0000-008000000048"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1600,32 +1602,32 @@ testObject_SimpleMembers_user_15 =
SimpleMembers
{ mMembers =
[ SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000006a-0000-0032-0000-004f0000001e"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000006a-0000-0032-0000-004f0000001e"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "da1vnxfznxggp6c2qcjdx4sbo4usg7jb58hmd_ylzyr_97m9rpyg6gmw9ikw"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000001f-0000-005a-0000-001a00000078"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000001f-0000-005a-0000-001a00000078"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "asfpn3xoxsvsz8ubdt6b3b"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000001a-0000-0017-0000-007e0000000c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000001a-0000-0017-0000-007e0000000c"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "ftkjnuoy9i1h0yyf1x87m97flhx21n2475_rsnn76nkpl9toieae7wk0y_f83ji"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000001e-0000-0019-0000-00180000004c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000001e-0000-0019-0000-00180000004c"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "g22_kcj2fae4nspxpz30n5f6ib5bhrb"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000006d-0000-001b-0000-00050000003f"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000006d-0000-001b-0000-00050000003f"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "qzkowgmbm3t4ck1lzb96ero0d6yw79kzdf2q"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000037-0000-001c-0000-000c0000002f"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000037-0000-001c-0000-000c0000002f"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "cyiw0yfayzt_ynv0h94pdv0hl5u46adyyyb6n"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000047-0000-0014-0000-007e00000049"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000047-0000-0014-0000-007e00000049"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1634,16 +1636,16 @@ testObject_SimpleMembers_user_15 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000028-0000-002f-0000-004a00000063"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000028-0000-002f-0000-004a00000063"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "s1cu0tibrwjvgpa49x9sk9kuzyd4hco7pj3gnbcc8ie519vmobd70ln2im2dx_yg_qoh4rc8"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000027-0000-0075-0000-003f00000016"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000027-0000-0075-0000-003f00000016"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "e6t98s2m_0jqjwibfan257dq0tbxl452q0dcs5mkl4kn"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000045-0000-007a-0000-003900000015"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000045-0000-007a-0000-003900000015"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1652,7 +1654,7 @@ testObject_SimpleMembers_user_15 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000000-0000-0040-0000-002e0000007c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000000-0000-0040-0000-002e0000007c"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1661,11 +1663,11 @@ testObject_SimpleMembers_user_15 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000057-0000-0060-0000-006e0000001d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000057-0000-0060-0000-006e0000001d"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "hq3mg17cd7p27q00dhyjrdu7pr4kdplicp4ipm"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000037-0000-0007-0000-006600000055"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000037-0000-0007-0000-006600000055"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1674,7 +1676,7 @@ testObject_SimpleMembers_user_15 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000032-0000-0078-0000-005100000021"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000032-0000-0078-0000-005100000021"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "8_lth324f81q1zr6nhz1jw5oeu4ovjqnl8lobb9t3azlu7hj3s62_xm30b3fie4s"))
}
@@ -1686,7 +1688,7 @@ testObject_SimpleMembers_user_16 =
SimpleMembers
{ mMembers =
[ SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000001c-0000-0079-0000-006800000001"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000001c-0000-0079-0000-006800000001"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1695,26 +1697,26 @@ testObject_SimpleMembers_user_16 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000005c-0000-0056-0000-00780000001a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000005c-0000-0056-0000-00780000001a"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "5roj3c12kqt7"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000032-0000-007f-0000-00270000002a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000032-0000-007f-0000-00270000002a"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "r_ivn3ruci8x4pl6bl1g_jex4"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000002b-0000-000e-0000-003400000009"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000002b-0000-000e-0000-003400000009"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "x__i5068zhcdautdjavpic3zi7u950hdw_iy63gdd0h6zbs1pfviyyui2zl"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000007a-0000-0017-0000-005800000050"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000007a-0000-0017-0000-005800000050"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
(parseRoleName "g5ignr39a41zundyudm1rovkz6a3rjy3dodkwk0ht3jnsqp1maz2ulc7yx93z7uy_dyqso9ofxblm2xqs")
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000061-0000-0036-0000-006700000071"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000061-0000-0036-0000-006700000071"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1723,11 +1725,11 @@ testObject_SimpleMembers_user_16 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000060-0000-007e-0000-004f0000005a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000060-0000-007e-0000-004f0000005a"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "ox94nzepea1423z47_yd1txi"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000002e-0000-0016-0000-001e0000001a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000002e-0000-0016-0000-001e0000001a"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1736,15 +1738,15 @@ testObject_SimpleMembers_user_16 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000006b-0000-0038-0000-006a0000004d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000006b-0000-0038-0000-006a0000004d"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "307w5bmxkox9r8klphxtmjge_8jgawjnotx_r0krsadx_n7"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000043-0000-003e-0000-005a0000007d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000043-0000-003e-0000-005a0000007d"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "j6_h0zam0_k8x6coroh4ixk9m3pk5acmwx_cg1q7mpnmn1zha_i"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000002d-0000-0010-0000-00010000000f"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000002d-0000-0010-0000-00010000000f"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1753,15 +1755,15 @@ testObject_SimpleMembers_user_16 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000043-0000-0070-0000-006400000045"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000043-0000-0070-0000-006400000045"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "ds4scvwxorxgxtlskf27hu"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000026-0000-002a-0000-00150000005d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000026-0000-002a-0000-00150000005d"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "1j2p5d49kfu8omp83fr67o4qpdb07"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000079-0000-0040-0000-006100000033"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000079-0000-0040-0000-006100000033"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1770,14 +1772,14 @@ testObject_SimpleMembers_user_16 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000007b-0000-0033-0000-005a00000007"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000007b-0000-0033-0000-005a00000007"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
(parseRoleName "u0lw_wyfzjmmu17s65cau3i295l_0c4hu823csp473bry2cn2zr24vsay4w2m2936y9ja0mvapjxafww89o")
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000078-0000-0058-0000-006300000019"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000078-0000-0058-0000-006300000019"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1786,15 +1788,15 @@ testObject_SimpleMembers_user_16 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000068-0000-0042-0000-007b0000002b"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000068-0000-0042-0000-007b0000002b"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "cgx4sx81so7w8wyohtqvr53bzf_8od3j77"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000004b-0000-002a-0000-007000000043"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000004b-0000-002a-0000-007000000043"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "p35w8lh2_arnf44pbqrk3g4ln0881ml0b4t"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000006d-0000-007b-0000-006b00000005"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000006d-0000-007b-0000-006b00000005"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "0z7pdm65ezdilg_qqnzz34l5e1zi8gsw78qbwitnu2ng"))
}
]
@@ -1805,7 +1807,7 @@ testObject_SimpleMembers_user_17 =
SimpleMembers
{ mMembers =
[ SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000071-0000-0050-0000-006200000041"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000071-0000-0050-0000-006200000041"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1814,25 +1816,25 @@ testObject_SimpleMembers_user_17 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000003-0000-0074-0000-004e0000003c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000003-0000-0074-0000-004e0000003c"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "qsowevndt0gqwh1yvpqxd_4u3junr66dhuerv38qrhzv5kf9i38fkd"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000000b-0000-003a-0000-003900000026"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000000b-0000-003a-0000-003900000026"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "dq1mag9bzqoenu3chbc2mn91ivbh"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000001b-0000-0058-0000-000500000057"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000001b-0000-0058-0000-000500000057"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "9ij9p1jla54lbtk66mhbakd7m7p502p6tz1ryyaep94rz7upsquixaaf6eoewz_oziw_ok7xbo49"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000007a-0000-001d-0000-005800000052"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000007a-0000-001d-0000-005800000052"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "13tjs8e20xxqd296duhver4er47dj47v2yyspcpfz5pdhbmnmlxyzar0w2recatb6r4_20zcd"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000001a-0000-001e-0000-00360000007b"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000001a-0000-001e-0000-00360000007b"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "gy3s_2c0r9lzi08hjnkbc9pbhdu3yvg409ipjztpmthie_j834nn12zjq_m56w1dqqi8mpde"))
}
@@ -1844,14 +1846,14 @@ testObject_SimpleMembers_user_18 =
SimpleMembers
{ mMembers =
[ SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000013-0000-003e-0000-006000000058"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000013-0000-003e-0000-006000000058"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
(parseRoleName "3me5rxn2utoa25v5xxht8ulguq6yxi7tp38dwoyvs_4o40u1to2j5nrtykcbqmxuefqoulfptt90s")
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000002-0000-0026-0000-005d00000007"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000002-0000-0026-0000-005d00000007"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1860,7 +1862,7 @@ testObject_SimpleMembers_user_18 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000078-0000-005b-0000-00580000004f"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000078-0000-005b-0000-00580000004f"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1869,11 +1871,11 @@ testObject_SimpleMembers_user_18 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000054-0000-0053-0000-00340000006e"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000054-0000-0053-0000-00340000006e"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "tg_h0qkv4aijetsz83m1kgblaem7q"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000023-0000-002f-0000-002000000063"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000023-0000-002f-0000-002000000063"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1882,7 +1884,7 @@ testObject_SimpleMembers_user_18 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000022-0000-0057-0000-006f00000014"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000022-0000-0057-0000-006f00000014"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1891,28 +1893,28 @@ testObject_SimpleMembers_user_18 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000003-0000-0013-0000-003b0000002d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000003-0000-0013-0000-003b0000002d"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "b72qpthui23k7cbxz8m3226h"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000052-0000-0046-0000-002e0000003a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000052-0000-0046-0000-002e0000003a"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "4ndtltebfabogp8i9skodvx86xbu_"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000048-0000-0028-0000-001b00000006"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000048-0000-0028-0000-001b00000006"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "r9jg4"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000007b-0000-004d-0000-005500000006"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000007b-0000-004d-0000-005500000006"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "1_r3da_nqzfzbs_6j8sztfleq4ov3zk7e6lhjg04"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000000f-0000-0056-0000-002a00000066"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000000f-0000-0056-0000-002a00000066"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "_v361ue5c23jdmlu43s7eckol6hzqgdvd49z_ga87_gtfu6s6j49c2g12tfsv"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000014-0000-006d-0000-00600000005f"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000014-0000-006d-0000-00600000005f"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1921,7 +1923,7 @@ testObject_SimpleMembers_user_18 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000005c-0000-0022-0000-007c00000001"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000005c-0000-0022-0000-007c00000001"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1930,15 +1932,15 @@ testObject_SimpleMembers_user_18 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000008-0000-0042-0000-00250000007a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000008-0000-0042-0000-00250000007a"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "0_lettu8qvkqk6krt4_nez4e95b7y"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000001f-0000-0022-0000-007c00000028"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000001f-0000-0022-0000-007c00000028"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "z3o8c78vi1ynsrc_s6ebpnz96960dez7lnlijjz843un77jtnj9a5pah"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000022-0000-0045-0000-007d00000007"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000022-0000-0045-0000-007d00000007"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1947,11 +1949,11 @@ testObject_SimpleMembers_user_18 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000007f-0000-006c-0000-006f0000005a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000007f-0000-006c-0000-006f0000005a"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "fnxibm1l1089wzrxa"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000022-0000-0020-0000-006900000033"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000022-0000-0020-0000-006900000033"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -1960,19 +1962,19 @@ testObject_SimpleMembers_user_18 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000000-0000-002f-0000-007000000064"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000000-0000-002f-0000-007000000064"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "b6fsp76pbub9rakkxrs7lk8gsh005ajo5m8ap4apvxjxoak95s"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000002-0000-0042-0000-000000000058"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000002-0000-0042-0000-000000000058"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "hinbqrmh843xzsvpu_a6ifnc6lc164f58gkhv"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000005f-0000-0052-0000-005400000002"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000005f-0000-0052-0000-005400000002"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "18os8cjhuuv8ng"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000004b-0000-0046-0000-006e00000024"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000004b-0000-0046-0000-006e00000024"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "4k62w0mz4j2hsstjelh8zx0gpg927v3ggod9z17i"))
}
]
@@ -1983,16 +1985,16 @@ testObject_SimpleMembers_user_19 =
SimpleMembers
{ mMembers =
[ SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000036-0000-0080-0000-004600000006"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000036-0000-0080-0000-004600000006"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "4fex2pu__ri6dlr68us285w6yv4alufdibfd_b8zt7ckdo7ej590lkosvd4be8cg4acr7"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000078-0000-007c-0000-004d0000001c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000078-0000-007c-0000-004d0000001c"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "6lab82iykqaweibdnw89206lrz9vs16h6ae31uruwd0dat90ms"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000002b-0000-0013-0000-006900000050"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000002b-0000-0013-0000-006900000050"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -2001,11 +2003,11 @@ testObject_SimpleMembers_user_19 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000037-0000-0053-0000-00300000001c"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000037-0000-0053-0000-00300000001c"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "u9dnn4lg0fkq7wjm352pnvivghndsyu5dc1v7"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000001a-0000-0014-0000-004100000073"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000001a-0000-0014-0000-004100000073"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -2014,7 +2016,7 @@ testObject_SimpleMembers_user_19 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000001-0000-003c-0000-007f00000051"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000001-0000-003c-0000-007f00000051"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -2023,11 +2025,11 @@ testObject_SimpleMembers_user_19 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000023-0000-001f-0000-001b0000007f"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000023-0000-001f-0000-001b0000007f"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "b3pojvx2wmrpy7q6adcuo5szs"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000055-0000-004d-0000-006900000053"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000055-0000-004d-0000-006900000053"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -2036,15 +2038,15 @@ testObject_SimpleMembers_user_19 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000047-0000-0003-0000-000500000001"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000047-0000-0003-0000-000500000001"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "xbu28z6zird3kd4iqv0j2r7_e0b2qdxzpdeuzvxb__idnrzhib1rud5o98b4"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000004b-0000-003b-0000-007f0000005a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000004b-0000-003b-0000-007f0000005a"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "rl_mm"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000006-0000-0034-0000-007e00000030"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000006-0000-0034-0000-007e00000030"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -2053,7 +2055,7 @@ testObject_SimpleMembers_user_19 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000006d-0000-007e-0000-000f00000060"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000006d-0000-007e-0000-000f00000060"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -2062,11 +2064,11 @@ testObject_SimpleMembers_user_19 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000075-0000-0022-0000-001100000044"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000075-0000-0022-0000-001100000044"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "vcan3ha2ahaaaxbs_rks5vygwuny8zp6st17fv9pk04f_2onxvw_guw"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000069-0000-0024-0000-00790000005f"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000069-0000-0024-0000-00790000005f"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -2075,7 +2077,7 @@ testObject_SimpleMembers_user_19 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000068-0000-0040-0000-003100000045"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000068-0000-0040-0000-003100000045"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -2084,7 +2086,7 @@ testObject_SimpleMembers_user_19 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000004a-0000-0007-0000-005f0000003d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000004a-0000-0007-0000-005f0000003d"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -2093,19 +2095,19 @@ testObject_SimpleMembers_user_19 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000068-0000-0008-0000-006f00000067"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000068-0000-0008-0000-006f00000067"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "qq2hd0"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000002-0000-000d-0000-00320000006b"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000002-0000-000d-0000-00320000006b"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "8hsl7yd_1raa4a"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000001-0000-000f-0000-001800000008"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000001-0000-000f-0000-001800000008"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "ftgvbfyalwkdozipte"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000004e-0000-001f-0000-001e0000007d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000004e-0000-001f-0000-001e0000007d"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -2121,14 +2123,14 @@ testObject_SimpleMembers_user_20 =
SimpleMembers
{ mMembers =
[ SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000001-0000-0026-0000-00580000001f"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000001-0000-0026-0000-00580000001f"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
(parseRoleName "wmx8molqscfxab9_imrcssdgf0_4m2ik51npx6i23vig82mer1rji1xwvddqxasyw6jqmy0xzykd2ums")
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000002b-0000-003b-0000-007600000012"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000002b-0000-003b-0000-007600000012"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -2137,11 +2139,11 @@ testObject_SimpleMembers_user_20 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000005b-0000-005f-0000-006500000034"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000005b-0000-005f-0000-006500000034"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "lvczhtpd0dgdsvzxtzyelmrbh6dkl17j3drta713mm4i"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000002b-0000-0003-0000-001700000060"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000002b-0000-0003-0000-001700000060"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -2150,15 +2152,15 @@ testObject_SimpleMembers_user_20 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000072-0000-0012-0000-000500000052"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000072-0000-0012-0000-000500000052"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "sjkd77e2wg_ddyj59wpadnncaup_41e7m8ayhs936zwkfy5"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000003a-0000-002b-0000-00050000002d"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000003a-0000-002b-0000-00050000002d"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "3bvwtadqp5w2ode0z"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000047-0000-006c-0000-00500000003b"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000047-0000-006c-0000-00500000003b"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -2167,24 +2169,24 @@ testObject_SimpleMembers_user_20 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000000-0000-0075-0000-000b0000000b"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000000-0000-0075-0000-000b0000000b"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "rspqdh94do5jxbxub9t7"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000006d-0000-002b-0000-003800000003"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000006d-0000-002b-0000-003800000003"))) (Domain "faraway.example.com"),
smConvRoleName =
(fromJust (parseRoleName "fqkwb05s1i7aww45jcx5hptvdzd856n2y_8uy5v35zcxhu07jp6v19ax1juyczkgtiiw"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000005c-0000-003c-0000-003d00000059"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000005c-0000-003c-0000-003d00000059"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "fq7zom614_e"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000015-0000-0050-0000-002200000061"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000015-0000-0050-0000-002200000061"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "j3kukcuzzfid3ecr70nzd"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "0000002e-0000-002e-0000-006100000030"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "0000002e-0000-002e-0000-006100000030"))) (Domain "faraway.example.com"),
smConvRoleName =
( fromJust
( parseRoleName
@@ -2193,11 +2195,11 @@ testObject_SimpleMembers_user_20 =
)
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000010-0000-0030-0000-006c0000006b"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000010-0000-0030-0000-006c0000006b"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "5v8e6cih_ueu_a2wd28uj8boqxv3gmfx15u1chfrbf_1fupa7fo_yqd"))
},
SimpleMember
- { smId = (Id (fromJust (UUID.fromString "00000037-0000-0048-0000-00460000006a"))),
+ { smQualifiedId = Qualified (Id (fromJust (UUID.fromString "00000037-0000-0048-0000-00460000006a"))) (Domain "faraway.example.com"),
smConvRoleName = (fromJust (parseRoleName "dxwk4qalr3oi4jh6v8e3r4agor5vce0b_5w_b3fwmdwfhc3_mqsk94ngdw"))
}
]
diff --git a/libs/wire-api/test/unit/Test/Wire/API/Roundtrip/Aeson.hs b/libs/wire-api/test/unit/Test/Wire/API/Roundtrip/Aeson.hs
index 37b6ffe427a..5a795309fc4 100644
--- a/libs/wire-api/test/unit/Test/Wire/API/Roundtrip/Aeson.hs
+++ b/libs/wire-api/test/unit/Test/Wire/API/Roundtrip/Aeson.hs
@@ -216,6 +216,7 @@ tests =
testRoundTrip @Team.LegalHold.External.NewLegalHoldClient,
testRoundTrip @Team.LegalHold.External.LegalHoldServiceConfirm,
testRoundTrip @Team.LegalHold.External.LegalHoldServiceRemove,
+ testRoundTrip @Team.LegalHold.LegalholdProtectee,
testRoundTrip @Team.Member.TeamMember,
testRoundTrip @Team.Member.ListType,
testRoundTrip @Team.Member.NewListType,
@@ -272,6 +273,7 @@ tests =
testRoundTrip @User.Auth.AccessToken,
testRoundTrip @(User.Client.UserClientMap Int),
testRoundTrip @User.Client.UserClients,
+ testRoundTrip @User.Client.UserClientsFull,
testRoundTrip @User.Client.ClientType,
testRoundTrip @User.Client.ClientClass,
testRoundTrip @User.Client.PubClient,
diff --git a/libs/wire-api/wire-api.cabal b/libs/wire-api/wire-api.cabal
index 1594637bd7f..924e5697041 100644
--- a/libs/wire-api/wire-api.cabal
+++ b/libs/wire-api/wire-api.cabal
@@ -4,7 +4,7 @@ cabal-version: 1.12
--
-- see: https://github.com/sol/hpack
--
--- hash: 363ed356edfc5bd34796bdcf14804d55e5d9faa5ef023a7e10e62219d2261323
+-- hash: 9404af483be016a508ad4a9919330394d673e725945662da3786fe94eb0785e1
name: wire-api
version: 0.1.0
@@ -33,6 +33,7 @@ library
Wire.API.Conversation.Typing
Wire.API.Cookie
Wire.API.CustomBackend
+ Wire.API.ErrorDescription
Wire.API.Event.Conversation
Wire.API.Event.Team
Wire.API.Message
diff --git a/services/brig/src/Brig/API/Client.hs b/services/brig/src/Brig/API/Client.hs
index 5efcd56d98f..944cef88bb4 100644
--- a/services/brig/src/Brig/API/Client.hs
+++ b/services/brig/src/Brig/API/Client.hs
@@ -25,7 +25,6 @@ module Brig.API.Client
legalHoldClientRequested,
removeLegalHoldClient,
lookupClient,
- lookupLocalClientCapabilities,
lookupClients,
lookupPubClientsBulk,
Data.lookupPrekeyIds,
@@ -35,6 +34,7 @@ module Brig.API.Client
claimLocalMultiPrekeyBundles,
claimLocalPrekeyBundle,
claimPrekey,
+ claimLocalPrekey,
claimPrekeyBundle,
claimMultiPrekeyBundles,
Data.lookupClientIds,
@@ -46,6 +46,7 @@ import Brig.App
import qualified Brig.Data.Client as Data
import qualified Brig.Data.User as Data
import qualified Brig.Federation.Client as Federation
+import Brig.IO.Intra (guardLegalhold)
import qualified Brig.IO.Intra as Intra
import qualified Brig.Options as Opt
import Brig.Types
@@ -73,7 +74,9 @@ import qualified System.Logger.Class as Log
import UnliftIO.Async (Concurrently (Concurrently, runConcurrently))
import Wire.API.Federation.Client (FederationError (..))
import qualified Wire.API.Message as Message
+import Wire.API.Team.LegalHold (LegalholdProtectee (..))
import Wire.API.User.Client (ClientCapabilityList (..), QualifiedUserClientPrekeyMap (..), QualifiedUserClients (..), UserClientPrekeyMap, mkQualifiedUserClientPrekeyMap, mkUserClientPrekeyMap)
+import qualified Wire.API.User.Client as Client
import Wire.API.UserMap (QualifiedUserMap (QualifiedUserMap))
lookupClient :: Qualified UserId -> ClientId -> ExceptT ClientError AppIO (Maybe Client)
@@ -87,10 +90,6 @@ lookupClient (Qualified uid domain) clientId = do
lookupLocalClient :: UserId -> ClientId -> AppIO (Maybe Client)
lookupLocalClient = Data.lookupClient
-lookupLocalClientCapabilities :: UserId -> ClientId -> AppIO ClientCapabilityList
-lookupLocalClientCapabilities uid cid =
- ClientCapabilityList . fromMaybe mempty <$> Data.lookupClientCapabilities uid cid
-
lookupClients :: Qualified UserId -> ExceptT ClientError AppIO [Client]
lookupClients (Qualified uid domain) = do
localdomain <- viewFederationDomain
@@ -116,7 +115,15 @@ addClient u con ip new = do
acc <- lift (Data.lookupAccount u) >>= maybe (throwE (ClientUserNotFound u)) return
loc <- maybe (return Nothing) locationOf ip
maxPermClients <- fromMaybe Opt.defUserMaxPermClients <$> Opt.setUserMaxPermClients <$> view settings
- (clt, old, count) <- Data.addClient u clientId' new maxPermClients loc !>> ClientDataError
+ let caps :: Maybe (Set Client.ClientCapability)
+ caps = updlhdev $ newClientCapabilities new
+ where
+ updlhdev =
+ if newClientType new == LegalHoldClientType
+ then Just . maybe (Set.singleton lhcaps) (Set.insert lhcaps)
+ else id
+ lhcaps = Client.ClientSupportsLegalholdImplicitConsent
+ (clt, old, count) <- Data.addClient u clientId' new maxPermClients loc caps !>> ClientDataError
let usr = accountUser acc
lift $ do
for_ old $ execDelete u con
@@ -133,12 +140,10 @@ addClient u con ip new = do
updateClient :: UserId -> ClientId -> UpdateClient -> ExceptT ClientError AppIO ()
updateClient u c r = do
- ok <- lift $ Data.hasClient u c
- unless ok $
- throwE ClientNotFound
+ client <- lift (Data.lookupClient u c) >>= maybe (throwE ClientNotFound) pure
for_ (updateClientLabel r) $ lift . Data.updateClientLabel u c . Just
for_ (updateClientCapabilities r) $ \caps' -> do
- caps <- fromMaybe mempty <$> Data.lookupClientCapabilities u c
+ let ClientCapabilityList caps = clientCapabilities client
if caps `Set.isSubsetOf` caps'
then lift . Data.updateClientCapabilities u c . Just $ caps'
else throwE ClientCapabilitiesCannotBeRemoved
@@ -161,39 +166,43 @@ rmClient u con clt pw =
_ -> Data.reauthenticate u pw !>> ClientDataError . ClientReAuthError
lift $ execDelete u (Just con) client
-claimPrekey :: UserId -> Domain -> ClientId -> ExceptT ClientError AppIO (Maybe ClientPrekey)
-claimPrekey u d c = do
+claimPrekey :: LegalholdProtectee -> UserId -> Domain -> ClientId -> ExceptT ClientError AppIO (Maybe ClientPrekey)
+claimPrekey protectee u d c = do
isLocalDomain <- (d ==) <$> viewFederationDomain
if isLocalDomain
- then lift $ claimLocalPrekey u c
+ then claimLocalPrekey protectee u c
else claimRemotePrekey (Qualified u d) c
-claimLocalPrekey :: UserId -> ClientId -> AppIO (Maybe ClientPrekey)
-claimLocalPrekey user client = do
- prekey <- Data.claimPrekey user client
- when (isNothing prekey) (noPrekeys user client)
- pure prekey
+claimLocalPrekey :: LegalholdProtectee -> UserId -> ClientId -> ExceptT ClientError AppIO (Maybe ClientPrekey)
+claimLocalPrekey protectee user client = do
+ guardLegalhold protectee (Client.mkUserClients [(user, [client])])
+ lift $ do
+ prekey <- Data.claimPrekey user client
+ when (isNothing prekey) (noPrekeys user client)
+ pure prekey
claimRemotePrekey :: Qualified UserId -> ClientId -> ExceptT ClientError AppIO (Maybe ClientPrekey)
claimRemotePrekey quser client = fmapLT ClientFederationError $ Federation.claimPrekey quser client
-claimPrekeyBundle :: Domain -> UserId -> ExceptT ClientError AppIO PrekeyBundle
-claimPrekeyBundle domain uid = do
+claimPrekeyBundle :: LegalholdProtectee -> Domain -> UserId -> ExceptT ClientError AppIO PrekeyBundle
+claimPrekeyBundle protectee domain uid = do
isLocalDomain <- (domain ==) <$> viewFederationDomain
if isLocalDomain
- then lift $ claimLocalPrekeyBundle uid
+ then claimLocalPrekeyBundle protectee uid
else claimRemotePrekeyBundle (Qualified uid domain)
-claimLocalPrekeyBundle :: UserId -> AppIO PrekeyBundle
-claimLocalPrekeyBundle u = do
+claimLocalPrekeyBundle :: LegalholdProtectee -> UserId -> ExceptT ClientError AppIO PrekeyBundle
+claimLocalPrekeyBundle protectee u = do
clients <- map clientId <$> Data.lookupClients u
- PrekeyBundle u . catMaybes <$> mapM (Data.claimPrekey u) clients
+ guardLegalhold protectee (Client.mkUserClients [(u, clients)])
+ PrekeyBundle u . catMaybes <$> lift (mapM (Data.claimPrekey u) clients)
claimRemotePrekeyBundle :: Qualified UserId -> ExceptT ClientError AppIO PrekeyBundle
-claimRemotePrekeyBundle quser = Federation.claimPrekeyBundle quser !>> ClientFederationError
+claimRemotePrekeyBundle quser = do
+ Federation.claimPrekeyBundle quser !>> ClientFederationError
-claimMultiPrekeyBundles :: QualifiedUserClients -> ExceptT ClientError AppIO QualifiedUserClientPrekeyMap
-claimMultiPrekeyBundles quc = do
+claimMultiPrekeyBundles :: LegalholdProtectee -> QualifiedUserClients -> ExceptT ClientError AppIO QualifiedUserClientPrekeyMap
+claimMultiPrekeyBundles protectee quc = do
localDomain <- viewFederationDomain
fmap (mkQualifiedUserClientPrekeyMap . Map.fromList)
-- FUTUREWORK(federation): parallelise federator requests here
@@ -204,11 +213,19 @@ claimMultiPrekeyBundles quc = do
where
claim :: Domain -> Domain -> UserClients -> ExceptT ClientError AppIO UserClientPrekeyMap
claim localDomain domain uc
- | domain == localDomain = lift (claimLocalMultiPrekeyBundles uc)
+ | domain == localDomain = claimLocalMultiPrekeyBundles protectee uc
| otherwise = Federation.claimMultiPrekeyBundle domain uc !>> ClientFederationError
-claimLocalMultiPrekeyBundles :: UserClients -> AppIO UserClientPrekeyMap
-claimLocalMultiPrekeyBundles = fmap mkUserClientPrekeyMap . foldMap (getChunk . Map.fromList) . chunksOf 16 . Map.toList . Message.userClients
+claimLocalMultiPrekeyBundles :: LegalholdProtectee -> UserClients -> ExceptT ClientError AppIO UserClientPrekeyMap
+claimLocalMultiPrekeyBundles protectee userClients = do
+ guardLegalhold protectee userClients
+ lift
+ . fmap mkUserClientPrekeyMap
+ . foldMap (getChunk . Map.fromList)
+ . chunksOf 16
+ . Map.toList
+ . Message.userClients
+ $ userClients
where
getChunk :: Map UserId (Set ClientId) -> AppIO (Map UserId (Map ClientId (Maybe Prekey)))
getChunk =
diff --git a/services/brig/src/Brig/API/Connection.hs b/services/brig/src/Brig/API/Connection.hs
index 9ebb35ff5d2..6b170f9f26b 100644
--- a/services/brig/src/Brig/API/Connection.hs
+++ b/services/brig/src/Brig/API/Connection.hs
@@ -30,6 +30,7 @@ module Brig.API.Connection
lookupConnections,
Data.lookupConnection,
Data.lookupConnectionStatus,
+ Data.lookupConnectionStatus',
Data.lookupContactList,
)
where
@@ -59,6 +60,7 @@ import qualified Galley.Types.Teams as Team
import Imports
import qualified System.Logger.Class as Log
import System.Logger.Message
+import Wire.API.Connection (RelationWithHistory (..))
import qualified Wire.API.Conversation as Conv
createConnection ::
@@ -106,8 +108,8 @@ createConnectionToLocalUser self crUser ConnectionRequest {crName, crMessage} co
logConnection self crUser
. msg (val "Creating connection")
cnv <- Intra.createConnectConv self crUser (Just crName) (Just crMessage) (Just conn)
- s2o' <- Data.insertConnection self crUser Sent (Just crMessage) cnv
- o2s' <- Data.insertConnection crUser self Pending (Just crMessage) cnv
+ s2o' <- Data.insertConnection self crUser SentWithHistory (Just crMessage) cnv
+ o2s' <- Data.insertConnection crUser self PendingWithHistory (Just crMessage) cnv
e2o <- ConnectionUpdated o2s' (ucStatus <$> o2s) <$> Data.lookupName self
let e2s = ConnectionUpdated s2o' (ucStatus <$> s2o) Nothing
mapM_ (Intra.onConnectionEvent self (Just conn)) [e2o, e2s]
@@ -121,7 +123,7 @@ createConnectionToLocalUser self crUser ConnectionRequest {crName, crMessage} co
(Accepted, Blocked) -> return $ ConnectionExists s2o
(Sent, Blocked) -> return $ ConnectionExists s2o
(Blocked, _) -> throwE $ InvalidTransition self Sent
- (_, Blocked) -> change s2o Sent
+ (_, Blocked) -> change s2o SentWithHistory
(_, Sent) -> accept s2o o2s
(_, Accepted) -> accept s2o o2s
(_, Ignored) -> resend s2o o2s
@@ -136,12 +138,12 @@ createConnectionToLocalUser self crUser ConnectionRequest {crName, crMessage} co
logConnection self (ucTo s2o)
. msg (val "Accepting connection")
cnv <- lift $ for (ucConvId s2o) $ Intra.acceptConnectConv self (Just conn)
- s2o' <- lift $ Data.updateConnection s2o Accepted
+ s2o' <- lift $ Data.updateConnection s2o AcceptedWithHistory
o2s' <-
lift $
if (cnvType <$> cnv) == Just ConnectConv
- then Data.updateConnection o2s Blocked
- else Data.updateConnection o2s Accepted
+ then Data.updateConnection o2s BlockedWithHistory
+ else Data.updateConnection o2s AcceptedWithHistory
e2o <- lift $ ConnectionUpdated o2s' (Just $ ucStatus o2s) <$> Data.lookupName self
let e2s = ConnectionUpdated s2o' (Just $ ucStatus s2o) Nothing
lift $ mapM_ (Intra.onConnectionEvent self (Just conn)) [e2o, e2s]
@@ -157,7 +159,7 @@ createConnectionToLocalUser self crUser ConnectionRequest {crName, crMessage} co
s2o' <- insert (Just s2o) (Just o2s)
return $ ConnectionExists s2o'
- change :: UserConnection -> Relation -> ExceptT ConnectionError AppIO ConnectionResult
+ change :: UserConnection -> RelationWithHistory -> ExceptT ConnectionError AppIO ConnectionResult
change c s = ConnectionExists <$> lift (Data.updateConnection c s)
belongSameTeam :: AppIO Bool
@@ -270,11 +272,11 @@ updateConnection self other newStatus conn = do
when (ucStatus o2s `elem` [Sent, Pending]) . lift $ do
o2s' <-
if (cnvType <$> cnv) /= Just ConnectConv
- then Data.updateConnection o2s Accepted
- else Data.updateConnection o2s Blocked
+ then Data.updateConnection o2s AcceptedWithHistory
+ else Data.updateConnection o2s BlockedWithHistory
e2o <- ConnectionUpdated o2s' (Just $ ucStatus o2s) <$> Data.lookupName self
Intra.onConnectionEvent self conn e2o
- lift $ Just <$> Data.updateConnection s2o Accepted
+ lift $ Just <$> Data.updateConnection s2o AcceptedWithHistory
block :: UserConnection -> ExceptT ConnectionError AppIO (Maybe UserConnection)
block s2o = lift $ do
@@ -282,10 +284,11 @@ updateConnection self other newStatus conn = do
logConnection self (ucTo s2o)
. msg (val "Blocking connection")
for_ (ucConvId s2o) $ Intra.blockConv (ucFrom s2o) conn
- Just <$> Data.updateConnection s2o Blocked
+ Just <$> Data.updateConnection s2o BlockedWithHistory
unblock :: UserConnection -> UserConnection -> Relation -> ExceptT ConnectionError AppIO (Maybe UserConnection)
unblock s2o o2s new = do
+ -- FUTUREWORK: new is always in [Sent, Accepted]. Refactor to total function.
when (new `elem` [Sent, Accepted]) $
checkLimit self
Log.info $
@@ -295,12 +298,12 @@ updateConnection self other newStatus conn = do
when (ucStatus o2s == Sent && new == Accepted) . lift $ do
o2s' :: UserConnection <-
if (cnvType <$> cnv) /= Just ConnectConv
- then Data.updateConnection o2s Accepted
- else Data.updateConnection o2s Blocked
+ then Data.updateConnection o2s AcceptedWithHistory
+ else Data.updateConnection o2s BlockedWithHistory
e2o :: ConnectionEvent <- ConnectionUpdated o2s' (Just $ ucStatus o2s) <$> Data.lookupName self
-- TODO: is this correct? shouldnt o2s be sent to other?
Intra.onConnectionEvent self conn e2o
- lift $ Just <$> Data.updateConnection s2o new
+ lift $ Just <$> Data.updateConnection s2o (mkRelationWithHistory (error "impossible") new)
cancel :: UserConnection -> UserConnection -> ExceptT ConnectionError AppIO (Maybe UserConnection)
cancel s2o o2s = do
@@ -308,17 +311,37 @@ updateConnection self other newStatus conn = do
logConnection self (ucTo s2o)
. msg (val "Cancelling connection")
lift . for_ (ucConvId s2o) $ Intra.blockConv (ucFrom s2o) conn
- o2s' <- lift $ Data.updateConnection o2s Cancelled
+ o2s' <- lift $ Data.updateConnection o2s CancelledWithHistory
let e2o = ConnectionUpdated o2s' (Just $ ucStatus o2s) Nothing
lift $ Intra.onConnectionEvent self conn e2o
change s2o Cancelled
change :: UserConnection -> Relation -> ExceptT ConnectionError AppIO (Maybe UserConnection)
- change c s = lift $ Just <$> Data.updateConnection c s
+ change c s = do
+ -- FUTUREWORK: refactor to total function. Gets only called with either Ignored, Accepted, Cancelled
+ lift $ Just <$> Data.updateConnection c (mkRelationWithHistory (error "impossible") s)
connection :: UserId -> UserId -> ExceptT ConnectionError AppIO UserConnection
connection a b = lift (Data.lookupConnection a b) >>= tryJust (NotConnected a b)
+mkRelationWithHistory :: HasCallStack => Relation -> Relation -> RelationWithHistory
+mkRelationWithHistory oldRel = \case
+ Accepted -> AcceptedWithHistory
+ Blocked -> BlockedWithHistory
+ Pending -> PendingWithHistory
+ Ignored -> IgnoredWithHistory
+ Sent -> SentWithHistory
+ Cancelled -> CancelledWithHistory
+ MissingLegalholdConsent ->
+ case oldRel of
+ Accepted -> MissingLegalholdConsentFromAccepted
+ Blocked -> MissingLegalholdConsentFromBlocked
+ Pending -> MissingLegalholdConsentFromPending
+ Ignored -> MissingLegalholdConsentFromIgnored
+ Sent -> MissingLegalholdConsentFromSent
+ Cancelled -> MissingLegalholdConsentFromCancelled
+ MissingLegalholdConsent -> error "impossible old relation"
+
updateConnectionInternal ::
UpdateConnectionsInternal ->
ExceptT ConnectionError AppIO ()
@@ -337,9 +360,8 @@ updateConnectionInternal = \case
s2o <- connection self other
o2s <- connection other self
for_ [s2o, o2s] $ \(uconn :: UserConnection) -> lift $ do
- -- TODO: check if Ignored is a possibility
Intra.blockConv (ucFrom uconn) Nothing `mapM_` ucConvId uconn
- uconn' <- Data.updateConnection uconn MissingLegalholdConsent
+ uconn' <- Data.updateConnection uconn (mkRelationWithHistory (ucStatus uconn) MissingLegalholdConsent)
let ev = ConnectionUpdated uconn' (Just $ ucStatus uconn) Nothing
Intra.onConnectionEvent self Nothing ev
@@ -371,19 +393,32 @@ updateConnectionInternal = \case
unblockDirected :: UserConnection -> UserConnection -> ExceptT ConnectionError AppIO ()
unblockDirected uconn uconnRev = do
- cnv :: Maybe Conv.Conversation <- lift . for (ucConvId uconn) $ Intra.unblockConv (ucFrom uconn) Nothing
- uconnRev' :: UserConnection <- do
- newRelation <- case cnvType <$> cnv of
- Just RegularConv -> throwE (InvalidTransition (ucFrom uconn) Accepted) -- (impossible, connection conv is always 1:1)
- Just SelfConv -> throwE (InvalidTransition (ucFrom uconn) Accepted)
- Just One2OneConv -> pure Accepted
- Just ConnectConv -> pure Sent
- Nothing -> throwE (InvalidTransition (ucFrom uconn) Accepted)
- lift $ Data.updateConnection uconnRev newRelation
-
+ void . lift . for (ucConvId uconn) $ Intra.unblockConv (ucFrom uconn) Nothing
+ uconnRevRel :: RelationWithHistory <- relationWithHistory (ucFrom uconnRev) (ucTo uconnRev)
+ uconnRev' <- lift $ Data.updateConnection uconnRev (undoRelationHistory uconnRevRel)
connEvent :: ConnectionEvent <- lift $ ConnectionUpdated uconnRev' (Just $ ucStatus uconnRev) <$> Data.lookupName (ucFrom uconn)
lift $ Intra.onConnectionEvent (ucFrom uconn) Nothing connEvent
+ relationWithHistory :: UserId -> UserId -> ExceptT ConnectionError AppIO RelationWithHistory
+ relationWithHistory a b = lift (Data.lookupRelationWithHistory a b) >>= tryJust (NotConnected a b)
+
+ undoRelationHistory :: RelationWithHistory -> RelationWithHistory
+ undoRelationHistory = \case
+ -- these cases are relevant.
+ MissingLegalholdConsentFromAccepted -> AcceptedWithHistory
+ MissingLegalholdConsentFromBlocked -> BlockedWithHistory
+ MissingLegalholdConsentFromPending -> PendingWithHistory
+ MissingLegalholdConsentFromIgnored -> IgnoredWithHistory
+ MissingLegalholdConsentFromSent -> SentWithHistory
+ MissingLegalholdConsentFromCancelled -> CancelledWithHistory
+ -- these cases should not be reachable, but if they are, this is probably what is expected from this function.
+ AcceptedWithHistory -> AcceptedWithHistory
+ BlockedWithHistory -> BlockedWithHistory
+ PendingWithHistory -> PendingWithHistory
+ IgnoredWithHistory -> IgnoredWithHistory
+ SentWithHistory -> SentWithHistory
+ CancelledWithHistory -> CancelledWithHistory
+
autoConnect ::
UserId ->
Set UserId ->
diff --git a/services/brig/src/Brig/API/Error.hs b/services/brig/src/Brig/API/Error.hs
index d9ec54aacc3..eba59c80e81 100644
--- a/services/brig/src/Brig/API/Error.hs
+++ b/services/brig/src/Brig/API/Error.hs
@@ -28,17 +28,13 @@ import Data.ByteString.Conversion
import Data.Domain (Domain)
import qualified Data.HashMap.Strict as HashMap
import Data.String.Conversions (cs)
-import qualified Data.Text.Encoding as T
-import qualified Data.Text.Lazy as LT
import qualified Data.ZAuth.Validation as ZAuth
import Imports
import Network.HTTP.Types.Header
import Network.HTTP.Types.Status
-import qualified Network.HTTP.Types.Status as HTTP
import qualified Network.Wai.Utilities.Error as Wai
-import qualified Servant.Client as Servant
-import Wire.API.Federation.Client (FederationClientError (..), FederationError (..))
-import qualified Wire.API.Federation.GRPC.Types as Proto
+import Wire.API.Federation.Client (FederationError (..))
+import Wire.API.Federation.Error
data Error where
StdError :: !Wai.Error -> Error
@@ -181,39 +177,10 @@ clientError ClientLegalHoldCannotBeRemoved = StdError can'tDeleteLegalHoldClient
clientError ClientLegalHoldCannotBeAdded = StdError can'tAddLegalHoldClient
clientError (ClientFederationError e) = fedError e
clientError ClientCapabilitiesCannotBeRemoved = StdError clientCapabilitiesCannotBeRemoved
+clientError ClientMissingLegalholdConsent = StdError missingLegalholdConsent
fedError :: FederationError -> Error
-fedError (FederationUnavailable err) = StdError (federationUnavailable err)
-fedError FederationNotImplemented = StdError federationNotImplemented
-fedError FederationNotConfigured = StdError federationNotConfigured
-fedError (FederationCallFailure err) =
- case err of
- FederationClientRPCError msg -> StdError (federationRpcError msg)
- FederationClientInvalidMethod mth ->
- StdError $
- federationInvalidCall
- ("Unexpected method: " <> LT.fromStrict (T.decodeUtf8 mth))
- FederationClientStreamingUnsupported -> StdError $ federationInvalidCall "Streaming unsupported"
- FederationClientOutwardError outwardErr -> StdError $ federationRemoteError outwardErr
- FederationClientServantError (Servant.DecodeFailure msg _) -> StdError $ federationInvalidBody msg
- FederationClientServantError (Servant.FailureResponse _ _) ->
- StdError $ Wai.Error unexpectedFederationResponseStatus "unknown-federation-error" "Unknown federation error"
- FederationClientServantError (Servant.InvalidContentTypeHeader res) ->
- StdError $
- Wai.Error
- unexpectedFederationResponseStatus
- "federation-invalid-content-type-header"
- ("Content-type: " <> contentType res)
- FederationClientServantError (Servant.UnsupportedContentType mediaType res) ->
- StdError $
- Wai.Error
- unexpectedFederationResponseStatus
- "federation-unsupported-content-type"
- ("Content-type: " <> contentType res <> ", Media-Type: " <> cs (show mediaType))
- FederationClientServantError (Servant.ConnectionError excpetion) ->
- StdError $ federationUnavailable $ cs $ show excpetion
- where
- contentType = LT.fromStrict . T.decodeUtf8 . maybe "" snd . find (\(name, _) -> name == "Content-Type") . Servant.responseHeaders
+fedError = StdError . federationErrorToWai
idtError :: RemoveIdentityError -> Error
idtError LastIdentity = StdError lastIdentity
@@ -252,121 +219,121 @@ updateProfileError (ProfileNotFound _) = StdError userNotFound
-- WAI Errors -----------------------------------------------------------------
tooManyProperties :: Wai.Error
-tooManyProperties = Wai.Error status403 "too-many-properties" "Too many properties"
+tooManyProperties = Wai.mkError status403 "too-many-properties" "Too many properties"
propertyKeyTooLarge :: Wai.Error
-propertyKeyTooLarge = Wai.Error status403 "property-key-too-large" "The property key is too large."
+propertyKeyTooLarge = Wai.mkError status403 "property-key-too-large" "The property key is too large."
propertyValueTooLarge :: Wai.Error
-propertyValueTooLarge = Wai.Error status403 "property-value-too-large" "The property value is too large"
+propertyValueTooLarge = Wai.mkError status403 "property-value-too-large" "The property value is too large"
connectionLimitReached :: Wai.Error
-connectionLimitReached = Wai.Error status403 "connection-limit" "Too many sent/accepted connections."
+connectionLimitReached = Wai.mkError status403 "connection-limit" "Too many sent/accepted connections."
missingAuthError :: Wai.Error
-missingAuthError = Wai.Error status403 "missing-auth" "Re-authentication via password required."
+missingAuthError = Wai.mkError status403 "missing-auth" "Re-authentication via password required."
clientNotFound :: Wai.Error
-clientNotFound = Wai.Error status404 "client-not-found" "Client not found"
+clientNotFound = Wai.mkError status404 "client-not-found" "Client not found"
tooManyClients :: Wai.Error
-tooManyClients = Wai.Error status403 "too-many-clients" "Too many clients"
+tooManyClients = Wai.mkError status403 "too-many-clients" "Too many clients"
malformedPrekeys :: Wai.Error
-malformedPrekeys = Wai.Error status400 "bad-request" "Malformed prekeys uploaded."
+malformedPrekeys = Wai.mkError status400 "bad-request" "Malformed prekeys uploaded."
clientCapabilitiesCannotBeRemoved :: Wai.Error
-clientCapabilitiesCannotBeRemoved = Wai.Error status409 "client-capabilities-cannot-be-removed" "You can only add capabilities to a client, not remove them."
+clientCapabilitiesCannotBeRemoved = Wai.mkError status409 "client-capabilities-cannot-be-removed" "You can only add capabilities to a client, not remove them."
invalidUser :: Wai.Error
-invalidUser = Wai.Error status400 "invalid-user" "Invalid user."
+invalidUser = Wai.mkError status400 "invalid-user" "Invalid user."
invalidTransition :: Wai.Error
-invalidTransition = Wai.Error status403 "bad-conn-update" "Invalid status transition."
+invalidTransition = Wai.mkError status403 "bad-conn-update" "Invalid status transition."
notConnected :: Wai.Error
-notConnected = Wai.Error status403 "no-connection" "No connection exists between users."
+notConnected = Wai.mkError status403 "no-connection" "No connection exists between users."
noIdentity :: Int -> Wai.Error
-noIdentity i = Wai.Error status403 "no-identity" ("The user has no verified identity (email or phone number). [code: " <> cs (show i) <> "]")
+noIdentity i = Wai.mkError status403 "no-identity" ("The user has no verified identity (email or phone number). [code: " <> cs (show i) <> "]")
noEmail :: Wai.Error
-noEmail = Wai.Error status403 "no-email" "This operation requires the user to have a verified email address."
+noEmail = Wai.mkError status403 "no-email" "This operation requires the user to have a verified email address."
lastIdentity :: Wai.Error
-lastIdentity = Wai.Error status403 "last-identity" "The last user identity (email or phone number) cannot be removed."
+lastIdentity = Wai.mkError status403 "last-identity" "The last user identity (email or phone number) cannot be removed."
noPassword :: Wai.Error
-noPassword = Wai.Error status403 "no-password" "The user has no password."
+noPassword = Wai.mkError status403 "no-password" "The user has no password."
invalidEmail :: Wai.Error
-invalidEmail = Wai.Error status400 "invalid-email" "Invalid e-mail address."
+invalidEmail = Wai.mkError status400 "invalid-email" "Invalid e-mail address."
invalidPwResetKey :: Wai.Error
-invalidPwResetKey = Wai.Error status400 "invalid-key" "Invalid email or mobile number for password reset."
+invalidPwResetKey = Wai.mkError status400 "invalid-key" "Invalid email or mobile number for password reset."
resetPasswordMustDiffer :: Wai.Error
-resetPasswordMustDiffer = Wai.Error status409 "password-must-differ" "For password reset, new and old password must be different."
+resetPasswordMustDiffer = Wai.mkError status409 "password-must-differ" "For password reset, new and old password must be different."
changePasswordMustDiffer :: Wai.Error
-changePasswordMustDiffer = Wai.Error status409 "password-must-differ" "For password change, new and old password must be different."
+changePasswordMustDiffer = Wai.mkError status409 "password-must-differ" "For password change, new and old password must be different."
invalidPhone :: Wai.Error
-invalidPhone = Wai.Error status400 "invalid-phone" "Invalid mobile phone number."
+invalidPhone = Wai.mkError status400 "invalid-phone" "Invalid mobile phone number."
invalidInvitationCode :: Wai.Error
-invalidInvitationCode = Wai.Error status400 "invalid-invitation-code" "Invalid invitation code."
+invalidInvitationCode = Wai.mkError status400 "invalid-invitation-code" "Invalid invitation code."
missingIdentity :: Wai.Error
-missingIdentity = Wai.Error status403 "missing-identity" "Using an invitation code requires registering the given email and/or phone."
+missingIdentity = Wai.mkError status403 "missing-identity" "Using an invitation code requires registering the given email and/or phone."
invalidPwResetCode :: Wai.Error
-invalidPwResetCode = Wai.Error status400 "invalid-code" "Invalid password reset code."
+invalidPwResetCode = Wai.mkError status400 "invalid-code" "Invalid password reset code."
duplicatePwResetCode :: Wai.Error
-duplicatePwResetCode = Wai.Error status409 "code-exists" "A password reset is already in progress."
+duplicatePwResetCode = Wai.mkError status409 "code-exists" "A password reset is already in progress."
userKeyExists :: Wai.Error
-userKeyExists = Wai.Error status409 "key-exists" "The given e-mail address or phone number is in use."
+userKeyExists = Wai.mkError status409 "key-exists" "The given e-mail address or phone number is in use."
emailExists :: Wai.Error
-emailExists = Wai.Error status409 "email-exists" "The given e-mail address is in use."
+emailExists = Wai.mkError status409 "email-exists" "The given e-mail address is in use."
phoneExists :: Wai.Error
-phoneExists = Wai.Error status409 "phone-exists" "The given phone number is in use."
+phoneExists = Wai.mkError status409 "phone-exists" "The given phone number is in use."
handleExists :: Wai.Error
-handleExists = Wai.Error status409 "handle-exists" "The given handle is already taken."
+handleExists = Wai.mkError status409 "handle-exists" "The given handle is already taken."
invalidHandle :: Wai.Error
-invalidHandle = Wai.Error status400 "invalid-handle" "The given handle is invalid."
+invalidHandle = Wai.mkError status400 "invalid-handle" "The given handle is invalid."
badRequest :: LText -> Wai.Error
-badRequest = Wai.Error status400 "bad-request"
+badRequest = Wai.mkError status400 "bad-request"
loginCodePending :: Wai.Error
-loginCodePending = Wai.Error status403 "pending-login" "A login code is still pending."
+loginCodePending = Wai.mkError status403 "pending-login" "A login code is still pending."
loginCodeNotFound :: Wai.Error
-loginCodeNotFound = Wai.Error status404 "no-pending-login" "No login code was found."
+loginCodeNotFound = Wai.mkError status404 "no-pending-login" "No login code was found."
accountPending :: Wai.Error
-accountPending = Wai.Error status403 "pending-activation" "Account pending activation."
+accountPending = Wai.mkError status403 "pending-activation" "Account pending activation."
accountSuspended :: Wai.Error
-accountSuspended = Wai.Error status403 "suspended" "Account suspended."
+accountSuspended = Wai.mkError status403 "suspended" "Account suspended."
accountEphemeral :: Wai.Error
-accountEphemeral = Wai.Error status403 "ephemeral" "Account is ephemeral."
+accountEphemeral = Wai.mkError status403 "ephemeral" "Account is ephemeral."
badCredentials :: Wai.Error
-badCredentials = Wai.Error status403 "invalid-credentials" "Authentication failed."
+badCredentials = Wai.mkError status403 "invalid-credentials" "Authentication failed."
newPasswordMustDiffer :: Wai.Error
-newPasswordMustDiffer = Wai.Error status409 "password-must-differ" "For provider password change or reset, new and old password must be different."
+newPasswordMustDiffer = Wai.mkError status409 "password-must-differ" "For provider password change or reset, new and old password must be different."
notFound :: LText -> Wai.Error
-notFound = Wai.Error status404 "not-found"
+notFound = Wai.mkError status404 "not-found"
userNotFound :: Wai.Error
userNotFound = notFound "User not found."
@@ -375,29 +342,29 @@ handleNotFound :: Wai.Error
handleNotFound = notFound "Handle not found."
invalidCode :: Wai.Error
-invalidCode = Wai.Error status403 "invalid-code" "Invalid verification code"
+invalidCode = Wai.mkError status403 "invalid-code" "Invalid verification code"
invalidAccountStatus :: Wai.Error
-invalidAccountStatus = Wai.Error status400 "invalid-status" "The specified account status cannot be set."
+invalidAccountStatus = Wai.mkError status400 "invalid-status" "The specified account status cannot be set."
activationKeyNotFound :: Wai.Error
activationKeyNotFound = notFound "Activation key not found."
invalidActivationCode :: LText -> Wai.Error
-invalidActivationCode = Wai.Error status404 "invalid-code"
+invalidActivationCode = Wai.mkError status404 "invalid-code"
activationCodeNotFound :: Wai.Error
activationCodeNotFound = invalidActivationCode "Activation key/code not found or invalid."
deletionCodePending :: Wai.Error
-deletionCodePending = Wai.Error status403 "pending-delete" "A verification code for account deletion is still pending."
+deletionCodePending = Wai.mkError status403 "pending-delete" "A verification code for account deletion is still pending."
whitelistError :: Wai.Error
-whitelistError = Wai.Error status403 "unauthorized" "Unauthorized e-mail address or phone number."
+whitelistError = Wai.mkError status403 "unauthorized" "Unauthorized e-mail address or phone number."
blacklistedEmail :: Wai.Error
blacklistedEmail =
- Wai.Error
+ Wai.mkError
status403
"blacklisted-email"
"The given e-mail address has been blacklisted due to a permanent bounce \
@@ -405,7 +372,7 @@ blacklistedEmail =
blacklistedPhone :: Wai.Error
blacklistedPhone =
- Wai.Error
+ Wai.mkError
status403
"blacklisted-phone"
"The given phone number has been blacklisted due to suspected abuse \
@@ -413,14 +380,14 @@ blacklistedPhone =
passwordExists :: Wai.Error
passwordExists =
- Wai.Error
+ Wai.mkError
status403
"password-exists"
"The operation is not permitted because the user has a password set."
phoneBudgetExhausted :: Wai.Error
phoneBudgetExhausted =
- Wai.Error
+ Wai.mkError
status403
"phone-budget-exhausted"
"The SMS or voice call budget for the given phone number has been \
@@ -429,34 +396,34 @@ phoneBudgetExhausted =
\permanent blacklisting of the phone number."
authMissingCookie :: Wai.Error
-authMissingCookie = Wai.Error status403 "invalid-credentials" "Missing cookie"
+authMissingCookie = Wai.mkError status403 "invalid-credentials" "Missing cookie"
authMissingToken :: Wai.Error
-authMissingToken = Wai.Error status403 "invalid-credentials" "Missing token"
+authMissingToken = Wai.mkError status403 "invalid-credentials" "Missing token"
authMissingCookieAndToken :: Wai.Error
-authMissingCookieAndToken = Wai.Error status403 "invalid-credentials" "Missing cookie and token"
+authMissingCookieAndToken = Wai.mkError status403 "invalid-credentials" "Missing cookie and token"
invalidUserToken :: Wai.Error
-invalidUserToken = Wai.Error status403 "invalid-credentials" "Invalid user token"
+invalidUserToken = Wai.mkError status403 "invalid-credentials" "Invalid user token"
invalidAccessToken :: Wai.Error
-invalidAccessToken = Wai.Error status403 "invalid-credentials" "Invalid access token"
+invalidAccessToken = Wai.mkError status403 "invalid-credentials" "Invalid access token"
authTokenMismatch :: Wai.Error
-authTokenMismatch = Wai.Error status403 "invalid-credentials" "Token mismatch"
+authTokenMismatch = Wai.mkError status403 "invalid-credentials" "Token mismatch"
authTokenExpired :: Wai.Error
-authTokenExpired = Wai.Error status403 "invalid-credentials" "Token expired"
+authTokenExpired = Wai.mkError status403 "invalid-credentials" "Token expired"
authTokenInvalid :: Wai.Error
-authTokenInvalid = Wai.Error status403 "invalid-credentials" "Invalid token"
+authTokenInvalid = Wai.mkError status403 "invalid-credentials" "Invalid token"
authTokenUnsupported :: Wai.Error
-authTokenUnsupported = Wai.Error status403 "invalid-credentials" "Unsupported token operation for this token type"
+authTokenUnsupported = Wai.mkError status403 "invalid-credentials" "Unsupported token operation for this token type"
incorrectPermissions :: Wai.Error
-incorrectPermissions = Wai.Error status403 "invalid-permissions" "Copy permissions must be a subset of self permissions"
+incorrectPermissions = Wai.mkError status403 "invalid-permissions" "Copy permissions must be a subset of self permissions"
-- | User's relation to the team is not what we expect it to be. Examples:
--
@@ -467,155 +434,77 @@ incorrectPermissions = Wai.Error status403 "invalid-permissions" "Copy permissio
--
-- * Requested action can't be performed if the user is the only team owner left in the team.
insufficientTeamPermissions :: Wai.Error
-insufficientTeamPermissions = Wai.Error status403 "insufficient-permissions" "Insufficient team permissions"
+insufficientTeamPermissions = Wai.mkError status403 "insufficient-permissions" "Insufficient team permissions"
noBindingTeam :: Wai.Error
-noBindingTeam = Wai.Error status403 "no-binding-team" "Operation allowed only on binding teams"
+noBindingTeam = Wai.mkError status403 "no-binding-team" "Operation allowed only on binding teams"
propertyManagedByScim :: LText -> Wai.Error
-propertyManagedByScim prop = Wai.Error status403 "managed-by-scim" $ "Updating \"" <> prop <> "\" is not allowed, because it is managed by SCIM"
+propertyManagedByScim prop = Wai.mkError status403 "managed-by-scim" $ "Updating \"" <> prop <> "\" is not allowed, because it is managed by SCIM"
sameBindingTeamUsers :: Wai.Error
-sameBindingTeamUsers = Wai.Error status403 "same-binding-team-users" "Operation not allowed to binding team users."
+sameBindingTeamUsers = Wai.mkError status403 "same-binding-team-users" "Operation not allowed to binding team users."
missingLegalholdConsent :: Wai.Error
-missingLegalholdConsent = Wai.Error status412 "missing-legalhold-consent" "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."
+missingLegalholdConsent = Wai.mkError status412 "missing-legalhold-consent" "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."
ownerDeletingSelf :: Wai.Error
ownerDeletingSelf =
- Wai.Error
+ Wai.mkError
status403
"no-self-delete-for-team-owner"
"Team owners are not allowed to delete themselves. Ask a fellow owner."
tooManyTeamInvitations :: Wai.Error
-tooManyTeamInvitations = Wai.Error status403 "too-many-team-invitations" "Too many team invitations for this team."
+tooManyTeamInvitations = Wai.mkError status403 "too-many-team-invitations" "Too many team invitations for this team."
tooManyTeamMembers :: Wai.Error
-tooManyTeamMembers = Wai.Error status403 "too-many-team-members" "Too many members in this team."
+tooManyTeamMembers = Wai.mkError status403 "too-many-team-members" "Too many members in this team."
-- | docs/reference/user/registration.md {#RefRestrictRegistration}.
userCreationRestricted :: Wai.Error
-userCreationRestricted = Wai.Error status403 "user-creation-restricted" "This instance does not allow creation of personal users or teams."
+userCreationRestricted = Wai.mkError status403 "user-creation-restricted" "This instance does not allow creation of personal users or teams."
-- | In contrast to 'tooManyFailedLogins', this is about too many *successful* logins.
loginsTooFrequent :: Wai.Error
-loginsTooFrequent = Wai.Error status429 "client-error" "Logins too frequent"
+loginsTooFrequent = Wai.mkError status429 "client-error" "Logins too frequent"
tooManyFailedLogins :: Wai.Error
-tooManyFailedLogins = Wai.Error status403 "client-error" "Too many failed logins"
+tooManyFailedLogins = Wai.mkError status403 "client-error" "Too many failed logins"
tooLargeRichInfo :: Wai.Error
-tooLargeRichInfo = Wai.Error status413 "too-large-rich-info" "Rich info has exceeded the limit"
+tooLargeRichInfo = Wai.mkError status413 "too-large-rich-info" "Rich info has exceeded the limit"
internalServerError :: Wai.Error
-internalServerError = Wai.Error status500 "internal-server-error" "Internal Server Error"
+internalServerError = Wai.mkError status500 "internal-server-error" "Internal Server Error"
invalidRange :: LText -> Wai.Error
-invalidRange = Wai.Error status400 "client-error"
+invalidRange = Wai.mkError status400 "client-error"
--- Legalhold
can'tDeleteLegalHoldClient :: Wai.Error
can'tDeleteLegalHoldClient =
- Wai.Error
+ Wai.mkError
status400
"client-error"
"LegalHold clients cannot be deleted. LegalHold must be disabled on this user by an admin"
can'tAddLegalHoldClient :: Wai.Error
can'tAddLegalHoldClient =
- Wai.Error
+ Wai.mkError
status400
"client-error"
"LegalHold clients cannot be added manually. LegalHold must be enabled on this user by an admin"
legalHoldNotEnabled :: Wai.Error
-legalHoldNotEnabled = Wai.Error status403 "legalhold-not-enabled" "LegalHold must be enabled and configured on the team first"
+legalHoldNotEnabled = Wai.mkError status403 "legalhold-not-enabled" "LegalHold must be enabled and configured on the team first"
-- (the tautological constraint in the type signature is added so that once we remove the
-- feature, ghc will guide us here.)
customerExtensionBlockedDomain :: (DomainsBlockedForRegistration ~ DomainsBlockedForRegistration) => Domain -> Wai.Error
-customerExtensionBlockedDomain domain = Wai.Error (mkStatus 451 "Unavailable For Legal Reasons") "domain-blocked-for-registration" msg
+customerExtensionBlockedDomain domain = Wai.mkError (mkStatus 451 "Unavailable For Legal Reasons") "domain-blocked-for-registration" msg
where
msg =
"[Customer extension] the email domain " <> cs (show domain)
<> " that you are attempting to register a user with has been \
\blocked for creating wire users. Please contact your IT department."
-
---------------------------------------------------------------------------------
--- Federation
-
-noFederationStatus :: Status
-noFederationStatus = status403
-
-unexpectedFederationResponseStatus :: Status
-unexpectedFederationResponseStatus = HTTP.Status 533 "Unexpected Federation Response"
-
-federatorConnectionRefusedStatus :: Status
-federatorConnectionRefusedStatus = HTTP.Status 521 "Remote Federator Connection Refused"
-
-federationNotImplemented :: Wai.Error
-federationNotImplemented =
- Wai.Error
- noFederationStatus
- "federation-not-implemented"
- "Federation is not yet implemented for this endpoint"
-
-federationInvalidCode :: Word32 -> Wai.Error
-federationInvalidCode code =
- Wai.Error
- unexpectedFederationResponseStatus
- "federation-invalid-code"
- ("Invalid response code from remote federator: " <> LT.pack (show code))
-
-federationInvalidBody :: Text -> Wai.Error
-federationInvalidBody msg =
- Wai.Error
- unexpectedFederationResponseStatus
- "federation-invalid-body"
- ("Could not parse remote federator response: " <> LT.fromStrict msg)
-
-federationNotConfigured :: Wai.Error
-federationNotConfigured =
- Wai.Error
- HTTP.status400
- "federation-not-enabled"
- "no federator configured on brig"
-
-federationRpcError :: Text -> Wai.Error
-federationRpcError msg =
- Wai.Error
- HTTP.status500
- "federation-rpc-error"
- (LT.fromStrict msg)
-
-federationUnavailable :: Text -> Wai.Error
-federationUnavailable err =
- Wai.Error
- HTTP.status500
- "federation-not-available"
- ("Local federator not available: " <> LT.fromStrict err)
-
-federationRemoteError :: Proto.OutwardError -> Wai.Error
-federationRemoteError err = Wai.Error status (LT.fromStrict label) (LT.fromStrict msg)
- where
- decodeError :: Maybe Proto.ErrorPayload -> (Text, Text)
- decodeError Nothing = ("unknown-federation-error", "Unknown federation error")
- decodeError (Just (Proto.ErrorPayload label' msg')) = (label', msg')
-
- (label, msg) = decodeError (Proto.outwardErrorPayload err)
-
- status = case Proto.outwardErrorType err of
- Proto.RemoteNotFound -> HTTP.status422
- Proto.DiscoveryFailed -> HTTP.status500
- Proto.ConnectionRefused -> HTTP.Status 521 "Web Server Is Down"
- Proto.TLSFailure -> HTTP.Status 525 "SSL Handshake Failure"
- Proto.InvalidCertificate -> HTTP.Status 526 "Invalid SSL Certificate"
- Proto.VersionMismatch -> HTTP.Status 531 "Version Mismatch"
- Proto.FederationDeniedByRemote -> HTTP.Status 532 "Federation Denied"
- Proto.FederationDeniedLocally -> HTTP.status400
- Proto.RemoteFederatorError -> unexpectedFederationResponseStatus
- Proto.InvalidRequest -> HTTP.status500
-
-federationInvalidCall :: LText -> Wai.Error
-federationInvalidCall = Wai.Error HTTP.status500 "federation-invalid-call"
diff --git a/services/brig/src/Brig/API/Federation.hs b/services/brig/src/Brig/API/Federation.hs
index 74b48a31980..deab91f9909 100644
--- a/services/brig/src/Brig/API/Federation.hs
+++ b/services/brig/src/Brig/API/Federation.hs
@@ -18,20 +18,22 @@
module Brig.API.Federation (federationSitemap) where
import qualified Brig.API.Client as API
+import Brig.API.Error (clientError)
import Brig.API.Handler (Handler)
import qualified Brig.API.User as API
-import qualified Brig.Data.Client as Data
import Brig.Types (PrekeyBundle)
import Brig.User.API.Handle
import Data.Handle (Handle (..), parseHandle)
import Data.Id (ClientId, UserId)
import Imports
+import Network.Wai.Utilities.Error ((!>>))
import Servant (ServerT)
import Servant.API.Generic (ToServantApi)
import Servant.Server.Generic (genericServerT)
import Wire.API.Federation.API.Brig (SearchRequest (SearchRequest))
import qualified Wire.API.Federation.API.Brig as Federated
import Wire.API.Message (UserClients)
+import Wire.API.Team.LegalHold (LegalholdProtectee (LegalholdPlusFederationNotImplemented))
import Wire.API.User (UserProfile)
import Wire.API.User.Client (UserClientPrekeyMap)
import Wire.API.User.Client.Prekey (ClientPrekey)
@@ -63,13 +65,15 @@ getUsersByIds uids =
lift (API.lookupLocalProfiles Nothing uids)
claimPrekey :: (UserId, ClientId) -> Handler (Maybe ClientPrekey)
-claimPrekey (user, client) = lift (Data.claimPrekey user client)
+claimPrekey (user, client) = do
+ API.claimLocalPrekey LegalholdPlusFederationNotImplemented user client !>> clientError
claimPrekeyBundle :: UserId -> Handler PrekeyBundle
-claimPrekeyBundle user = lift (API.claimLocalPrekeyBundle user)
+claimPrekeyBundle user =
+ API.claimLocalPrekeyBundle LegalholdPlusFederationNotImplemented user !>> clientError
claimMultiPrekeyBundle :: UserClients -> Handler UserClientPrekeyMap
-claimMultiPrekeyBundle uc = lift (API.claimLocalMultiPrekeyBundles uc)
+claimMultiPrekeyBundle uc = API.claimLocalMultiPrekeyBundles LegalholdPlusFederationNotImplemented uc !>> clientError
-- | Searching for federated users on a remote backend should
-- only search by exact handle search, not in elasticsearch.
diff --git a/services/brig/src/Brig/API/Internal.hs b/services/brig/src/Brig/API/Internal.hs
index 5b4a6fa4829..6476d9c63ca 100644
--- a/services/brig/src/Brig/API/Internal.hs
+++ b/services/brig/src/Brig/API/Internal.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE RecordWildCards #-}
-
-- This file is part of the Wire Server implementation.
--
-- Copyright (C) 2020 Wire Swiss GmbH
@@ -34,6 +32,7 @@ import Brig.API.Types
import qualified Brig.API.User as API
import Brig.API.Util (validateHandle)
import Brig.App
+import qualified Brig.Data.Client as Data
import qualified Brig.Data.User as Data
import qualified Brig.IO.Intra as Intra
import Brig.Options hiding (internalEvents, sesQueue)
@@ -74,6 +73,7 @@ import Servant.Swagger.Internal.Orphans ()
import Servant.Swagger.UI
import qualified System.Logger.Class as Log
import Wire.API.User
+import Wire.API.User.Client (UserClientsFull (..))
import Wire.API.User.RichInfo
---------------------------------------------------------------------------
@@ -271,6 +271,10 @@ sitemap = do
accept "application" "json"
.&. jsonRequest @UserSet
+ post "/i/clients/full" (continue internalListFullClientsH) $
+ accept "application" "json"
+ .&. jsonRequest @UserSet
+
-- This endpoint can lead to the following events being sent:
-- - ClientAdded event to the user
-- - ClientRemoved event to the user, if removing old clients due to max number of clients
@@ -333,6 +337,14 @@ internalListClients (UserSet usrs) = do
UserClients . Map.fromList
<$> API.lookupUsersClientIds (Set.toList usrs)
+internalListFullClientsH :: JSON ::: JsonRequest UserSet -> Handler Response
+internalListFullClientsH (_ ::: req) =
+ json <$> (lift . internalListFullClients =<< parseJsonBody req)
+
+internalListFullClients :: UserSet -> AppIO UserClientsFull
+internalListFullClients (UserSet usrs) =
+ UserClientsFull <$> Data.lookupClientsBulk (Set.toList usrs)
+
autoConnectH :: JSON ::: UserId ::: Maybe ConnId ::: JsonRequest UserSet -> Handler Response
autoConnectH (_ ::: uid ::: conn ::: req) = do
json <$> (autoConnect uid conn =<< parseJsonBody req)
@@ -487,7 +499,7 @@ getConnectionsStatusH (_ ::: req ::: flt) = do
getConnectionsStatus :: ConnectionsStatusRequest -> Maybe Relation -> AppIO [ConnectionStatus]
getConnectionsStatus ConnectionsStatusRequest {csrFrom, csrTo} flt = do
- r <- API.lookupConnectionStatus csrFrom csrTo
+ r <- maybe (API.lookupConnectionStatus' csrFrom) (API.lookupConnectionStatus csrFrom) csrTo
return $ maybe r (filterByRelation r) flt
where
filterByRelation l rel = filter ((== rel) . csStatus) l
diff --git a/services/brig/src/Brig/API/Public.hs b/services/brig/src/Brig/API/Public.hs
index b0a0dcd5fff..c92524f685c 100644
--- a/services/brig/src/Brig/API/Public.hs
+++ b/services/brig/src/Brig/API/Public.hs
@@ -99,6 +99,7 @@ import qualified Wire.API.Routes.Public.LegalHold as LegalHoldAPI
import qualified Wire.API.Routes.Public.Spar as SparAPI
import qualified Wire.API.Swagger as Public.Swagger (models)
import qualified Wire.API.Team as Public
+import Wire.API.Team.LegalHold (LegalholdProtectee (..))
import qualified Wire.API.User as Public
import qualified Wire.API.User.Activation as Public
import qualified Wire.API.User.Auth as Public
@@ -830,34 +831,34 @@ listPropertyKeysAndValuesH (u ::: _) = do
keysAndVals <- lift (API.lookupPropertyKeysAndValues u)
pure $ json (keysAndVals :: Public.PropertyKeysAndValues)
-getPrekeyUnqualifiedH :: UserId -> ClientId -> Handler Public.ClientPrekey
-getPrekeyUnqualifiedH user client = do
+getPrekeyUnqualifiedH :: UserId -> UserId -> ClientId -> Handler Public.ClientPrekey
+getPrekeyUnqualifiedH zusr user client = do
domain <- viewFederationDomain
- getPrekeyH domain user client
+ getPrekeyH zusr domain user client
-getPrekeyH :: Domain -> UserId -> ClientId -> Handler Public.ClientPrekey
-getPrekeyH domain user client = do
- mPrekey <- API.claimPrekey user domain client !>> clientError
+getPrekeyH :: UserId -> Domain -> UserId -> ClientId -> Handler Public.ClientPrekey
+getPrekeyH zusr domain user client = do
+ mPrekey <- API.claimPrekey (ProtectedUser zusr) user domain client !>> clientError
ifNothing (notFound "prekey not found") mPrekey
-getPrekeyBundleUnqualifiedH :: UserId -> Handler Public.PrekeyBundle
-getPrekeyBundleUnqualifiedH uid = do
+getPrekeyBundleUnqualifiedH :: UserId -> UserId -> Handler Public.PrekeyBundle
+getPrekeyBundleUnqualifiedH zusr uid = do
domain <- viewFederationDomain
- API.claimPrekeyBundle domain uid !>> clientError
+ API.claimPrekeyBundle (ProtectedUser zusr) domain uid !>> clientError
-getPrekeyBundleH :: Domain -> UserId -> Handler Public.PrekeyBundle
-getPrekeyBundleH domain uid =
- API.claimPrekeyBundle domain uid !>> clientError
+getPrekeyBundleH :: UserId -> Domain -> UserId -> Handler Public.PrekeyBundle
+getPrekeyBundleH zusr domain uid =
+ API.claimPrekeyBundle (ProtectedUser zusr) domain uid !>> clientError
-getMultiUserPrekeyBundleUnqualifiedH :: Public.UserClients -> Handler Public.UserClientPrekeyMap
-getMultiUserPrekeyBundleUnqualifiedH userClients = do
+getMultiUserPrekeyBundleUnqualifiedH :: UserId -> Public.UserClients -> Handler Public.UserClientPrekeyMap
+getMultiUserPrekeyBundleUnqualifiedH zusr userClients = do
maxSize <- fromIntegral . setMaxConvSize <$> view settings
when (Map.size (Public.userClients userClients) > maxSize) $
throwStd tooManyClients
- lift $ API.claimLocalMultiPrekeyBundles userClients
+ API.claimLocalMultiPrekeyBundles (ProtectedUser zusr) userClients !>> clientError
-getMultiUserPrekeyBundleH :: Public.QualifiedUserClients -> Handler Public.QualifiedUserClientPrekeyMap
-getMultiUserPrekeyBundleH qualUserClients = do
+getMultiUserPrekeyBundleH :: UserId -> Public.QualifiedUserClients -> Handler Public.QualifiedUserClientPrekeyMap
+getMultiUserPrekeyBundleH zusr qualUserClients = do
maxSize <- fromIntegral . setMaxConvSize <$> view settings
let Sum (size :: Int) =
Map.foldMapWithKey
@@ -865,7 +866,7 @@ getMultiUserPrekeyBundleH qualUserClients = do
(Public.qualifiedUserClients qualUserClients)
when (size > maxSize) $
throwStd tooManyClients
- API.claimMultiPrekeyBundles qualUserClients !>> clientError
+ API.claimMultiPrekeyBundles (ProtectedUser zusr) qualUserClients !>> clientError
addClientH :: JsonRequest Public.NewClient ::: UserId ::: ConnId ::: Maybe IpAddr ::: JSON -> Handler Response
addClientH (req ::: usr ::: con ::: ip ::: _) = do
@@ -950,7 +951,12 @@ getClientCapabilitiesH :: UserId ::: ClientId ::: JSON -> Handler Response
getClientCapabilitiesH (uid ::: cid ::: _) = json <$> getClientCapabilities uid cid
getClientCapabilities :: UserId -> ClientId -> Handler Public.ClientCapabilityList
-getClientCapabilities uid cid = lift $ API.lookupLocalClientCapabilities uid cid
+getClientCapabilities uid cid = do
+ localdomain <- viewFederationDomain
+ ( API.lookupClient (Qualified uid localdomain) cid
+ >>= maybe (throwE ClientNotFound) (pure . Public.clientCapabilities)
+ )
+ !>> clientError
getRichInfoH :: UserId ::: UserId ::: JSON -> Handler Response
getRichInfoH (self ::: user ::: _) =
diff --git a/services/brig/src/Brig/API/Types.hs b/services/brig/src/Brig/API/Types.hs
index 2c6c4d8c48b..8241149efb8 100644
--- a/services/brig/src/Brig/API/Types.hs
+++ b/services/brig/src/Brig/API/Types.hs
@@ -195,6 +195,7 @@ data ClientError
| ClientLegalHoldCannotBeAdded
| ClientFederationError FederationError
| ClientCapabilitiesCannotBeRemoved
+ | ClientMissingLegalholdConsent
data RemoveIdentityError
= LastIdentity
diff --git a/services/brig/src/Brig/Data/Client.hs b/services/brig/src/Brig/Data/Client.hs
index 3455774c846..45b5631220e 100644
--- a/services/brig/src/Brig/Data/Client.hs
+++ b/services/brig/src/Brig/Data/Client.hs
@@ -26,9 +26,9 @@ module Brig.Data.Client
rmClient,
hasClient,
lookupClient,
- lookupClientCapabilities,
lookupClients,
lookupPubClientsBulk,
+ lookupClientsBulk,
lookupClientIds,
lookupUsersClientIds,
Brig.Data.Client.updateClientLabel,
@@ -78,7 +78,7 @@ import qualified System.CryptoBox as CryptoBox
import System.Logger.Class (field, msg, val)
import qualified System.Logger.Class as Log
import UnliftIO (pooledMapConcurrentlyN)
-import Wire.API.User.Client (ClientCapability)
+import Wire.API.User.Client (ClientCapability, ClientCapabilityList (ClientCapabilityList))
import Wire.API.UserMap (UserMap (..))
data ClientDataError
@@ -93,8 +93,9 @@ addClient ::
NewClient ->
Int ->
Maybe Location ->
+ Maybe (Imports.Set ClientCapability) ->
ExceptT ClientDataError AppIO (Client, [Client], Word)
-addClient u newId c maxPermClients loc = do
+addClient u newId c maxPermClients loc cps = do
clients <- lookupClients u
let typed = filter ((== newClientType c) . clientType) clients
let count = length typed
@@ -110,11 +111,16 @@ addClient u newId c maxPermClients loc = do
let old = maybe (filter (not . exists) typed) (const []) limit
return (new, old, total)
where
+ limit :: Maybe Int
limit = case newClientType c of
PermanentClientType -> Just maxPermClients
TemporaryClientType -> Nothing
LegalHoldClientType -> Nothing
+
+ exists :: Client -> Bool
exists = (==) newId . clientId
+
+ insert :: ExceptT ClientDataError AppIO Client
insert = do
-- Is it possible to do this somewhere else? Otherwise we could use `MonadClient` instead
now <- toUTCTimeMillis <$> (liftIO =<< view currentTime)
@@ -123,19 +129,25 @@ addClient u newId c maxPermClients loc = do
let lat = Latitude . view latitude <$> loc
lon = Longitude . view longitude <$> loc
mdl = newClientModel c
- prm = (u, newId, now, newClientType c, newClientLabel c, newClientClass c, newClientCookie c, lat, lon, mdl)
+ prm = (u, newId, now, newClientType c, newClientLabel c, newClientClass c, newClientCookie c, lat, lon, mdl, C.Set . Set.toList <$> cps)
retry x5 $ write insertClient (params Quorum prm)
- return $! Client newId (newClientType c) now (newClientClass c) (newClientLabel c) (newClientCookie c) loc mdl
+ return $! Client newId (newClientType c) now (newClientClass c) (newClientLabel c) (newClientCookie c) loc mdl (ClientCapabilityList $ fromMaybe mempty cps)
lookupClient :: MonadClient m => UserId -> ClientId -> m (Maybe Client)
lookupClient u c =
fmap toClient
<$> retry x1 (query1 selectClient (params Quorum (u, c)))
-lookupClientCapabilities :: MonadClient m => UserId -> ClientId -> m (Maybe (Imports.Set ClientCapability))
-lookupClientCapabilities u c =
- fmap (Set.fromList . C.fromSet . runIdentity)
- <$> retry x1 (query1 selectClientCapabilities (params Quorum (u, c)))
+lookupClientsBulk :: (MonadClient m) => [UserId] -> m (Map UserId (Imports.Set Client))
+lookupClientsBulk uids = liftClient $ do
+ userClientTuples <- pooledMapConcurrentlyN 50 getClientSetWithUser uids
+ pure $ Map.fromList userClientTuples
+ where
+ getClientSetWithUser :: MonadClient m => UserId -> m (UserId, Imports.Set Client)
+ getClientSetWithUser u = (u,) . Set.fromList <$> executeQuery u
+
+ executeQuery :: MonadClient m => UserId -> m [Client]
+ executeQuery u = toClient <$$> retry x1 (query selectClients (params Quorum (Identity u)))
lookupPubClientsBulk :: (MonadClient m) => [UserId] -> m (UserMap (Imports.Set PubClient))
lookupPubClientsBulk uids = liftClient $ do
@@ -238,8 +250,8 @@ claimPrekey u c =
-------------------------------------------------------------------------------
-- Queries
-insertClient :: PrepQuery W (UserId, ClientId, UTCTimeMillis, ClientType, Maybe Text, Maybe ClientClass, Maybe CookieLabel, Maybe Latitude, Maybe Longitude, Maybe Text) ()
-insertClient = "INSERT INTO clients (user, client, tstamp, type, label, class, cookie, lat, lon, model) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
+insertClient :: PrepQuery W (UserId, ClientId, UTCTimeMillis, ClientType, Maybe Text, Maybe ClientClass, Maybe CookieLabel, Maybe Latitude, Maybe Longitude, Maybe Text, Maybe (C.Set ClientCapability)) ()
+insertClient = "INSERT INTO clients (user, client, tstamp, type, label, class, cookie, lat, lon, model, capabilities) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
updateClientLabelQuery :: PrepQuery W (Maybe Text, UserId, ClientId) ()
updateClientLabelQuery = "UPDATE clients SET label = ? WHERE user = ? AND client = ?"
@@ -250,17 +262,14 @@ updateClientCapabilitiesQuery = "UPDATE clients SET capabilities = ? WHERE user
selectClientIds :: PrepQuery R (Identity UserId) (Identity ClientId)
selectClientIds = "SELECT client from clients where user = ?"
-selectClients :: PrepQuery R (Identity UserId) (ClientId, ClientType, UTCTimeMillis, Maybe Text, Maybe ClientClass, Maybe CookieLabel, Maybe Latitude, Maybe Longitude, Maybe Text)
-selectClients = "SELECT client, type, tstamp, label, class, cookie, lat, lon, model from clients where user = ?"
+selectClients :: PrepQuery R (Identity UserId) (ClientId, ClientType, UTCTimeMillis, Maybe Text, Maybe ClientClass, Maybe CookieLabel, Maybe Latitude, Maybe Longitude, Maybe Text, Maybe (C.Set ClientCapability))
+selectClients = "SELECT client, type, tstamp, label, class, cookie, lat, lon, model, capabilities from clients where user = ?"
selectPubClients :: PrepQuery R (Identity UserId) (ClientId, Maybe ClientClass)
selectPubClients = "SELECT client, class from clients where user = ?"
-selectClient :: PrepQuery R (UserId, ClientId) (ClientId, ClientType, UTCTimeMillis, Maybe Text, Maybe ClientClass, Maybe CookieLabel, Maybe Latitude, Maybe Longitude, Maybe Text)
-selectClient = "SELECT client, type, tstamp, label, class, cookie, lat, lon, model from clients where user = ? and client = ?"
-
-selectClientCapabilities :: PrepQuery R (UserId, ClientId) (Identity (C.Set ClientCapability))
-selectClientCapabilities = "SELECT capabilities from clients where user = ? and client = ?"
+selectClient :: PrepQuery R (UserId, ClientId) (ClientId, ClientType, UTCTimeMillis, Maybe Text, Maybe ClientClass, Maybe CookieLabel, Maybe Latitude, Maybe Longitude, Maybe Text, Maybe (C.Set ClientCapability))
+selectClient = "SELECT client, type, tstamp, label, class, cookie, lat, lon, model, capabilities from clients where user = ? and client = ?"
insertClientKey :: PrepQuery W (UserId, ClientId, PrekeyId, Text) ()
insertClientKey = "INSERT INTO prekeys (user, client, key, data) VALUES (?, ?, ?, ?)"
@@ -289,8 +298,8 @@ checkClient = "SELECT client from clients where user = ? and client = ?"
-------------------------------------------------------------------------------
-- Conversions
-toClient :: (ClientId, ClientType, UTCTimeMillis, Maybe Text, Maybe ClientClass, Maybe CookieLabel, Maybe Latitude, Maybe Longitude, Maybe Text) -> Client
-toClient (cid, cty, tme, lbl, cls, cok, lat, lon, mdl) =
+toClient :: (ClientId, ClientType, UTCTimeMillis, Maybe Text, Maybe ClientClass, Maybe CookieLabel, Maybe Latitude, Maybe Longitude, Maybe Text, Maybe (C.Set ClientCapability)) -> Client
+toClient (cid, cty, tme, lbl, cls, cok, lat, lon, mdl, cps) =
Client
{ clientId = cid,
clientType = cty,
@@ -299,7 +308,8 @@ toClient (cid, cty, tme, lbl, cls, cok, lat, lon, mdl) =
clientLabel = lbl,
clientCookie = cok,
clientLocation = location <$> lat <*> lon,
- clientModel = mdl
+ clientModel = mdl,
+ clientCapabilities = ClientCapabilityList $ maybe Set.empty (Set.fromList . C.fromSet) cps
}
toPubClient :: (ClientId, Maybe ClientClass) -> PubClient
diff --git a/services/brig/src/Brig/Data/Connection.hs b/services/brig/src/Brig/Data/Connection.hs
index c020f779fa6..1988b94a5a3 100644
--- a/services/brig/src/Brig/Data/Connection.hs
+++ b/services/brig/src/Brig/Data/Connection.hs
@@ -23,8 +23,10 @@ module Brig.Data.Connection
insertConnection,
updateConnection,
lookupConnection,
+ lookupRelationWithHistory,
lookupConnections,
lookupConnectionStatus,
+ lookupConnectionStatus',
lookupContactList,
lookupContactListWithRelation,
countConnections,
@@ -46,6 +48,7 @@ import Data.Range
import Data.Time (getCurrentTime)
import Imports
import UnliftIO.Async (pooledMapConcurrentlyN_)
+import Wire.API.Connection
connectUsers :: UserId -> [(UserId, ConvId)] -> AppIO [UserConnection]
connectUsers from to = do
@@ -54,8 +57,8 @@ connectUsers from to = do
setType BatchLogged
setConsistency Quorum
forM_ to $ \(u, c) -> do
- addPrepQuery connectionInsert (from, u, Accepted, now, Nothing, c)
- addPrepQuery connectionInsert (u, from, Accepted, now, Nothing, c)
+ addPrepQuery connectionInsert (from, u, AcceptedWithHistory, now, Nothing, c)
+ addPrepQuery connectionInsert (u, from, AcceptedWithHistory, now, Nothing, c)
return . concat . (`map` to) $ \(u, c) ->
[ UserConnection from u Accepted now Nothing (Just c),
UserConnection u from Accepted now Nothing (Just c)
@@ -66,7 +69,7 @@ insertConnection ::
UserId ->
-- | To
UserId ->
- Relation ->
+ RelationWithHistory ->
Maybe Message ->
ConvId ->
AppIO UserConnection
@@ -75,13 +78,13 @@ insertConnection from to status msg cid = do
retry x5 . write connectionInsert $ params Quorum (from, to, status, now, msg, cid)
return $ toUserConnection (from, to, status, now, msg, Just cid)
-updateConnection :: UserConnection -> Relation -> AppIO UserConnection
+updateConnection :: UserConnection -> RelationWithHistory -> AppIO UserConnection
updateConnection c@UserConnection {..} status = do
now <- toUTCTimeMillis <$> liftIO getCurrentTime
retry x5 . write connectionUpdate $ params Quorum (status, now, ucFrom, ucTo)
return $
c
- { ucStatus = status,
+ { ucStatus = relationDropHistory status,
ucLastUpdate = now
}
@@ -96,6 +99,17 @@ lookupConnection from to =
liftM toUserConnection
<$> retry x1 (query1 connectionSelect (params Quorum (from, to)))
+-- | 'lookupConnection' with more 'Relation' info.
+lookupRelationWithHistory ::
+ -- | User 'A'
+ UserId ->
+ -- | User 'B'
+ UserId ->
+ AppIO (Maybe RelationWithHistory)
+lookupRelationWithHistory from to =
+ liftM runIdentity
+ <$> retry x1 (query1 relationSelect (params Quorum (from, to)))
+
-- | For a given user 'A', lookup his outgoing connections (A -> X) to other users.
lookupConnections :: UserId -> Maybe UserId -> Range 1 500 Int32 -> AppIO (ResultPage UserConnection)
lookupConnections from start (fromRange -> size) =
@@ -112,27 +126,35 @@ lookupConnectionStatus from to =
map toConnectionStatus
<$> retry x1 (query connectionStatusSelect (params Quorum (from, to)))
+-- | Lookup all relations between two sets of users (cartesian product).
+lookupConnectionStatus' :: [UserId] -> AppIO [ConnectionStatus]
+lookupConnectionStatus' from =
+ map toConnectionStatus
+ <$> retry x1 (query connectionStatusSelect' (params Quorum (Identity from)))
+
-- | See 'lookupContactListWithRelation'.
lookupContactList :: UserId -> AppIO [UserId]
lookupContactList u =
- fst <$$> (filter ((== Accepted) . snd) <$> lookupContactListWithRelation u)
+ fst <$$> (filter ((== AcceptedWithHistory) . snd) <$> lookupContactListWithRelation u)
-- | For a given user 'A', lookup the list of users that form his contact list,
-- i.e. the users to whom 'A' has an outgoing 'Accepted' relation (A -> B).
-lookupContactListWithRelation :: UserId -> AppIO [(UserId, Relation)]
+lookupContactListWithRelation :: UserId -> AppIO [(UserId, RelationWithHistory)]
lookupContactListWithRelation u =
retry x1 (query contactsSelect (params Quorum (Identity u)))
-- | Count the number of connections a user has in a specific relation status.
+-- (If you want to distinguish 'RelationWithHistory', write a new function.)
-- Note: The count is eventually consistent.
countConnections :: UserId -> [Relation] -> AppIO Int64
countConnections u r = do
rels <- retry x1 . query selectStatus $ params One (Identity u)
return $ foldl' count 0 rels
where
- selectStatus :: QueryString R (Identity UserId) (Identity Relation)
+ selectStatus :: QueryString R (Identity UserId) (Identity RelationWithHistory)
selectStatus = "SELECT status FROM connection WHERE left = ?"
- count n (Identity s) | s `elem` r = n + 1
+
+ count n (Identity s) | (relationDropHistory s) `elem` r = n + 1
count n _ = n
deleteConnections :: UserId -> AppIO ()
@@ -146,25 +168,31 @@ deleteConnections u = do
-- Queries
-connectionInsert :: PrepQuery W (UserId, UserId, Relation, UTCTimeMillis, Maybe Message, ConvId) ()
+connectionInsert :: PrepQuery W (UserId, UserId, RelationWithHistory, UTCTimeMillis, Maybe Message, ConvId) ()
connectionInsert = "INSERT INTO connection (left, right, status, last_update, message, conv) VALUES (?, ?, ?, ?, ?, ?)"
-connectionUpdate :: PrepQuery W (Relation, UTCTimeMillis, UserId, UserId) ()
+connectionUpdate :: PrepQuery W (RelationWithHistory, UTCTimeMillis, UserId, UserId) ()
connectionUpdate = "UPDATE connection SET status = ?, last_update = ? WHERE left = ? AND right = ?"
-connectionSelect :: PrepQuery R (UserId, UserId) (UserId, UserId, Relation, UTCTimeMillis, Maybe Message, Maybe ConvId)
+connectionSelect :: PrepQuery R (UserId, UserId) (UserId, UserId, RelationWithHistory, UTCTimeMillis, Maybe Message, Maybe ConvId)
connectionSelect = "SELECT left, right, status, last_update, message, conv FROM connection WHERE left = ? AND right = ?"
-connectionStatusSelect :: PrepQuery R ([UserId], [UserId]) (UserId, UserId, Relation)
+relationSelect :: PrepQuery R (UserId, UserId) (Identity RelationWithHistory)
+relationSelect = "SELECT status FROM connection WHERE left = ? AND right = ?"
+
+connectionStatusSelect :: PrepQuery R ([UserId], [UserId]) (UserId, UserId, RelationWithHistory)
connectionStatusSelect = "SELECT left, right, status FROM connection WHERE left IN ? AND right IN ?"
-contactsSelect :: PrepQuery R (Identity UserId) (UserId, Relation)
+connectionStatusSelect' :: PrepQuery R (Identity [UserId]) (UserId, UserId, RelationWithHistory)
+connectionStatusSelect' = "SELECT left, right, status FROM connection WHERE left IN ?"
+
+contactsSelect :: PrepQuery R (Identity UserId) (UserId, RelationWithHistory)
contactsSelect = "SELECT right, status FROM connection WHERE left = ?"
-connectionsSelect :: PrepQuery R (Identity UserId) (UserId, UserId, Relation, UTCTimeMillis, Maybe Message, Maybe ConvId)
+connectionsSelect :: PrepQuery R (Identity UserId) (UserId, UserId, RelationWithHistory, UTCTimeMillis, Maybe Message, Maybe ConvId)
connectionsSelect = "SELECT left, right, status, last_update, message, conv FROM connection WHERE left = ? ORDER BY right ASC"
-connectionsSelectFrom :: PrepQuery R (UserId, UserId) (UserId, UserId, Relation, UTCTimeMillis, Maybe Message, Maybe ConvId)
+connectionsSelectFrom :: PrepQuery R (UserId, UserId) (UserId, UserId, RelationWithHistory, UTCTimeMillis, Maybe Message, Maybe ConvId)
connectionsSelectFrom = "SELECT left, right, status, last_update, message, conv FROM connection WHERE left = ? AND right > ? ORDER BY right ASC"
connectionDelete :: PrepQuery W (UserId, UserId) ()
@@ -175,8 +203,8 @@ connectionClear = "DELETE FROM connection WHERE left = ?"
-- Conversions
-toUserConnection :: (UserId, UserId, Relation, UTCTimeMillis, Maybe Message, Maybe ConvId) -> UserConnection
-toUserConnection (l, r, rel, time, msg, cid) = UserConnection l r rel time msg cid
+toUserConnection :: (UserId, UserId, RelationWithHistory, UTCTimeMillis, Maybe Message, Maybe ConvId) -> UserConnection
+toUserConnection (l, r, relationDropHistory -> rel, time, msg, cid) = UserConnection l r rel time msg cid
-toConnectionStatus :: (UserId, UserId, Relation) -> ConnectionStatus
-toConnectionStatus (l, r, rel) = ConnectionStatus l r rel
+toConnectionStatus :: (UserId, UserId, RelationWithHistory) -> ConnectionStatus
+toConnectionStatus (l, r, relationDropHistory -> rel) = ConnectionStatus l r rel
diff --git a/services/brig/src/Brig/Data/Instances.hs b/services/brig/src/Brig/Data/Instances.hs
index aee068550ec..b2822916233 100644
--- a/services/brig/src/Brig/Data/Instances.hs
+++ b/services/brig/src/Brig/Data/Instances.hs
@@ -37,6 +37,7 @@ import Data.Range ()
import Data.String.Conversions (LBS, ST, cs)
import Data.Text.Ascii ()
import Imports
+import Wire.API.Connection (RelationWithHistory (..))
import Wire.API.User.RichInfo
deriving instance Cql Name
@@ -83,27 +84,37 @@ instance Cql UserSSOId where
toCql = toCql . cs @LBS @ST . encode
-instance Cql Relation where
+instance Cql RelationWithHistory where
ctype = Tagged IntColumn
fromCql (CqlInt i) = case i of
- 0 -> return Accepted
- 1 -> return Blocked
- 2 -> return Pending
- 3 -> return Ignored
- 4 -> return Sent
- 5 -> return Cancelled
- 6 -> return MissingLegalholdConsent
- n -> Left $ "unexpected relation: " ++ show n
- fromCql _ = Left "relation: int expected"
-
- toCql Accepted = CqlInt 0
- toCql Blocked = CqlInt 1
- toCql Pending = CqlInt 2
- toCql Ignored = CqlInt 3
- toCql Sent = CqlInt 4
- toCql Cancelled = CqlInt 5
- toCql MissingLegalholdConsent = CqlInt 6
+ 0 -> pure AcceptedWithHistory
+ 1 -> pure BlockedWithHistory
+ 2 -> pure PendingWithHistory
+ 3 -> pure IgnoredWithHistory
+ 4 -> pure SentWithHistory
+ 5 -> pure CancelledWithHistory
+ 6 -> pure MissingLegalholdConsentFromAccepted
+ 7 -> pure MissingLegalholdConsentFromBlocked
+ 8 -> pure MissingLegalholdConsentFromPending
+ 9 -> pure MissingLegalholdConsentFromIgnored
+ 10 -> pure MissingLegalholdConsentFromSent
+ 11 -> pure MissingLegalholdConsentFromCancelled
+ n -> Left $ "unexpected RelationWithHistory: " ++ show n
+ fromCql _ = Left "RelationWithHistory: int expected"
+
+ toCql AcceptedWithHistory = CqlInt 0
+ toCql BlockedWithHistory = CqlInt 1
+ toCql PendingWithHistory = CqlInt 2
+ toCql IgnoredWithHistory = CqlInt 3
+ toCql SentWithHistory = CqlInt 4
+ toCql CancelledWithHistory = CqlInt 5
+ toCql MissingLegalholdConsentFromAccepted = CqlInt 6
+ toCql MissingLegalholdConsentFromBlocked = CqlInt 7
+ toCql MissingLegalholdConsentFromPending = CqlInt 8
+ toCql MissingLegalholdConsentFromIgnored = CqlInt 9
+ toCql MissingLegalholdConsentFromSent = CqlInt 10
+ toCql MissingLegalholdConsentFromCancelled = CqlInt 11
-- DEPRECATED
instance Cql Pict where
diff --git a/services/brig/src/Brig/IO/Intra.hs b/services/brig/src/Brig/IO/Intra.hs
index 88098be8b48..4b7e4361e88 100644
--- a/services/brig/src/Brig/IO/Intra.hs
+++ b/services/brig/src/Brig/IO/Intra.hs
@@ -56,12 +56,16 @@ module Brig.IO.Intra
getTeamLegalHoldStatus,
changeTeamStatus,
getTeamSearchVisibility,
+
+ -- * Legalhold
+ guardLegalhold,
)
where
import Bilge hiding (head, options, requestId)
import Bilge.RPC
import Bilge.Retry
+import Brig.API.Error (internalServerError)
import Brig.API.Types
import Brig.App
import Brig.Data.Connection (lookupContactList)
@@ -70,7 +74,10 @@ import Brig.RPC
import Brig.Types
import Brig.Types.User.Event
import qualified Brig.User.Search.Index as Search
+import Control.Error (ExceptT)
import Control.Lens (view, (.~), (?~), (^.))
+import Control.Monad.Catch (MonadThrow (throwM))
+import Control.Monad.Trans.Except (throwE)
import Control.Retry
import Data.Aeson hiding (json)
import Data.ByteString.Conversion
@@ -86,6 +93,7 @@ import Data.Range
import qualified Data.Set as Set
import Galley.Types (Connect (..), Conversation)
import qualified Galley.Types.Teams as Team
+import Galley.Types.Teams.Intra (GuardLegalholdPolicyConflicts (GuardLegalholdPolicyConflicts))
import qualified Galley.Types.Teams.Intra as Team
import qualified Galley.Types.Teams.SearchVisibility as Team
import Gundeck.Types.Push.V2
@@ -95,7 +103,9 @@ import Network.HTTP.Types.Method
import Network.HTTP.Types.Status
import qualified Network.Wai.Utilities.Error as Wai
import System.Logger.Class as Log hiding (name, (.=))
+import Wire.API.Message (UserClients)
import Wire.API.Team.Feature (TeamFeatureName (..), TeamFeatureStatus)
+import Wire.API.Team.LegalHold (LegalholdProtectee)
-----------------------------------------------------------------------------
-- Event Handlers
@@ -882,3 +892,17 @@ changeTeamStatus tid s cur = do
. header "Content-Type" "application/json"
. expect2xx
. lbytes (encode $ Team.TeamStatusUpdate s cur)
+
+guardLegalhold :: LegalholdProtectee -> UserClients -> ExceptT ClientError AppIO ()
+guardLegalhold protectee userClients = do
+ res <- lift $ galleyRequest PUT req
+ case Bilge.statusCode res of
+ 200 -> pure ()
+ 412 -> throwE ClientMissingLegalholdConsent
+ 404 -> pure () -- allow for galley not to be ready, so the set of valid deployment orders is non-empty.
+ _ -> throwM internalServerError
+ where
+ req =
+ paths ["i", "guard-legalhold-policy-conflicts"]
+ . header "Content-Type" "application/json"
+ . lbytes (encode $ GuardLegalholdPolicyConflicts protectee userClients)
diff --git a/services/brig/src/Brig/Provider/API.hs b/services/brig/src/Brig/Provider/API.hs
index 0525de32b51..d7735b1f38a 100644
--- a/services/brig/src/Brig/Provider/API.hs
+++ b/services/brig/src/Brig/Provider/API.hs
@@ -65,7 +65,7 @@ import Data.LegalHold
import qualified Data.List as List
import Data.List1 (maybeList1)
import qualified Data.Map.Strict as Map
-import Data.Misc (Fingerprint (..), Rsa)
+import Data.Misc (Fingerprint (..), FutureWork (FutureWork), Rsa)
import Data.Predicate
import Data.Qualified
import Data.Range
@@ -101,8 +101,9 @@ import qualified Wire.API.Provider as Public
import qualified Wire.API.Provider.Bot as Public (BotUserView)
import qualified Wire.API.Provider.Service as Public
import qualified Wire.API.Provider.Service.Tag as Public
+import Wire.API.Team.LegalHold (LegalholdProtectee (UnprotectedBot))
import qualified Wire.API.User as Public (UserProfile, publicProfile)
-import qualified Wire.API.User.Client as Public (Client, PubClient (..), UserClientPrekeyMap, UserClients, userClients)
+import qualified Wire.API.User.Client as Public (Client, ClientCapability (ClientSupportsLegalholdImplicitConsent), PubClient (..), UserClientPrekeyMap, UserClients, userClients)
import qualified Wire.API.User.Client.Prekey as Public (PrekeyId)
import qualified Wire.API.User.Identity as Public (Email)
@@ -857,8 +858,12 @@ addBot zuid zcon cid add = do
}
lift $ User.insertAccount (UserAccount usr Active) (Just (cid, cnvTeam cnv)) Nothing True
maxPermClients <- fromMaybe Opt.defUserMaxPermClients <$> Opt.setUserMaxPermClients <$> view settings
- (clt, _, _) <-
- User.addClient (botUserId bid) bcl newClt maxPermClients Nothing
+ (clt, _, _) <- do
+ _ <- do
+ -- if we want to protect bots against lh, 'addClient' cannot just send lh capability
+ -- implicitly in the next line.
+ pure $ FutureWork @'UnprotectedBot undefined
+ User.addClient (botUserId bid) bcl newClt maxPermClients Nothing (Just $ Set.singleton Public.ClientSupportsLegalholdImplicitConsent)
!>> const (StdError badGateway) -- MalformedPrekeys
-- Add the bot to the conversation
@@ -944,7 +949,7 @@ botClaimUsersPrekeys body = do
maxSize <- fromIntegral . setMaxConvSize <$> view settings
when (Map.size (Public.userClients body) > maxSize) $
throwStd tooManyClients
- lift (Client.claimLocalMultiPrekeyBundles body)
+ Client.claimLocalMultiPrekeyBundles UnprotectedBot body !>> clientError
botListUserProfilesH :: List UserId -> Handler Response
botListUserProfilesH uids = do
@@ -1086,31 +1091,31 @@ rangeChecked :: Within a n m => a -> Handler (Range n m a)
rangeChecked = either (throwStd . invalidRange . fromString) return . checkedEither
invalidServiceKey :: Wai.Error
-invalidServiceKey = Wai.Error status400 "invalid-service-key" "Invalid service key."
+invalidServiceKey = Wai.mkError status400 "invalid-service-key" "Invalid service key."
invalidProvider :: Wai.Error
-invalidProvider = Wai.Error status403 "invalid-provider" "The provider does not exist."
+invalidProvider = Wai.mkError status403 "invalid-provider" "The provider does not exist."
invalidBot :: Wai.Error
-invalidBot = Wai.Error status403 "invalid-bot" "The targeted user is not a bot."
+invalidBot = Wai.mkError status403 "invalid-bot" "The targeted user is not a bot."
invalidConv :: Wai.Error
-invalidConv = Wai.Error status403 "invalid-conversation" "The operation is not allowed in this conversation."
+invalidConv = Wai.mkError status403 "invalid-conversation" "The operation is not allowed in this conversation."
badGateway :: Wai.Error
-badGateway = Wai.Error status502 "bad-gateway" "The upstream service returned an invalid response."
+badGateway = Wai.mkError status502 "bad-gateway" "The upstream service returned an invalid response."
tooManyMembers :: Wai.Error
-tooManyMembers = Wai.Error status403 "too-many-members" "Maximum number of members per conversation reached."
+tooManyMembers = Wai.mkError status403 "too-many-members" "Maximum number of members per conversation reached."
tooManyBots :: Wai.Error
-tooManyBots = Wai.Error status409 "too-many-bots" "Maximum number of bots for the service reached."
+tooManyBots = Wai.mkError status409 "too-many-bots" "Maximum number of bots for the service reached."
serviceDisabled :: Wai.Error
-serviceDisabled = Wai.Error status403 "service-disabled" "The desired service is currently disabled."
+serviceDisabled = Wai.mkError status403 "service-disabled" "The desired service is currently disabled."
serviceNotWhitelisted :: Wai.Error
-serviceNotWhitelisted = Wai.Error status403 "service-not-whitelisted" "The desired service is not on the whitelist of allowed services for this team."
+serviceNotWhitelisted = Wai.mkError status403 "service-not-whitelisted" "The desired service is not on the whitelist of allowed services for this team."
serviceError :: RPC.ServiceError -> Wai.Error
serviceError RPC.ServiceUnavailable = badGateway
diff --git a/services/brig/src/Brig/User/EJPD.hs b/services/brig/src/Brig/User/EJPD.hs
index 706f90c3f16..f41d885adeb 100644
--- a/services/brig/src/Brig/User/EJPD.hs
+++ b/services/brig/src/Brig/User/EJPD.hs
@@ -35,7 +35,7 @@ import Data.Id (UserId)
import qualified Data.Set as Set
import Imports hiding (head)
import Servant.Swagger.Internal.Orphans ()
-import Wire.API.Connection (Relation)
+import Wire.API.Connection (Relation, RelationWithHistory (..), relationDropHistory)
import qualified Wire.API.Push.Token as PushTok
import qualified Wire.API.Team.Member as Team
import Wire.API.User (User, userDisplayName, userEmail, userHandle, userId, userPhone, userTeam)
@@ -62,11 +62,11 @@ ejpdRequest includeContacts (EJPDRequestBody handles) = do
mbContacts <-
if includeContacts'
then do
- contacts :: [(UserId, Relation)] <-
+ contacts :: [(UserId, RelationWithHistory)] <-
Conn.lookupContactListWithRelation uid
contactsFull :: [Maybe (Relation, EJPDResponseItem)] <-
- forM contacts $ \(uid', rel) -> do
+ forM contacts $ \(uid', relationDropHistory -> rel) -> do
mbUsr <- lookupUser NoPendingInvitations uid'
maybe (pure Nothing) (\usr -> Just . (rel,) <$> go2 False usr) mbUsr
diff --git a/services/brig/test/integration/API/Provider.hs b/services/brig/test/integration/API/Provider.hs
index 77b6bfc386f..574e5445fc1 100644
--- a/services/brig/test/integration/API/Provider.hs
+++ b/services/brig/test/integration/API/Provider.hs
@@ -46,6 +46,7 @@ import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as C8
import Data.ByteString.Conversion
import qualified Data.ByteString.Lazy.Char8 as LC8
+import Data.Domain
import Data.Handle (Handle (Handle))
import qualified Data.HashMap.Strict as HashMap
import Data.Id hiding (client)
@@ -86,8 +87,8 @@ import Test.Tasty.HUnit
import Util
import Web.Cookie (SetCookie (..), parseSetCookie)
-tests :: Config -> Manager -> DB.ClientState -> Brig -> Cannon -> Galley -> IO TestTree
-tests conf p db b c g = do
+tests :: Domain -> Config -> Manager -> DB.ClientState -> Brig -> Cannon -> Galley -> IO TestTree
+tests dom conf p db b c g = do
return $
testGroup
"provider"
@@ -123,7 +124,7 @@ tests conf p db b c g = do
test p "search honors enabling and whitelisting" $
testSearchWhitelistHonorUpdates conf db b,
test p "de-whitelisted bots are removed" $
- testWhitelistKickout conf db b g c,
+ testWhitelistKickout dom conf db b g c,
test p "de-whitelisting works with deleted conversations" $
testDeWhitelistDeletedConv conf db b g c
],
@@ -488,17 +489,22 @@ testDeleteService config db brig galley cannon = withTestService config db brig
u1 <- createUser "Ernie" brig
u2 <- createUser "Bert" brig
let uid1 = userId u1
- let uid2 = userId u2
+ quid1 = userQualifiedId u1
+ localDomain = qDomain quid1
+ uid2 = userId u2
postConnection brig uid1 uid2 !!! const 201 === statusCode
putConnection brig uid2 uid1 Accepted !!! const 200 === statusCode
cnv <- responseJsonError =<< (createConv galley uid1 [uid2] do
deleteService brig pid sid defProviderPassword
@@ -507,8 +513,8 @@ testDeleteService config db brig galley cannon = withTestService config db brig
_ <- waitFor (5 # Second) not (isMember galley buid2 cid)
getBotConv galley bid1 cid !!! const 404 === statusCode
getBotConv galley bid2 cid !!! const 404 === statusCode
- wsAssertMemberLeave ws cid buid1 [buid1]
- wsAssertMemberLeave ws cid buid2 [buid2]
+ wsAssertMemberLeave ws qcid qbuid1 [buid1]
+ wsAssertMemberLeave ws qcid qbuid2 [buid2]
-- The service should not be available
getService brig pid sid
!!! const 404 === statusCode
@@ -523,7 +529,9 @@ testAddRemoveBot config db brig galley cannon = withTestService config db brig d
u1 <- createUser "Ernie" brig
u2 <- createUser "Bert" brig
let uid1 = userId u1
- let uid2 = userId u2
+ quid1 = userQualifiedId u1
+ localDomain = qDomain quid1
+ uid2 = userId u2
h <- randomHandle
putHandle brig uid1 h !!! const 200 === statusCode
postConnection brig uid1 uid2 !!! const 201 === statusCode
@@ -532,7 +540,7 @@ testAddRemoveBot config db brig galley cannon = withTestService config db brig d
_rs <- createConv galley uid1 [uid2] DB.ClientState -> Brig -> Galley -> Cannon -> Http ()
testMessageBot config db brig galley cannon = withTestService config db brig defServiceApp $ \sref buf -> do
@@ -577,17 +585,22 @@ testAddRemoveBotTeam :: Config -> DB.ClientState -> Brig -> Galley -> Cannon ->
testAddRemoveBotTeam config db brig galley cannon = withTestService config db brig defServiceApp $ \sref buf -> do
(u1, u2, h, tid, cid, pid, sid) <- prepareBotUsersTeam brig galley sref
let (uid1, uid2) = (userId u1, userId u2)
+ quid1 = userQualifiedId u1
+ localDomain = qDomain quid1
-- Ensure cannot add bots to managed conversations
cidFail <- Team.createManagedConv galley tid uid1 [uid2] Nothing
addBot brig uid1 pid sid cidFail !!! do
const 403 === statusCode
const (Just "invalid-conversation") === fmap Error.label . responseJsonMaybe
- testAddRemoveBotUtil pid sid cid u1 u2 h sref buf brig galley cannon
+ testAddRemoveBotUtil localDomain pid sid cid u1 u2 h sref buf brig galley cannon
testBotTeamOnlyConv :: Config -> DB.ClientState -> Brig -> Galley -> Cannon -> Http ()
testBotTeamOnlyConv config db brig galley cannon = withTestService config db brig defServiceApp $ \sref buf -> do
(u1, u2, _h, _tid, cid, pid, sid) <- prepareBotUsersTeam brig galley sref
let (uid1, uid2) = (userId u1, userId u2)
+ quid1 = userQualifiedId u1
+ localDomain = qDomain quid1
+ qcid = Qualified cid localDomain
-- Make the conversation team-only and check that the bot can't be added
-- to the conversation
setAccessRole uid1 cid TeamAccessRole
@@ -596,8 +609,9 @@ testBotTeamOnlyConv config db brig galley cannon = withTestService config db bri
const (Just "invalid-conversation") === fmap Error.label . responseJsonMaybe
-- Make the conversation allowed for guests and add the bot successfully
setAccessRole uid1 cid NonActivatedAccessRole
- bid <- addBotConv brig cannon uid1 uid2 cid pid sid buf
+ bid <- addBotConv localDomain brig cannon uid1 uid2 cid pid sid buf
let buid = botUserId bid
+ qbuid = Qualified buid localDomain
-- Make the conversation team-only again and check that the bot has been removed
WS.bracketR cannon uid1 $ \ws -> do
setAccessRole uid1 cid TeamAccessRole
@@ -606,11 +620,11 @@ testBotTeamOnlyConv config db brig galley cannon = withTestService config db bri
!!! const 404 === statusCode
svcAssertConvAccessUpdate
buf
- uid1
+ quid1
(ConversationAccessUpdate [InviteAccess] TeamAccessRole)
- cid
- svcAssertMemberLeave buf buid [buid] cid
- wsAssertMemberLeave ws cid buid [buid]
+ qcid
+ svcAssertMemberLeave buf qbuid [buid] qcid
+ wsAssertMemberLeave ws qcid qbuid [buid]
where
setAccessRole uid cid role =
updateConversationAccess galley uid cid [InviteAccess] role
@@ -637,16 +651,19 @@ testDeleteConvBotTeam config db brig galley cannon = withTestService config db b
-- Prepare users and the bot
(u1, u2, _, tid, cid, pid, sid) <- prepareBotUsersTeam brig galley sref
let (uid1, uid2) = (userId u1, userId u2)
- bid <- addBotConv brig cannon uid1 uid2 cid pid sid buf
+ quid2 = userQualifiedId u2
+ localDomain = qDomain quid2
+ qcid = Qualified cid localDomain
+ bid <- addBotConv localDomain brig cannon uid1 uid2 cid pid sid buf
-- Delete the conversation and check that everyone is notified
-- via an event, including the bot itself.
WS.bracketR2 cannon uid1 uid2 $ \wss -> do
-- 200 response on success
Team.deleteTeamConv galley tid cid uid2
-- Events for the users
- forM_ wss $ \ws -> wsAssertConvDelete ws cid uid2
+ forM_ wss $ \ws -> wsAssertConvDelete ws qcid quid2
-- Event for the bot
- svcAssertConvDelete buf uid2 cid
+ svcAssertConvDelete buf quid2 qcid
-- Check that the conversation no longer exists
forM_ [uid1, uid2] $ \uid ->
getConversation galley uid cid !!! const 404 === statusCode
@@ -657,7 +674,10 @@ testDeleteTeamBotTeam config db brig galley cannon = withTestService config db b
-- Prepare users and the bot
(u1, u2, _, tid, cid, pid, sid) <- prepareBotUsersTeam brig galley sref
let (uid1, uid2) = (userId u1, userId u2)
- bid <- addBotConv brig cannon uid1 uid2 cid pid sid buf
+ quid1 = userQualifiedId u1
+ localDomain = qDomain quid1
+ qcid = Qualified cid localDomain
+ bid <- addBotConv localDomain brig cannon uid1 uid2 cid pid sid buf
-- Delete the team, and check that the bot (eventually)
-- receives a notification via event
Team.deleteTeam galley tid uid1
@@ -665,7 +685,7 @@ testDeleteTeamBotTeam config db brig galley cannon = withTestService config db b
-- events may or may not be sent (for instance, team members)
-- leaving a conversation. Thus, we check _only_ for the relevant
-- ones for the bot, which are the ConvDelete event
- svcAssertEventuallyConvDelete buf uid1 cid
+ svcAssertEventuallyConvDelete buf quid1 qcid
-- Wait until all users have been deleted (can take a while)
forM_ [uid1, uid2] $ \uid -> do
void $ retryWhileN 20 (/= Intra.Deleted) (getStatus brig uid)
@@ -850,11 +870,13 @@ testWhitelistBasic config db brig galley =
disableService brig pid sid
whitelistService brig owner tid pid sid
-testWhitelistKickout :: Config -> DB.ClientState -> Brig -> Galley -> Cannon -> Http ()
-testWhitelistKickout config db brig galley cannon = do
+testWhitelistKickout :: Domain -> Config -> DB.ClientState -> Brig -> Galley -> Cannon -> Http ()
+testWhitelistKickout localDomain config db brig galley cannon = do
-- Create a team and a conversation
(owner, tid) <- Team.createUserWithTeam brig
+ let qowner = Qualified owner localDomain
cid <- Team.createTeamConv galley tid owner [] Nothing
+ let qcid = Qualified cid localDomain
-- Create a service
withTestService config db brig defServiceApp $ \sref buf -> do
-- Add it to the conversation
@@ -866,16 +888,17 @@ testWhitelistKickout config db brig galley cannon = do
=<< (addBot brig owner pid sid cid do
dewhitelistService brig owner tid pid sid
_ <- waitFor (2 # Second) not (isMember galley buid cid)
getBotConv galley bid cid
!!! const 404 === statusCode
- wsAssertMemberLeave ws cid owner [buid]
- svcAssertMemberLeave buf owner [buid] cid
+ wsAssertMemberLeave ws qcid qowner [buid]
+ svcAssertMemberLeave buf qowner [buid] qcid
-- The bot should not get any further events
liftIO $
timeout (2 # Second) (readChan buf) >>= \case
@@ -891,8 +914,10 @@ testDeWhitelistDeletedConv config db brig galley cannon = do
(u1, u2, _h, tid, cid, pid, sid) <- prepareBotUsersTeam brig galley sref
let uid1 = userId u1
uid2 = userId u2
+ quid1 = userQualifiedId u1
+ localDomain = qDomain quid1
-- Add a bot there
- _bid1 <- addBotConv brig cannon uid1 uid2 cid pid sid buf
+ _bid1 <- addBotConv localDomain brig cannon uid1 uid2 cid pid sid buf
-- Delete conversation (to ensure deleteService can be called even with a deleted conversation)
Team.deleteTeamConv galley tid cid uid1
-- De-whitelist the service
@@ -1252,7 +1277,7 @@ createConv g u us =
. contentJson
. body (RequestBodyLBS (encode (NewConvUnmanaged conv)))
where
- conv = NewConv us Nothing Set.empty Nothing Nothing Nothing Nothing roleNameWireAdmin
+ conv = NewConv us [] Nothing Set.empty Nothing Nothing Nothing Nothing roleNameWireAdmin
postMessage ::
Galley ->
@@ -1647,7 +1672,7 @@ defServiceApp buf =
writeChan buf (TestBotMessage ev)
k $ responseLBS status200 [] "success"
-wsAssertMemberJoin :: MonadIO m => WS.WebSocket -> ConvId -> UserId -> [UserId] -> m ()
+wsAssertMemberJoin :: MonadIO m => WS.WebSocket -> Qualified ConvId -> Qualified UserId -> [Qualified UserId] -> m ()
wsAssertMemberJoin ws conv usr new = void $
liftIO $
WS.assertMatch (5 # Second) ws $
@@ -1659,7 +1684,7 @@ wsAssertMemberJoin ws conv usr new = void $
evtFrom e @?= usr
evtData e @?= EdMembersJoin (SimpleMembers (fmap (\u -> SimpleMember u roleNameWireAdmin) new))
-wsAssertMemberLeave :: MonadIO m => WS.WebSocket -> ConvId -> UserId -> [UserId] -> m ()
+wsAssertMemberLeave :: MonadIO m => WS.WebSocket -> Qualified ConvId -> Qualified UserId -> [UserId] -> m ()
wsAssertMemberLeave ws conv usr old = void $
liftIO $
WS.assertMatch (5 # Second) ws $
@@ -1671,7 +1696,7 @@ wsAssertMemberLeave ws conv usr old = void $
evtFrom e @?= usr
evtData e @?= EdMembersLeave (UserIdList old)
-wsAssertConvDelete :: MonadIO m => WS.WebSocket -> ConvId -> UserId -> m ()
+wsAssertConvDelete :: MonadIO m => WS.WebSocket -> Qualified ConvId -> Qualified UserId -> m ()
wsAssertConvDelete ws conv from = void $
liftIO $
WS.assertMatch (5 # Second) ws $
@@ -1683,7 +1708,7 @@ wsAssertConvDelete ws conv from = void $
evtFrom e @?= from
evtData e @?= EdConvDelete
-wsAssertMessage :: MonadIO m => WS.WebSocket -> ConvId -> UserId -> ClientId -> ClientId -> Text -> m ()
+wsAssertMessage :: MonadIO m => WS.WebSocket -> Qualified ConvId -> Qualified UserId -> ClientId -> ClientId -> Text -> m ()
wsAssertMessage ws conv fromu fromc to txt = void $
liftIO $
WS.assertMatch (5 # Second) ws $
@@ -1695,7 +1720,7 @@ wsAssertMessage ws conv fromu fromc to txt = void $
evtFrom e @?= fromu
evtData e @?= EdOtrMessage (OtrMessage fromc to txt (Just "data"))
-svcAssertMemberJoin :: MonadIO m => Chan TestBotEvent -> UserId -> [UserId] -> ConvId -> m ()
+svcAssertMemberJoin :: MonadIO m => Chan TestBotEvent -> Qualified UserId -> [Qualified UserId] -> Qualified ConvId -> m ()
svcAssertMemberJoin buf usr new cnv = liftIO $ do
evt <- timeout (5 # Second) $ readChan buf
case evt of
@@ -1707,7 +1732,7 @@ svcAssertMemberJoin buf usr new cnv = liftIO $ do
assertEqual "event data" (EdMembersJoin msg) (evtData e)
_ -> assertFailure "Event timeout (TestBotMessage: member-join)"
-svcAssertMemberLeave :: MonadIO m => Chan TestBotEvent -> UserId -> [UserId] -> ConvId -> m ()
+svcAssertMemberLeave :: MonadIO m => Chan TestBotEvent -> Qualified UserId -> [UserId] -> Qualified ConvId -> m ()
svcAssertMemberLeave buf usr gone cnv = liftIO $ do
evt <- timeout (5 # Second) $ readChan buf
case evt of
@@ -1719,7 +1744,7 @@ svcAssertMemberLeave buf usr gone cnv = liftIO $ do
assertEqual "event data" (EdMembersLeave msg) (evtData e)
_ -> assertFailure "Event timeout (TestBotMessage: member-leave)"
-svcAssertConvAccessUpdate :: MonadIO m => Chan TestBotEvent -> UserId -> ConversationAccessUpdate -> ConvId -> m ()
+svcAssertConvAccessUpdate :: MonadIO m => Chan TestBotEvent -> Qualified UserId -> ConversationAccessUpdate -> Qualified ConvId -> m ()
svcAssertConvAccessUpdate buf usr upd cnv = liftIO $ do
evt <- timeout (5 # Second) $ readChan buf
case evt of
@@ -1730,7 +1755,7 @@ svcAssertConvAccessUpdate buf usr upd cnv = liftIO $ do
assertEqual "event data" (EdConvAccessUpdate upd) (evtData e)
_ -> assertFailure "Event timeout (TestBotMessage: conv-access-update)"
-svcAssertConvDelete :: MonadIO m => Chan TestBotEvent -> UserId -> ConvId -> m ()
+svcAssertConvDelete :: MonadIO m => Chan TestBotEvent -> Qualified UserId -> Qualified ConvId -> m ()
svcAssertConvDelete buf usr cnv = liftIO $ do
evt <- timeout (5 # Second) $ readChan buf
case evt of
@@ -1753,7 +1778,7 @@ svcAssertBotCreated buf bid cid = liftIO $ do
return b
_ -> throwM $ HUnitFailure Nothing "Event timeout (TestBotCreated)"
-svcAssertMessage :: MonadIO m => Chan TestBotEvent -> UserId -> OtrMessage -> ConvId -> m ()
+svcAssertMessage :: MonadIO m => Chan TestBotEvent -> Qualified UserId -> OtrMessage -> Qualified ConvId -> m ()
svcAssertMessage buf from msg cnv = liftIO $ do
evt <- timeout (5 # Second) $ readChan buf
case evt of
@@ -1764,7 +1789,7 @@ svcAssertMessage buf from msg cnv = liftIO $ do
assertEqual "event data" (EdOtrMessage msg) (evtData e)
_ -> assertFailure "Event timeout (TestBotMessage: otr-message-add)"
-svcAssertEventuallyConvDelete :: MonadIO m => Chan TestBotEvent -> UserId -> ConvId -> m ()
+svcAssertEventuallyConvDelete :: MonadIO m => Chan TestBotEvent -> Qualified UserId -> Qualified ConvId -> m ()
svcAssertEventuallyConvDelete buf usr cnv = liftIO $ do
evt <- timeout (5 # Second) $ readChan buf
case evt of
@@ -1824,6 +1849,7 @@ taggedServiceNames prefix =
mkName n = Name (prefix <> "|" <> n)
testAddRemoveBotUtil ::
+ Domain ->
ProviderId ->
ServiceId ->
ConvId ->
@@ -1836,27 +1862,31 @@ testAddRemoveBotUtil ::
Galley ->
WS.Cannon ->
Http ()
-testAddRemoveBotUtil pid sid cid u1 u2 h sref buf brig galley cannon = do
- let uid1 = userId u1
- let uid2 = userId u2
+testAddRemoveBotUtil localDomain pid sid cid u1 u2 h sref buf brig galley cannon = do
+ let qcid = Qualified cid localDomain
+ uid1 = userId u1
+ uid2 = userId u2
+ quid1 = Qualified uid1 localDomain
+ quid2 = Qualified uid2 localDomain
-- Add the bot and check that everyone is notified via an event,
-- including the bot itself.
(rs, bot) <- WS.bracketR2 cannon uid1 uid2 $ \(ws1, ws2) -> do
_rs <- addBot brig uid1 pid sid cid wsAssertMemberJoin ws cid uid1 [botUserId bid]
+ forM_ [ws1, ws2] $ \ws -> wsAssertMemberJoin ws qcid quid1 [qbuid]
-- Member join event for the bot
- svcAssertMemberJoin buf uid1 [botUserId bid] cid
+ svcAssertMemberJoin buf quid1 [qbuid] qcid
return (rs, bot)
let bid = rsAddBotId rs
- let buid = botUserId bid
- -- Check that the bot token grants access to the right user and conversation
- let Just tok = fromByteString (Text.encodeUtf8 (testBotToken bot))
+ buid = botUserId bid
+ -- Check that the bot token grants access to the right user and conversation
+ Just tok = fromByteString (Text.encodeUtf8 (testBotToken bot))
liftIO $ do
assertEqual "principal" bid (BotId (Id (tok ^. ZAuth.body . ZAuth.bot)))
assertEqual "conversation" cid (Id (tok ^. ZAuth.body . ZAuth.conv))
@@ -1878,7 +1908,7 @@ testAddRemoveBotUtil pid sid cid u1 u2 h sref buf brig galley cannon = do
assertEqual "assets" defServiceAssets (profileAssets bp)
-- Check that the bot client exists and has prekeys
let isBotPrekey = (`elem` testBotPrekeys bot) . prekeyData
- getPreKey brig buid (rsAddBotClient rs) !!! do
+ getPreKey brig buid buid (rsAddBotClient rs) !!! do
const 200 === statusCode
const (Just True) === fmap isBotPrekey . responseJsonMaybe
-- Remove the bot and check that everyone is notified via an event,
@@ -1889,9 +1919,9 @@ testAddRemoveBotUtil pid sid cid u1 u2 h sref buf brig galley cannon = do
let Just ev = rsRemoveBotEvent <$> responseJsonMaybe _rs
liftIO $ assertEqual "bot event" MemberLeave (evtType ev)
-- Events for both users
- forM_ [ws1, ws2] $ \ws -> wsAssertMemberLeave ws cid uid2 [buid]
+ forM_ [ws1, ws2] $ \ws -> wsAssertMemberLeave ws qcid quid2 [buid]
-- Event for the bot
- svcAssertMemberLeave buf uid2 [buid] cid
+ svcAssertMemberLeave buf quid2 [buid] qcid
-- Empty 204 response if the bot is not in the conversation
removeBot brig uid2 cid bid !!! const 204 === statusCode
-- Check that the bot no longer has access to the conversation
@@ -1911,21 +1941,23 @@ testMessageBotUtil ::
Http ()
testMessageBotUtil quid uc cid pid sid sref buf brig galley cannon = do
let uid = qUnqualified quid
- let localDomain = qDomain quid
+ localDomain = qDomain quid
+ qcid = Qualified cid localDomain
-- Add bot to conversation
_rs <- addBot brig uid pid sid cid do
postBotMessage galley bid bc cid [(uid, uc, "Hi User!")]
!!! const 201 === statusCode
- wsAssertMessage ws cid buid bc uc "Hi User!"
+ wsAssertMessage ws qcid qbuid bc uc "Hi User!"
-- The user replies
postMessage galley uid uc cid [(buid, bc, "Hi Bot")]
!!! const 201 === statusCode
let msg = OtrMessage uc bc "Hi Bot" (Just "data")
- svcAssertMessage buf uid msg cid
+ svcAssertMessage buf quid msg qcid
-- Remove the entire service; the bot should be removed from the conversation
WS.bracketR cannon uid $ \ws -> do
deleteService brig pid sid defProviderPassword
@@ -1949,7 +1981,7 @@ testMessageBotUtil quid uc cid pid sid sref buf brig galley cannon = do
_ <- waitFor (5 # Second) not (isMember galley buid cid)
getBotConv galley bid cid
!!! const 404 === statusCode
- wsAssertMemberLeave ws cid buid [buid]
+ wsAssertMemberLeave ws qcid qbuid [buid]
prepareBotUsersTeam ::
HasCallStack =>
@@ -1975,6 +2007,7 @@ prepareBotUsersTeam brig galley sref = do
addBotConv ::
HasCallStack =>
+ Domain ->
Brig ->
WS.Cannon ->
UserId ->
@@ -1984,7 +2017,9 @@ addBotConv ::
ServiceId ->
Chan TestBotEvent ->
Http BotId
-addBotConv brig cannon uid1 uid2 cid pid sid buf =
+addBotConv localDomain brig cannon uid1 uid2 cid pid sid buf = do
+ let quid1 = Qualified uid1 localDomain
+ qcid = Qualified cid localDomain
-- Add the bot and check that everyone is notified via an event,
-- including the bot itself.
WS.bracketR2 cannon uid1 uid2 $ \(ws1, ws2) -> do
@@ -1994,10 +2029,11 @@ addBotConv brig cannon uid1 uid2 cid pid sid buf =
bot <- svcAssertBotCreated buf bid cid
liftIO $ assertEqual "bot client" (rsAddBotClient rs) (testBotClient bot)
liftIO $ assertEqual "bot event" MemberJoin (evtType (rsAddBotEvent rs))
+ let qbotId = Qualified (botUserId bid) localDomain
-- Member join event for both users
- forM_ [ws1, ws2] $ \ws -> wsAssertMemberJoin ws cid uid1 [botUserId bid]
+ forM_ [ws1, ws2] $ \ws -> wsAssertMemberJoin ws qcid quid1 [qbotId]
-- Member join event for the bot
- svcAssertMemberJoin buf uid1 [botUserId bid] cid
+ svcAssertMemberJoin buf quid1 [qbotId] qcid
return (rsAddBotId rs)
----------------------------------------------------------------------------
diff --git a/services/brig/test/integration/API/Team/Util.hs b/services/brig/test/integration/API/Team/Util.hs
index 35f2f436786..f2bea23d82a 100644
--- a/services/brig/test/integration/API/Team/Util.hs
+++ b/services/brig/test/integration/API/Team/Util.hs
@@ -206,7 +206,7 @@ createTeamConv g tid u us mtimer = do
let tinfo = Just $ ConvTeamInfo tid False
let conv =
NewConvUnmanaged $
- NewConv us Nothing (Set.fromList []) Nothing tinfo mtimer Nothing roleNameWireAdmin
+ NewConv us [] Nothing (Set.fromList []) Nothing tinfo mtimer Nothing roleNameWireAdmin
r <-
post
( g
@@ -228,7 +228,7 @@ createManagedConv g tid u us mtimer = do
let tinfo = Just $ ConvTeamInfo tid True
let conv =
NewConvManaged $
- NewConv us Nothing (Set.fromList []) Nothing tinfo mtimer Nothing roleNameWireAdmin
+ NewConv us [] Nothing (Set.fromList []) Nothing tinfo mtimer Nothing roleNameWireAdmin
r <-
post
( g
@@ -293,6 +293,13 @@ putLegalHoldEnabled tid enabled g = do
. lbytes (encode (Public.TeamFeatureStatusNoConfig enabled))
. expect2xx
+putLHWhitelistTeam :: HasCallStack => Galley -> TeamId -> Http ResponseLBS
+putLHWhitelistTeam galley tid = do
+ put
+ ( galley
+ . paths ["i", "legalhold", "whitelisted-teams", toByteString' tid]
+ )
+
accept :: Email -> InvitationCode -> RequestBody
accept email code = acceptWithName (Name "Bob") email code
diff --git a/services/brig/test/integration/API/User/Account.hs b/services/brig/test/integration/API/User/Account.hs
index 64afc7d3ce7..9cfdb429ccd 100644
--- a/services/brig/test/integration/API/User/Account.hs
+++ b/services/brig/test/integration/API/User/Account.hs
@@ -36,7 +36,7 @@ import Brig.Types.User.Auth hiding (user)
import qualified Brig.Types.User.Auth as Auth
import qualified CargoHold.Types.V3 as CHV3
import Control.Arrow ((&&&))
-import Control.Lens ((^.), (^?))
+import Control.Lens (ix, preview, (^.), (^?))
import Control.Monad.Catch
import Data.Aeson
import qualified Data.Aeson as Aeson
@@ -449,7 +449,12 @@ testUserInvalidDomain brig = do
qself <- userQualifiedId <$> randomUser brig
let uid = qUnqualified qself
get (brig . paths ["users", "invalid.example.com", toByteString' uid] . zUser uid)
- !!! const 422 === statusCode
+ !!! do
+ const 422 === statusCode
+ const (Just "/federation/get-users-by-ids")
+ === preview (ix "data" . ix "path") . responseJsonUnsafe @Value
+ const (Just "invalid.example.com")
+ === preview (ix "data" . ix "domain") . responseJsonUnsafe @Value
testExistingUserUnqualified :: Brig -> Http ()
testExistingUserUnqualified brig = do
diff --git a/services/brig/test/integration/API/User/Auth.hs b/services/brig/test/integration/API/User/Auth.hs
index 3590159df1c..07ef4868ea1 100644
--- a/services/brig/test/integration/API/User/Auth.hs
+++ b/services/brig/test/integration/API/User/Auth.hs
@@ -47,6 +47,7 @@ import Data.Id
import Data.Misc (PlainTextPassword (..))
import Data.Proxy
import qualified Data.Text as Text
+import Data.Text.IO (hPutStrLn)
import qualified Data.Text.Lazy as Lazy
import Data.Time.Clock
import qualified Data.UUID.V4 as UUID
@@ -59,7 +60,25 @@ import Test.Tasty.HUnit
import qualified Test.Tasty.HUnit as HUnit
import UnliftIO.Async hiding (wait)
import Util
-import Wire.API.Team.Feature (TeamFeatureStatusValue (..))
+
+-- | FUTUREWORK: Implement this function. This wrapper should make sure that
+-- wrapped tests run only when the feature flag 'legalhold' is set to
+-- 'whitelist-teams-and-implicit-consent' in galley's config. If tests marked
+-- with this are failing then assumption that
+-- 'whitelist-teams-and-implicit-consent' is set in all test environments is no
+-- longer correct.
+onlyIfLhWhitelisted :: (MonadIO m, Monad m) => m () -> m ()
+onlyIfLhWhitelisted action = do
+ let isGalleyLegalholdFeatureWhitelist = True
+ if isGalleyLegalholdFeatureWhitelist
+ then action
+ else
+ liftIO $
+ hPutStrLn
+ stderr
+ "*** skipping test. This test only works if you manually adjust the server config files\
+ \(the 'withLHWhitelist' trick does not work because it does not allow \
+ \brig to talk to the dynamically spawned galley)."
tests :: Opts.Opts -> Manager -> ZAuth.Env -> Brig -> Galley -> Nginz -> TestTree
tests conf m z b g n =
@@ -84,17 +103,17 @@ tests conf m z b g n =
testGroup
"legalhold-login"
[ test m "failure-no-team" (testRegularUserLegalHoldLogin b),
- test m "team-user-with-legalhold-enabled" (testTeamUserLegalHoldLogin b g),
+ test m "team-user-with-legalhold-enabled" (onlyIfLhWhitelisted (testTeamUserLegalHoldLogin b g)),
test m "failure-suspended" (testSuspendedLegalHoldLogin b),
test m "failure-no-user" (testNoUserLegalHoldLogin b),
- test m "failure-wrong-password" (testWrongPasswordLegalHoldLogin b g),
- test m "always-persistent-cookie" (testLegalHoldSessionCookie b g),
- test m "legalhold-logout" (testLegalHoldLogout b g)
+ test m "failure-wrong-password" (onlyIfLhWhitelisted (testWrongPasswordLegalHoldLogin b g)),
+ test m "always-persistent-cookie" (onlyIfLhWhitelisted (testLegalHoldSessionCookie b g)),
+ test m "legalhold-logout" (onlyIfLhWhitelisted (testLegalHoldLogout b g))
],
testGroup
"nginz"
[ test m "nginz-login" (testNginz b n),
- test m "nginz-legalhold-login" (testNginzLegalHold b g n),
+ test m "nginz-legalhold-login" (onlyIfLhWhitelisted (testNginzLegalHold b g n)),
test m "nginz-login-multiple-cookies" (testNginzMultipleCookies conf b n)
]
],
@@ -107,7 +126,7 @@ tests conf m z b g n =
test m "missing-cookie legalhold" (testMissingCookie @ZAuth.LegalHoldUser @ZAuth.LegalHoldAccess z b),
test m "unknown-cookie" (testUnknownCookie @ZAuth.User z b),
test m "unknown-cookie legalhold" (testUnknownCookie @ZAuth.LegalHoldUser z b),
- test m "token mismatch" (testTokenMismatch z b g),
+ test m "token mismatch" (onlyIfLhWhitelisted (testTokenMismatchLegalhold z b g)),
test m "new-persistent-cookie" (testNewPersistentCookie conf b),
test m "new-session-cookie" (testNewSessionCookie conf b),
test m "suspend-inactive" (testSuspendInactiveUsers conf b)
@@ -167,7 +186,7 @@ testNginzLegalHold :: Brig -> Galley -> Nginz -> Http ()
testNginzLegalHold b g n = do
-- create team user Alice
(alice, tid) <- createUserWithTeam' b
- putLegalHoldEnabled tid TeamFeatureEnabled g -- enable it for this team
+ putLHWhitelistTeam g tid !!! const 200 === statusCode
(c, t) <- do
-- we need to get the cookie domain from a login through nginz. otherwise, if brig and
-- nginz are running on different hosts, no cookie will be presented in the later requests
@@ -455,7 +474,7 @@ testRegularUserLegalHoldLogin brig = do
legalHoldLogin brig (LegalHoldLogin uid (Just defPassword) Nothing) PersistentCookie !!! do
const 403 === statusCode
-testTeamUserLegalHoldLogin :: Brig -> Galley -> Http ()
+testTeamUserLegalHoldLogin :: HasCallStack => Brig -> Galley -> Http ()
testTeamUserLegalHoldLogin brig galley = do
-- create team user Alice
(alice, tid) <- createUserWithTeam brig
@@ -463,7 +482,7 @@ testTeamUserLegalHoldLogin brig galley = do
-- fail if legalhold isn't activated yet for this user
legalHoldLogin brig (LegalHoldLogin alice (Just defPassword) Nothing) PersistentCookie !!! do
const 403 === statusCode
- putLegalHoldEnabled tid TeamFeatureEnabled galley -- enable it for this team
+ putLHWhitelistTeam galley tid !!! const 200 === statusCode
_rs <-
legalHoldLogin brig (LegalHoldLogin alice (Just defPassword) Nothing) PersistentCookie
Brig -> Galley -> Http ()
-testTokenMismatch z brig galley = do
+testTokenMismatchLegalhold :: ZAuth.Env -> Brig -> Galley -> Http ()
+testTokenMismatchLegalhold z brig galley = do
u <- randomUser brig
let Just email = userEmail u
_rs <-
@@ -635,7 +654,7 @@ testTokenMismatch z brig galley = do
const (Just "Token mismatch") =~= responseBody
-- try refresh with a regular AccessToken but a LegalHoldUserCookie
(alice, tid) <- createUserWithTeam brig
- putLegalHoldEnabled tid TeamFeatureEnabled galley -- enable it for this team
+ putLHWhitelistTeam galley tid !!! const 200 === statusCode
_rs <- legalHoldLogin brig (LegalHoldLogin alice (Just defPassword) Nothing) PersistentCookie
let c' = decodeCookie _rs
t' <- toByteString' <$> runZAuth z (randomAccessToken @ZAuth.User @ZAuth.Access)
@@ -920,7 +939,7 @@ prepareLegalHoldUser :: Brig -> Galley -> Http (UserId)
prepareLegalHoldUser brig galley = do
(uid, tid) <- createUserWithTeam brig
-- enable it for this team - without that, legalhold login will fail.
- putLegalHoldEnabled tid TeamFeatureEnabled galley
+ putLHWhitelistTeam galley tid !!! const 200 === statusCode
return uid
getCookieId :: forall u. (HasCallStack, ZAuth.UserTokenLike u) => Http.Cookie -> CookieId
diff --git a/services/brig/test/integration/API/User/Client.hs b/services/brig/test/integration/API/User/Client.hs
index 589c3414bcd..07fe84b18bd 100644
--- a/services/brig/test/integration/API/User/Client.hs
+++ b/services/brig/test/integration/API/User/Client.hs
@@ -313,12 +313,12 @@ generateClients n brig = do
testGetUserPrekeys :: Brig -> Http ()
testGetUserPrekeys brig = do
[(uid, _c, lpk, cpk)] <- generateClients 1 brig
- get (brig . paths ["users", toByteString' uid, "prekeys"]) !!! do
+ get (brig . paths ["users", toByteString' uid, "prekeys"] . zUser uid) !!! do
const 200 === statusCode
const (Just $ PrekeyBundle uid [cpk]) === responseJsonMaybe
-- prekeys are deleted when retrieved, except the last one
replicateM_ 2 $
- get (brig . paths ["users", toByteString' uid, "prekeys"]) !!! do
+ get (brig . paths ["users", toByteString' uid, "prekeys"] . zUser uid) !!! do
const 200 === statusCode
const (Just $ PrekeyBundle uid [lpk]) === responseJsonMaybe
@@ -326,20 +326,20 @@ testGetUserPrekeysQualified :: Brig -> Opt.Opts -> Http ()
testGetUserPrekeysQualified brig opts = do
let domain = opts ^. Opt.optionSettings & Opt.setFederationDomain
[(uid, _c, _lpk, cpk)] <- generateClients 1 brig
- get (brig . paths ["users", toByteString' domain, toByteString' uid, "prekeys"]) !!! do
+ get (brig . paths ["users", toByteString' domain, toByteString' uid, "prekeys"] . zUser uid) !!! do
const 200 === statusCode
const (Just $ PrekeyBundle uid [cpk]) === responseJsonMaybe
testGetUserPrekeysInvalidDomain :: Brig -> Http ()
testGetUserPrekeysInvalidDomain brig = do
[(uid, _c, _lpk, _)] <- generateClients 1 brig
- get (brig . paths ["users", "invalid.example.com", toByteString' uid, "prekeys"]) !!! do
+ get (brig . paths ["users", "invalid.example.com", toByteString' uid, "prekeys"] . zUser uid) !!! do
const 422 === statusCode
testGetClientPrekey :: Brig -> Http ()
testGetClientPrekey brig = do
[(uid, c, _lpk, cpk)] <- generateClients 1 brig
- get (brig . paths ["users", toByteString' uid, "prekeys", toByteString' (clientId c)]) !!! do
+ get (brig . paths ["users", toByteString' uid, "prekeys", toByteString' (clientId c)] . zUser uid) !!! do
const 200 === statusCode
const (Just $ cpk) === responseJsonMaybe
@@ -347,7 +347,7 @@ testGetClientPrekeyQualified :: Brig -> Opt.Opts -> Http ()
testGetClientPrekeyQualified brig opts = do
let domain = opts ^. Opt.optionSettings & Opt.setFederationDomain
[(uid, c, _lpk, cpk)] <- generateClients 1 brig
- get (brig . paths ["users", toByteString' domain, toByteString' uid, "prekeys", toByteString' (clientId c)]) !!! do
+ get (brig . paths ["users", toByteString' domain, toByteString' uid, "prekeys", toByteString' (clientId c)] . zUser uid) !!! do
const 200 === statusCode
const (Just $ cpk) === responseJsonMaybe
@@ -366,11 +366,14 @@ testMultiUserGetPrekeys brig = do
xs <&> \(uid, c, _lpk, cpk) ->
(uid, Map.singleton (clientId c) (Just (prekeyData cpk)))
+ uid <- userId <$> randomUser brig
+
post
( brig
. paths ["users", "prekeys"]
. contentJson
. body (RequestBodyLBS $ encode userClients)
+ . zUser uid
)
!!! do
const 200 === statusCode
@@ -389,19 +392,22 @@ testMultiUserGetPrekeysQualified brig opts = do
xs <&> \(uid, c, _lpk, _cpk) ->
(uid, Set.fromList [clientId c])
+ uid <- userId <$> randomUser brig
+
let expectedUserClientMap =
mkQualifiedUserClientPrekeyMap $
Map.singleton domain $
mkUserClientPrekeyMap $
Map.fromList $
- xs <&> \(uid, c, _lpk, cpk) ->
- (uid, Map.singleton (clientId c) (Just (prekeyData cpk)))
+ xs <&> \(uid', c, _lpk, cpk) ->
+ (uid', Map.singleton (clientId c) (Just (prekeyData cpk)))
post
( brig
. paths ["users", "list-prekeys"]
. contentJson
. body (RequestBodyLBS $ encode userClients)
+ . zUser uid
)
!!! do
const 200 === statusCode
@@ -454,7 +460,7 @@ testRemoveClient hasPwd brig cannon = do
-- Not found on retry
deleteClient brig uid (clientId c) Nothing !!! const 404 === statusCode
-- Prekeys are gone
- getPreKey brig uid (clientId c) !!! const 404 === statusCode
+ getPreKey brig uid uid (clientId c) !!! const 404 === statusCode
-- Cookies are gone
numCookies' <- countCookies brig (userId u) defCookieLabel
liftIO $ Just 0 @=? numCookies'
@@ -474,7 +480,7 @@ testUpdateClient opts brig = do
newClientModel = Just "featurephone"
}
c <- responseJsonError =<< addClient brig uid clt
- get (brig . paths ["users", toByteString' uid, "prekeys", toByteString' (clientId c)]) !!! do
+ get (brig . paths ["users", toByteString' uid, "prekeys", toByteString' (clientId c)] . zUser uid) !!! do
const 200 === statusCode
const (Just $ ClientPrekey (clientId c) (somePrekeys !! 0)) === responseJsonMaybe
getClient brig uid (clientId c) !!! do
@@ -493,7 +499,7 @@ testUpdateClient opts brig = do
)
!!! const 200
=== statusCode
- get (brig . paths ["users", toByteString' uid, "prekeys", toByteString' (clientId c)]) !!! do
+ get (brig . paths ["users", toByteString' uid, "prekeys", toByteString' (clientId c)] . zUser uid) !!! do
const 200 === statusCode
const (Just $ ClientPrekey (clientId c) newPrekey) === responseJsonMaybe
@@ -573,7 +579,7 @@ testUpdateClient opts brig = do
flushClientPrekey = do
responseJsonMaybe
<$> ( get
- (brig . paths ["users", toByteString' uid, "prekeys", toByteString' (clientId c)])
+ (brig . paths ["users", toByteString' uid, "prekeys", toByteString' (clientId c)] . zUser uid)
somePrekeys !! i) [1 .. 10]
c <- responseJsonError =<< addClient brig uid (defNewClient PermanentClientType pks (someLastPrekeys !! 0))
pks' <- flip mapConcurrently pks $ \_ -> do
- rs <- getPreKey brig uid (clientId c) responseJsonMaybe rs
-- We should not hand out regular prekeys more than once (i.e. at most once).
let actual = catMaybes pks'
diff --git a/services/brig/test/integration/Federation/End2end.hs b/services/brig/test/integration/Federation/End2end.hs
index 9ee542de081..2ae3bf71b0a 100644
--- a/services/brig/test/integration/Federation/End2end.hs
+++ b/services/brig/test/integration/Federation/End2end.hs
@@ -29,6 +29,7 @@ import Data.ByteString.Conversion (toByteString')
import Data.Domain (Domain)
import Data.Handle
import Data.Id (ClientId)
+import Data.List.NonEmpty (NonEmpty ((:|)))
import qualified Data.Map as Map
import Data.Qualified
import qualified Data.Set as Set
@@ -38,6 +39,8 @@ import Test.Tasty
import Test.Tasty.HUnit
import Util
import Util.Options (Endpoint)
+import Wire.API.Conversation (InviteQualified (..), NewConv (..), NewConvUnmanaged (..), cnvId)
+import Wire.API.Conversation.Role (roleNameWireAdmin)
import Wire.API.Message (UserClients (UserClients))
import Wire.API.User (ListUsersQuery (ListUsersByIds))
import Wire.API.User.Client (QualifiedUserClients (..), mkQualifiedUserClientPrekeyMap, mkUserClientPrekeyMap)
@@ -54,8 +57,8 @@ import Wire.API.User.Client (QualifiedUserClients (..), mkQualifiedUserClientPre
-- - Remote discovery succeeds but server doesn't exist
-- - Remote federator fails to respond in many ways (protocol error, timeout, etc.)
-- - SRV record has two servers but higher priority one always fails
-spec :: BrigOpts.Opts -> Manager -> Brig -> Endpoint -> Brig -> IO TestTree
-spec _brigOpts mg brig _federator brigTwo =
+spec :: BrigOpts.Opts -> Manager -> Brig -> Galley -> Endpoint -> Brig -> IO TestTree
+spec _brigOpts mg brig galley _federator brigTwo =
pure $
testGroup
"federation-end2end-user"
@@ -64,7 +67,8 @@ spec _brigOpts mg brig _federator brigTwo =
test mg "get users by ids on multiple backends" $ testGetUsersById brig brigTwo,
test mg "claim client prekey" $ testClaimPrekeySuccess brig brigTwo,
test mg "claim prekey bundle" $ testClaimPrekeyBundleSuccess brig brigTwo,
- test mg "claim multi-prekey bundle" $ testClaimMultiPrekeyBundleSuccess brig brigTwo
+ test mg "claim multi-prekey bundle" $ testClaimMultiPrekeyBundleSuccess brig brigTwo,
+ test mg "add remote users to local conversation" $ testAddRemoteUsersToLocalConv brig galley brigTwo
]
-- | Path covered by this test:
@@ -202,3 +206,36 @@ testClaimMultiPrekeyBundleSuccess brig1 brig2 = do
!!! do
const 200 === statusCode
const (Just ucm) === responseJsonMaybe
+
+testAddRemoteUsersToLocalConv :: Brig -> Galley -> Brig -> Http ()
+testAddRemoteUsersToLocalConv brig1 galley1 brig2 = do
+ alice <- randomUser brig1
+ bob <- randomUser brig2
+
+ let conv = NewConvUnmanaged $ NewConv [] [] (Just "gossip") mempty Nothing Nothing Nothing Nothing roleNameWireAdmin
+ convId <-
+ cnvId . responseJsonUnsafe
+ <$> post
+ ( galley1
+ . path "/conversations"
+ . zUser (userId alice)
+ . zConn "conn"
+ . header "Z-Type" "access"
+ . json conv
+ )
+
+ let invite = InviteQualified (userQualifiedId bob :| []) roleNameWireAdmin
+ post
+ ( galley1
+ . paths ["conversations", toByteString' convId, "members", "v2"]
+ . zUser (userId alice)
+ . zConn "conn"
+ . header "Z-Type" "access"
+ . json invite
+ )
+ !!! (const 200 === statusCode)
+
+-- FUTUREWORK: check the happy path case as implementation of these things progresses:
+-- - conversation can be queried and shows members (galley1)
+-- - conversation can be queried and shows members (galley2 via qualified get conversation endpoint)
+-- - this (qualified) convId pops up for both alice (on galley1) and bob (on galley2) when they request their own conversations ( GET /conversations )
diff --git a/services/brig/test/integration/Federation/Util.hs b/services/brig/test/integration/Federation/Util.hs
index 7eed27afb52..16bd6345e48 100644
--- a/services/brig/test/integration/Federation/Util.hs
+++ b/services/brig/test/integration/Federation/Util.hs
@@ -70,7 +70,7 @@ import Wire.API.Team.Feature (TeamFeatureStatusValue (..))
-- which will contact this mocked federator instead of a real federator.
withMockFederator :: Opt.Opts -> IORef Mock.MockState -> OutwardResponse -> Session a -> IO (a, Mock.ReceivedRequests)
withMockFederator opts ref resp action = assertRightT
- . Mock.withMockFederator ref (pure resp)
+ . Mock.withMockFederator ref (const (pure resp))
$ \st -> lift $ do
let opts' =
opts
@@ -81,7 +81,7 @@ withMockFederator opts ref resp action = assertRightT
withTempMockFederator :: Opt.Opts -> Domain -> OutwardResponse -> Session a -> IO (a, Mock.ReceivedRequests)
withTempMockFederator opts targetDomain resp action = assertRightT
- . Mock.withTempMockFederator st0 (pure resp)
+ . Mock.withTempMockFederator st0 (const (pure resp))
$ \st -> lift $ do
let opts' =
opts
diff --git a/services/brig/test/integration/Main.hs b/services/brig/test/integration/Main.hs
index f48b125d276..01ef86e6b44 100644
--- a/services/brig/test/integration/Main.hs
+++ b/services/brig/test/integration/Main.hs
@@ -108,6 +108,7 @@ runTests iConf brigOpts otherArgs = do
let turnFile = Opts.servers . Opts.turn $ brigOpts
turnFileV2 = (Opts.serversV2 . Opts.turn) brigOpts
+ localDomain = brigOpts ^. Opts.optionSettings . Opts.federationDomain
casHost = (\v -> (Opts.cassandra v) ^. casEndpoint . epHost) brigOpts
casPort = (\v -> (Opts.cassandra v) ^. casEndpoint . epPort) brigOpts
casKey = (\v -> (Opts.cassandra v) ^. casKeyspace) brigOpts
@@ -119,7 +120,7 @@ runTests iConf brigOpts otherArgs = do
emailAWSOpts <- parseEmailAWSOpts
awsEnv <- AWS.mkEnv lg awsOpts emailAWSOpts mg
userApi <- User.tests brigOpts mg b c ch g n awsEnv
- providerApi <- Provider.tests (provider iConf) mg db b c g
+ providerApi <- Provider.tests localDomain (provider iConf) mg db b c g
searchApis <- Search.tests brigOpts mg g b
teamApis <- Team.tests brigOpts mg n b c g awsEnv
turnApi <- Calling.tests mg b brigOpts turnFile turnFileV2
@@ -128,7 +129,7 @@ runTests iConf brigOpts otherArgs = do
createIndex <- Index.Create.spec brigOpts
browseTeam <- TeamUserSearch.tests brigOpts mg g b
userPendingActivation <- UserPendingActivation.tests brigOpts mg db b g s
- federationEnd2End <- Federation.End2end.spec brigOpts mg b f brigTwo
+ federationEnd2End <- Federation.End2end.spec brigOpts mg b g f brigTwo
federationEndpoints <- API.Federation.tests mg b fedBrigClient
includeFederationTests <- (== Just "1") <$> Blank.getEnv "INTEGRATION_FEDERATION_TESTS"
internalApi <- API.Internal.tests brigOpts mg b (brig iConf) gd
diff --git a/services/brig/test/integration/Util.hs b/services/brig/test/integration/Util.hs
index e9ce7dd3dfb..c364b95fe3f 100644
--- a/services/brig/test/integration/Util.hs
+++ b/services/brig/test/integration/Util.hs
@@ -504,11 +504,12 @@ defNewClient ty pks lpk =
newClientModel = Just "Test Model"
}
-getPreKey :: Brig -> UserId -> ClientId -> Http ResponseLBS
-getPreKey brig u c =
+getPreKey :: Brig -> UserId -> UserId -> ClientId -> Http ResponseLBS
+getPreKey brig zusr u c =
get $
brig
. paths ["users", toByteString' u, "prekeys", toByteString' c]
+ . zUser zusr
getTeamMember ::
(MonadIO m, MonadCatch m, MonadFail m, MonadHttp m, HasCallStack) =>
diff --git a/services/brig/test/unit/Test/Brig/API/Error.hs b/services/brig/test/unit/Test/Brig/API/Error.hs
index 0ee1ff08125..7c844e2b69a 100644
--- a/services/brig/test/unit/Test/Brig/API/Error.hs
+++ b/services/brig/test/unit/Test/Brig/API/Error.hs
@@ -1,6 +1,7 @@
module Test.Brig.API.Error where
import Brig.API.Error
+import Data.Domain
import Imports
import qualified Network.HTTP.Types as HTTP
import qualified Network.Wai.Utilities as Wai
@@ -8,6 +9,7 @@ import qualified Servant.Client.Core as Servant
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (assertEqual, testCase)
import Wire.API.Federation.Client
+import Wire.API.Federation.Error
import qualified Wire.API.Federation.GRPC.Types as Proto
tests :: TestTree
@@ -24,24 +26,24 @@ testFedError =
testCase "when federation is not configured" $
assertFedErrorStatus FederationNotConfigured 400,
testCase "when federation call fails due to RPC error" $
- assertFedErrorStatus (FederationCallFailure (FederationClientRPCError "some failure")) 500,
+ assertFedErrorStatus (mkFailure (FederationClientRPCError "some failure")) 500,
testCase "when federation call fails due wrong method" $
- assertFedErrorStatus (FederationCallFailure (FederationClientInvalidMethod "GET")) 500,
+ assertFedErrorStatus (mkFailure (FederationClientInvalidMethod "GET")) 500,
testCase "when federation call fails due to requesting streaming" $
- assertFedErrorStatus (FederationCallFailure FederationClientStreamingUnsupported) 500,
+ assertFedErrorStatus (mkFailure FederationClientStreamingUnsupported) 500,
testCase "when federation call fails due to discovery failure" $ do
let outwardErr = FederationClientOutwardError (Proto.OutwardError Proto.DiscoveryFailed Nothing)
- assertFedErrorStatus (FederationCallFailure outwardErr) 500,
+ assertFedErrorStatus (mkFailure outwardErr) 500,
testCase "when federation call fails due to decode failure" $
- assertFedErrorStatus (FederationCallFailure (FederationClientServantError (Servant.DecodeFailure "some failure" emptyRes))) 533,
+ assertFedErrorStatus (mkFailure (FederationClientServantError (Servant.DecodeFailure "some failure" emptyRes))) 533,
testCase "when federation call fails due to Servant.FailureResponse" $
- assertFedErrorStatus (FederationCallFailure (FederationClientServantError (Servant.FailureResponse emptyReq emptyRes))) 533,
+ assertFedErrorStatus (mkFailure (FederationClientServantError (Servant.FailureResponse emptyReq emptyRes))) 533,
testCase "when federation call fails due to invalid content type" $
- assertFedErrorStatus (FederationCallFailure (FederationClientServantError (Servant.InvalidContentTypeHeader emptyRes))) 533,
+ assertFedErrorStatus (mkFailure (FederationClientServantError (Servant.InvalidContentTypeHeader emptyRes))) 533,
testCase "when federation call fails due to unsupported content type" $
- assertFedErrorStatus (FederationCallFailure (FederationClientServantError (Servant.UnsupportedContentType "application/xml" emptyRes))) 533,
+ assertFedErrorStatus (mkFailure (FederationClientServantError (Servant.UnsupportedContentType "application/xml" emptyRes))) 533,
testCase "when federation call fails due to connection error" $
- assertFedErrorStatus (FederationCallFailure (FederationClientServantError (Servant.ConnectionError (SomeException TestException)))) 500
+ assertFedErrorStatus (mkFailure (FederationClientServantError (Servant.ConnectionError (SomeException TestException)))) 500
]
testOutwardError :: TestTree
@@ -83,6 +85,11 @@ testOutwardError =
]
]
+mkFailure :: FederationClientError -> FederationError
+mkFailure =
+ FederationCallFailure
+ . FederationClientFailure (Domain "far-away.example.com") "/federation/test"
+
assertFedErrorStatus :: HasCallStack => FederationError -> Int -> IO ()
assertFedErrorStatus err sts = assertEqual ("http status should be " <> show sts) (statusFor err) sts
diff --git a/services/cannon/src/Cannon/App.hs b/services/cannon/src/Cannon/App.hs
index 428549a4dfc..70a3c966f3d 100644
--- a/services/cannon/src/Cannon/App.hs
+++ b/services/cannon/src/Cannon/App.hs
@@ -153,7 +153,7 @@ readLoop ws s = loop
rejectOnError :: PendingConnection -> HandshakeException -> IO a
rejectOnError p x = do
- let f lb mg = toStrict . encode $ Error status400 lb mg
+ let f lb mg = toStrict . encode $ mkError status400 lb mg
case x of
NotSupported -> rejectRequest p (f "protocol not supported" "N/A")
MalformedRequest _ m -> rejectRequest p (f "malformed-request" (Text.pack m))
diff --git a/services/cannon/src/Cannon/WS.hs b/services/cannon/src/Cannon/WS.hs
index aeff344f222..113f9ea14bc 100644
--- a/services/cannon/src/Cannon/WS.hs
+++ b/services/cannon/src/Cannon/WS.hs
@@ -221,7 +221,7 @@ isRemoteRegistered u c = do
recovering retry3x rpcHandlers $
const $
rpc' "gundeck" (upstream e) (method GET . paths ["/i/presences", toByteString' u] . expect2xx)
- cs <- map connId <$> parseResponse (Error status502 "server-error") rs
+ cs <- map connId <$> parseResponse (mkError status502 "server-error") rs
return $ c `elem` cs
sendMsg :: L.ByteString -> Key -> Websocket -> WS ()
diff --git a/services/cargohold/src/CargoHold/API/Error.hs b/services/cargohold/src/CargoHold/API/Error.hs
index be4acb92538..d8be728e837 100644
--- a/services/cargohold/src/CargoHold/API/Error.hs
+++ b/services/cargohold/src/CargoHold/API/Error.hs
@@ -25,23 +25,23 @@ import Network.HTTP.Types.Status
import Network.Wai.Utilities.Error
assetTooLarge :: Error
-assetTooLarge = Error status413 "client-error" "Asset too large."
+assetTooLarge = mkError status413 "client-error" "Asset too large."
unauthorised :: Error
-unauthorised = Error status403 "unauthorised" "Unauthorised operation."
+unauthorised = mkError status403 "unauthorised" "Unauthorised operation."
invalidLength :: Error
-invalidLength = Error status400 "invalid-length" "Invalid content length."
+invalidLength = mkError status400 "invalid-length" "Invalid content length."
assetNotFound :: Error
-assetNotFound = Error status404 "not-found" "Asset not found."
+assetNotFound = mkError status404 "not-found" "Asset not found."
invalidMD5 :: Error
-invalidMD5 = Error status400 "client-error" "Invalid MD5."
+invalidMD5 = mkError status400 "client-error" "Invalid MD5."
requestTimeout :: Error
requestTimeout =
- Error
+ mkError
status408
"request-timeout"
"The request timed out. The server was still expecting more data \
@@ -50,7 +50,7 @@ requestTimeout =
invalidOffset :: Offset -> Offset -> Error
invalidOffset expected given =
- Error status409 "invalid-offset" $
+ mkError status409 "invalid-offset" $
toLazyText $
"Invalid offset: "
<> "expected: "
@@ -62,7 +62,7 @@ invalidOffset expected given =
uploadTooSmall :: Error
uploadTooSmall =
- Error
+ mkError
status403
"client-error"
"The current chunk size is \
@@ -70,7 +70,7 @@ uploadTooSmall =
uploadTooLarge :: Error
uploadTooLarge =
- Error
+ mkError
status413
"client-error"
"The current chunk size + offset \
@@ -78,7 +78,7 @@ uploadTooLarge =
uploadIncomplete :: TotalSize -> TotalSize -> Error
uploadIncomplete expected actual =
- Error status403 "client-error" $
+ mkError status403 "client-error" $
toLazyText $
"The upload is incomplete: "
<> "expected size: "
@@ -89,7 +89,7 @@ uploadIncomplete expected actual =
<> "."
clientError :: LText -> Error
-clientError = Error status400 "client-error"
+clientError = mkError status400 "client-error"
serverError :: Error
-serverError = Error status500 "server-error" "Server Error."
+serverError = mkError status500 "server-error" "Server Error."
diff --git a/services/cargohold/src/CargoHold/API/Public.hs b/services/cargohold/src/CargoHold/API/Public.hs
index 33950d5c024..0d141ad808c 100644
--- a/services/cargohold/src/CargoHold/API/Public.hs
+++ b/services/cargohold/src/CargoHold/API/Public.hs
@@ -61,7 +61,7 @@ sitemap = do
.&. contentType "multipart" "mixed"
.&. request
document "POST" "uploadAsset" $ do
- Doc.summary "Upload an asset"
+ Doc.summary "Upload an asset. In the multipart/mixed body, the first section's content type should be application/json. The second section's content type should be always application/octet-stream. Other content types will be ignored by the server."
Doc.consumes "multipart/mixed"
Doc.errorResponse Error.assetTooLarge
Doc.errorResponse Error.invalidLength
diff --git a/services/cargohold/src/CargoHold/S3.hs b/services/cargohold/src/CargoHold/S3.hs
index 176672fe979..c71220e9beb 100644
--- a/services/cargohold/src/CargoHold/S3.hs
+++ b/services/cargohold/src/CargoHold/S3.hs
@@ -94,10 +94,21 @@ newtype S3AssetKey = S3AssetKey {s3Key :: Text}
data S3AssetMeta = S3AssetMeta
{ v3AssetOwner :: V3.Principal,
v3AssetToken :: Maybe V3.AssetToken,
- v3AssetType :: MIME.Type
+ v3AssetType :: MIME.Type -- should be ignored, see note on overrideMimeTypeAsOctetStream. FUTUREWORK: remove entirely.
}
deriving (Show)
+-- [Note: overrideMimeTypeAsOctetStream]
+-- The asset V3 upload API allows setting arbitrary Asset MIME types on the
+-- "outside" of an uploaded (generally encrypted, exception: public profile
+-- pictures) asset. (outside meaning outside the encrypted blob in the second
+-- part of the multipart/mixed body section). However, outside-MIME types are
+-- not really used by Wire clients. To avoid any potential abuse of setting
+-- unexpected outside MIME types, yet remain backwards-compatible with older
+-- clients still setting mime types different to application/octet-stream, we
+-- ignore the uploaded mimetype header and force it to be
+-- application/octet-stream.
+
uploadV3 ::
V3.Principal ->
V3.AssetKey ->
@@ -105,16 +116,20 @@ uploadV3 ::
Maybe V3.AssetToken ->
Conduit.ConduitM () ByteString (ResourceT IO) () ->
ExceptT Error App ()
-uploadV3 prc (s3Key . mkKey -> key) (V3.AssetHeaders ct cl) tok src = do
+uploadV3 prc (s3Key . mkKey -> key) originalHeaders@(V3.AssetHeaders _ cl) tok src = do
Log.info $
"remote" .= val "S3"
~~ "asset.owner" .= toByteString prc
~~ "asset.key" .= key
+ ~~ "asset.type_from_request_ignored" .= MIME.showType (V3.hdrType originalHeaders)
~~ "asset.type" .= MIME.showType ct
~~ "asset.size" .= cl
~~ msg (val "Uploading asset")
void $ exec req
where
+ ct :: MIME.Type
+ ct = octets -- See note on overrideMimeTypeAsOctetStream
+ stream :: ConduitM () ByteString (ResourceT IO) ()
stream =
src
-- Rechunk bytestream to ensure we satisfy AWS's minimum chunk size
@@ -122,7 +137,11 @@ uploadV3 prc (s3Key . mkKey -> key) (V3.AssetHeaders ct cl) tok src = do
.| Conduit.chunksOfCE (fromIntegral defaultChunkSize)
-- Ignore any 'junk' after the content; take only 'cl' bytes.
.| Conduit.isolate (fromIntegral cl)
+
+ reqBdy :: ChunkedBody
reqBdy = ChunkedBody defaultChunkSize (fromIntegral cl) stream
+
+ req :: Text -> PutObject
req b =
putObject (BucketName b) (ObjectKey key) (toBody reqBdy)
& poContentType ?~ MIME.showType ct
@@ -163,7 +182,7 @@ deleteV3 (s3Key . mkKey -> key) = do
req b = deleteObject (BucketName b) (ObjectKey key)
updateMetadataV3 :: V3.AssetKey -> S3AssetMeta -> ExceptT Error App ()
-updateMetadataV3 (s3Key . mkKey -> key) (S3AssetMeta prc tok ct) = do
+updateMetadataV3 (s3Key . mkKey -> key) (S3AssetMeta prc tok _) = do
Log.debug $
"remote" .= val "S3"
~~ "asset.owner" .= show prc
@@ -171,6 +190,8 @@ updateMetadataV3 (s3Key . mkKey -> key) (S3AssetMeta prc tok ct) = do
~~ msg (val "Updating asset metadata")
void $ exec req
where
+ ct :: MIME.Type
+ ct = octets -- See note on overrideMimeTypeAsOctetStream
copySrc b =
decodeLatin1 . LBS.toStrict . toLazyByteString $
urlEncode [] $
@@ -372,7 +393,8 @@ createResumable ::
V3.TotalSize ->
Maybe V3.AssetToken ->
ExceptT Error App S3Resumable
-createResumable k p typ size tok = do
+createResumable k p _ size tok = do
+ let typ = octets -- see note: overrideMimeTypeAsOctetStream
let csize = calculateChunkSize size
ex <- addUTCTime V3.assetVolatileSeconds <$> liftIO getCurrentTime
let key = mkResumableKey k
@@ -438,7 +460,7 @@ uploadChunk r offset rsrc = do
return (r', rest)
where
nr = mkChunkNr r offset
- ct = MIME.showType (resumableType r)
+ ct = MIME.showType octets -- see note overrideMimeTypeAsOctetStream
putChunk chunk size = do
let S3ChunkKey k = mkChunkKey (resumableKey r) nr
let req b =
@@ -488,7 +510,7 @@ completeResumable r = do
let reqBdy = Chunked $ ChunkedBody chunkSize totalSize (chunkSource e chunks)
let putRq b =
putObject (BucketName b) (ObjectKey (s3Key (mkKey ast))) reqBdy
- & poContentType ?~ MIME.showType (resumableType r)
+ & poContentType ?~ MIME.showType octets -- see note overrideMimeTypeAsOctetStream
& poMetadata .~ metaHeaders (resumableToken r) own
void $ exec putRq
diff --git a/services/cargohold/test/integration/API/V3.hs b/services/cargohold/test/integration/API/V3.hs
index 8855819eea6..bde128d4f42 100644
--- a/services/cargohold/test/integration/API/V3.hs
+++ b/services/cargohold/test/integration/API/V3.hs
@@ -106,7 +106,7 @@ testSimpleRoundtrip c = do
r3 <- flip get' id =<< parseUrlThrow (C8.unpack (getHeader' "Location" r2))
liftIO $ do
assertEqual "status" status200 (responseStatus r3)
- assertEqual "content-type mismatch" (Just applicationText) (getContentType r3)
+ assertEqual "content-type should always be application/octet-stream" (Just applicationOctetStream) (getContentType r3)
assertEqual "token mismatch" tok (decodeHeader "x-amz-meta-token" r3)
assertEqual "user mismatch" uid (decodeHeader "x-amz-meta-user" r3)
assertEqual "data mismatch" (Just "Hello World") (responseBody r3)
@@ -159,7 +159,7 @@ testSimpleTokens c = do
r4 <- flip get' id =<< parseUrlThrow (C8.unpack (getHeader' "Location" r3))
liftIO $ do
assertEqual "status" status200 (responseStatus r4)
- assertEqual "content-type mismatch" (Just applicationText) (getContentType r4)
+ assertEqual "content-type should always be application/octet-stream" (Just applicationOctetStream) (getContentType r4)
assertEqual "token mismatch" tok' (decodeHeader "x-amz-meta-token" r4)
assertEqual "user mismatch" uid (decodeHeader "x-amz-meta-user" r4)
assertEqual "data mismatch" (Just "Hello World") (responseBody r4)
@@ -291,7 +291,7 @@ assertRandomResumable c totalSize chunkSize typ = do
r <- downloadAsset c uid key Nothing
liftIO $ do
assertEqual "status" status200 (responseStatus r)
- assertEqual "content-type mismatch" (Just textPlain) (getContentType r)
+ assertEqual "content-type should always be application/octet-stream" (Just applicationOctetStream) (getContentType r)
assertEqual "user mismatch" uid (decodeHeader "x-amz-meta-user" r)
assertEqual "data mismatch" (Just $ Lazy.fromStrict dat) (responseBody r)
diff --git a/services/federator/federator.cabal b/services/federator/federator.cabal
index 157d3a24b19..0b2892c9de7 100644
--- a/services/federator/federator.cabal
+++ b/services/federator/federator.cabal
@@ -4,7 +4,7 @@ cabal-version: 1.12
--
-- see: https://github.com/sol/hpack
--
--- hash: 9838302126363c5c21222de184bbf831d238744bb2871b425aadee55a917b341
+-- hash: b7d470767dc3f1475b43b3914d7aa4572f00de52e59a396331bf20062d1e71fb
name: federator
version: 1.0.0
@@ -19,7 +19,6 @@ build-type: Simple
library
exposed-modules:
Federator.App
- Federator.Brig
Federator.Discovery
Federator.Env
Federator.ExternalServer
@@ -27,6 +26,7 @@ library
Federator.Options
Federator.Remote
Federator.Run
+ Federator.Service
Federator.Utils.PolysemyServerError
Federator.Validation
other-modules:
diff --git a/services/federator/federator.integration.yaml b/services/federator/federator.integration.yaml
index c194f31625d..18f3c17c823 100644
--- a/services/federator/federator.integration.yaml
+++ b/services/federator/federator.integration.yaml
@@ -7,6 +7,9 @@ federatorExternal:
brig:
host: 0.0.0.0
port: 8082
+galley:
+ host: 0.0.0.0
+ port: 8085
logLevel: Debug
logNetStrings: false
diff --git a/services/federator/src/Federator/Env.hs b/services/federator/src/Federator/Env.hs
index 3b102833d27..dfcc5f69a03 100644
--- a/services/federator/src/Federator/Env.hs
+++ b/services/federator/src/Federator/Env.hs
@@ -28,7 +28,7 @@ import Federator.Options (RunSettings)
import Network.DNS.Resolver (Resolver)
import qualified Network.HTTP.Client as HTTP
import qualified System.Logger.Class as LC
-import Util.Options
+import Wire.API.Federation.GRPC.Types
data Env = Env
{ _metrics :: Metrics,
@@ -36,8 +36,7 @@ data Env = Env
_requestId :: RequestId,
_dnsResolver :: Resolver,
_runSettings :: RunSettings,
- _brig :: RPC.Request,
- _brigEndpoint :: Endpoint,
+ _service :: Component -> RPC.Request,
_httpManager :: HTTP.Manager
}
diff --git a/services/federator/src/Federator/ExternalServer.hs b/services/federator/src/Federator/ExternalServer.hs
index 058e3a8e144..006e965dc9d 100644
--- a/services/federator/src/Federator/ExternalServer.hs
+++ b/services/federator/src/Federator/ExternalServer.hs
@@ -26,9 +26,9 @@ import qualified Data.ByteString.Lazy as LBS
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import Federator.App (Federator, runAppT)
-import Federator.Brig (Brig, brigCall, interpretBrig)
import Federator.Env (Env, applog, runSettings)
import Federator.Options (RunSettings)
+import Federator.Service (Service, interpretService, serviceCall)
import Federator.Utils.PolysemyServerError (absorbServerError)
import Federator.Validation
import Imports
@@ -52,13 +52,13 @@ import Wire.API.Federation.GRPC.Types
-- FUTUREWORK(federation): How do we make sure that only legitimate endpoints can be
-- reached, some discussion here:
-- https://wearezeta.atlassian.net/wiki/spaces/CORE/pages/224166764/Limiting+access+to+federation+endpoints
--- Also, see comment in 'Federator.Brig.interpretBrig'
+-- Also, see comment in 'Federator.Service.interpretService'
--
-- FUTUREWORK(federation): implement server2server authentication!
-- (current validation only checks parsing and compares to allowList)
--
-- FUTUREWORK: consider using Polysemy.Error also in callLocal to reduce nesting and improve readability.
-callLocal :: (Members '[Brig, Embed IO, TinyLog, Polysemy.Reader RunSettings] r) => Request -> Sem r InwardResponse
+callLocal :: (Members '[Service, Embed IO, TinyLog, Polysemy.Reader RunSettings] r) => Request -> Sem r InwardResponse
callLocal req@Request {..} = do
Log.debug $
Log.msg ("Inward Request" :: ByteString)
@@ -68,7 +68,7 @@ callLocal req@Request {..} = do
case validation of
Left err -> pure $ InwardResponseErr err
Right domain -> do
- (resStatus, resBody) <- brigCall path body domain
+ (resStatus, resBody) <- serviceCall component path body domain
pure $ case HTTP.statusCode resStatus of
200 -> InwardResponseBody $ maybe mempty LBS.toStrict resBody
-- TODO: There is a unit test for this, but Akshay has seen the integration
@@ -78,19 +78,19 @@ callLocal req@Request {..} = do
-- our integration test, let's verify this.
code -> InwardResponseErr $ "Invalid HTTP status from component: " <> Text.pack (show code) <> " " <> Text.decodeUtf8 (HTTP.statusMessage resStatus)
-routeToInternal :: (Members '[Brig, Embed IO, Polysemy.Error ServerError, TinyLog, Polysemy.Reader RunSettings] r) => SingleServerT info Inward (Sem r) _
+routeToInternal :: (Members '[Service, Embed IO, Polysemy.Error ServerError, TinyLog, Polysemy.Reader RunSettings] r) => SingleServerT info Inward (Sem r) _
routeToInternal = singleService (Mu.method @"call" callLocal)
serveInward :: Env -> Int -> IO ()
serveInward env port = do
runGRpcAppTrans msgProtoBuf port transformer routeToInternal
where
- transformer :: Sem '[TinyLog, Embed IO, Polysemy.Error ServerError, Brig, Polysemy.Reader RunSettings, Embed Federator] a -> ServerErrorIO a
+ transformer :: Sem '[TinyLog, Embed IO, Polysemy.Error ServerError, Service, Polysemy.Reader RunSettings, Embed Federator] a -> ServerErrorIO a
transformer action =
runAppT env
. runM @Federator
. Polysemy.runReader (view runSettings env)
- . interpretBrig
+ . interpretService
. absorbServerError
. embedToMonadIO @Federator
. Log.runTinyLog (view applog env)
diff --git a/services/federator/src/Federator/Options.hs b/services/federator/src/Federator/Options.hs
index af88ea54182..115d06286f7 100644
--- a/services/federator/src/Federator/Options.hs
+++ b/services/federator/src/Federator/Options.hs
@@ -77,6 +77,8 @@ data Opts = Opts
federatorExternal :: Endpoint,
-- | Host and port of brig
brig :: Endpoint,
+ -- | Host and port of galley
+ galley :: Endpoint,
-- | Log level (Debug, Info, etc)
logLevel :: Level,
-- | Use netstrings encoding (see )
diff --git a/services/federator/src/Federator/Run.hs b/services/federator/src/Federator/Run.hs
index 8175904a57d..47124f09c7f 100644
--- a/services/federator/src/Federator/Run.hs
+++ b/services/federator/src/Federator/Run.hs
@@ -51,6 +51,7 @@ import qualified System.Logger.Extended as LogExt
import UnliftIO (bracket)
import UnliftIO.Async (async, waitAnyCancel)
import Util.Options
+import Wire.API.Federation.GRPC.Types
import qualified Wire.Network.DNS.Helper as DNS
------------------------------------------------------------------------------
@@ -86,12 +87,12 @@ newEnv o _dnsResolver = do
_applog <- LogExt.mkLogger (Opt.logLevel o) (Opt.logNetStrings o) (Opt.logFormat o)
let _requestId = def
let _runSettings = Opt.optSettings o
- let _brig = mkEndpoint (Opt.brig o)
- let _brigEndpoint = Opt.brig o
+ let _service Brig = mkEndpoint (Opt.brig o)
+ _service Galley = mkEndpoint (Opt.galley o)
_httpManager <- initHttpManager
return Env {..}
where
- mkEndpoint service = RPC.host (encodeUtf8 (service ^. epHost)) . RPC.port (service ^. epPort) $ RPC.empty
+ mkEndpoint s = RPC.host (encodeUtf8 (s ^. epHost)) . RPC.port (s ^. epPort) $ RPC.empty
closeEnv :: Env -> IO ()
closeEnv e = do
diff --git a/services/federator/src/Federator/Brig.hs b/services/federator/src/Federator/Service.hs
similarity index 74%
rename from services/federator/src/Federator/Brig.hs
rename to services/federator/src/Federator/Service.hs
index f666b60989d..6d2176026be 100644
--- a/services/federator/src/Federator/Brig.hs
+++ b/services/federator/src/Federator/Service.hs
@@ -15,10 +15,8 @@
-- You should have received a copy of the GNU Affero General Public License along
-- with this program. If not, see .
-module Federator.Brig where
+module Federator.Service where
--- Is there is a point in creating an effect for each service?
---
-- FUTUREWORK(federation): Once we authenticate the call, we should send authentication data
-- to brig so brig can do some authorization as required.
@@ -27,35 +25,37 @@ import Bilge.RPC (rpc')
import Control.Lens (view)
import Data.Domain
import Data.String.Conversions (cs)
+import qualified Data.Text.Lazy as LText
import Federator.App (Federator, liftAppIOToFederator)
-import Federator.Env (brig)
+import Federator.Env (service)
import Imports
import qualified Network.HTTP.Types as HTTP
import Polysemy
+import Wire.API.Federation.GRPC.Types
-newtype BrigError = BrigErrorInvalidStatus HTTP.Status
+newtype ServiceError = ServiceErrorInvalidStatus HTTP.Status
deriving (Eq, Show)
-data Brig m a where
+data Service m a where
-- | Returns status and body, 'HTTP.Response' is not nice to work with in tests
- BrigCall :: ByteString -> ByteString -> Domain -> Brig m (HTTP.Status, Maybe LByteString)
+ ServiceCall :: Component -> ByteString -> ByteString -> Domain -> Service m (HTTP.Status, Maybe LByteString)
-makeSem ''Brig
+makeSem ''Service
-- FUTUREWORK(federation): Do we want to use servant client here? May make
-- everything typed and safe
--
-- FUTUREWORK: Avoid letting the IO errors escape into `Embed Federator` and
-- return them as `Left`
-interpretBrig ::
+interpretService ::
Member (Embed Federator) r =>
- Sem (Brig ': r) a ->
+ Sem (Service ': r) a ->
Sem r a
-interpretBrig = interpret $ \case
- BrigCall path body domain -> embed @Federator . liftAppIOToFederator $ do
- brigReq <- view brig <$> ask
+interpretService = interpret $ \case
+ ServiceCall component path body domain -> embed @Federator . liftAppIOToFederator $ do
+ serviceReq <- view service <$> ask
res <-
- rpc' "brig" brigReq $
+ rpc' (LText.pack (show component)) (serviceReq component) $
RPC.method HTTP.POST
. RPC.path path -- FUTUREWORK(federation): Protect against arbitrary paths
. RPC.body (RPC.RequestBodyBS body)
diff --git a/services/federator/test/unit/Test/Federator/ExternalServer.hs b/services/federator/test/unit/Test/Federator/ExternalServer.hs
index 2594cd916c2..df4a2b16d31 100644
--- a/services/federator/test/unit/Test/Federator/ExternalServer.hs
+++ b/services/federator/test/unit/Test/Federator/ExternalServer.hs
@@ -20,9 +20,9 @@
module Test.Federator.ExternalServer where
import Data.Domain (Domain (..))
-import Federator.Brig (Brig)
import Federator.ExternalServer (callLocal)
import Federator.Options (FederationStrategy (AllowAll), RunSettings (..))
+import Federator.Service (Service)
import Imports
import qualified Network.HTTP.Types as HTTP
import Polysemy (embed, runM)
@@ -36,42 +36,56 @@ import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (assertEqual, testCase)
import Wire.API.Federation.GRPC.Types
-genMock ''Brig
+genMock ''Service
tests :: TestTree
tests =
testGroup "ExternalServer" $
[ requestBrigSuccess,
- requestBrigFailure
+ requestBrigFailure,
+ requestGalleySuccess
]
requestBrigSuccess :: TestTree
requestBrigSuccess =
testCase "should translate response from brig to 'InwardResponseBody' when response has status 200" $
- runM . evalMock @Brig @IO $ do
- mockBrigCallReturns @IO (\_ _ _ -> pure (HTTP.ok200, Just "response body"))
- let request = Request Brig "/users" "\"foo\"" exampleDomain
+ runM . evalMock @Service @IO $ do
+ mockServiceCallReturns @IO (\_ _ _ _ -> pure (HTTP.ok200, Just "response body"))
+ let request = Request Brig "/get-user-by-handle" "\"foo\"" exampleDomain
- res :: InwardResponse <- mock @Brig @IO . noLogs . Polysemy.runReader allowAllSettings $ callLocal request
- actualCalls <- mockBrigCallCalls @IO
- let expectedCall = ("/users", "\"foo\"", aValidDomain)
+ res :: InwardResponse <- mock @Service @IO . noLogs . Polysemy.runReader allowAllSettings $ callLocal request
+ actualCalls <- mockServiceCallCalls @IO
+ let expectedCall = (Brig, "/get-user-by-handle", "\"foo\"", aValidDomain)
embed $ assertEqual "one call to brig should be made" [expectedCall] actualCalls
embed $ assertEqual "response should be success with correct body" (InwardResponseBody "response body") res
requestBrigFailure :: TestTree
requestBrigFailure =
testCase "should translate response from brig to 'InwardResponseBody' when response has status 404" $
- runM . evalMock @Brig @IO $ do
- mockBrigCallReturns @IO (\_ _ _ -> pure (HTTP.notFound404, Just "response body"))
- let request = Request Brig "/users" "\"foo\"" exampleDomain
+ runM . evalMock @Service @IO $ do
+ mockServiceCallReturns @IO (\_ _ _ _ -> pure (HTTP.notFound404, Just "response body"))
+ let request = Request Brig "/get-user-by-handle" "\"foo\"" exampleDomain
- res <- mock @Brig @IO . noLogs . Polysemy.runReader allowAllSettings $ callLocal request
+ res <- mock @Service @IO . noLogs . Polysemy.runReader allowAllSettings $ callLocal request
- actualCalls <- mockBrigCallCalls @IO
- let expectedCall = ("/users", "\"foo\"", aValidDomain)
+ actualCalls <- mockServiceCallCalls @IO
+ let expectedCall = (Brig, "/get-user-by-handle", "\"foo\"", aValidDomain)
embed $ assertEqual "one call to brig should be made" [expectedCall] actualCalls
embed $ assertEqual "response should be success with correct body" (InwardResponseErr "Invalid HTTP status from component: 404 Not Found") res
+requestGalleySuccess :: TestTree
+requestGalleySuccess =
+ testCase "should translate response from galley to 'InwardResponseBody' when response has status 200" $
+ runM . evalMock @Service @IO $ do
+ mockServiceCallReturns @IO (\_ _ _ _ -> pure (HTTP.ok200, Just "response body"))
+ let request = Request Galley "/get-conversations" "{}" exampleDomain
+
+ res :: InwardResponse <- mock @Service @IO . noLogs . Polysemy.runReader allowAllSettings $ callLocal request
+ actualCalls <- mockServiceCallCalls @IO
+ let expectedCall = (Galley, "/get-conversations", "{}", aValidDomain)
+ embed $ assertEqual "one call to brig should be made" [expectedCall] actualCalls
+ embed $ assertEqual "response should be success with correct body" (InwardResponseBody "response body") res
+
allowAllSettings :: RunSettings
allowAllSettings = RunSettings AllowAll
diff --git a/services/galley/galley.cabal b/services/galley/galley.cabal
index d95febfd027..8b2b0dabc1f 100644
--- a/services/galley/galley.cabal
+++ b/services/galley/galley.cabal
@@ -4,7 +4,7 @@ cabal-version: 1.12
--
-- see: https://github.com/sol/hpack
--
--- hash: 81355d11f878feb725c6f2396f039acc8e2ac867b59a65fcc45bff2a32f85af9
+-- hash: 63f50e23b5853d8dd4b8b87b8588dc8499783ec5e7e72a181b0ccb399089a6ed
name: galley
version: 0.83.0
@@ -136,6 +136,7 @@ library
, time >=1.4
, tinylog >=0.10
, tls >=1.3.10
+ , transformers >=0.3
, types-common >=0.16
, types-common-journal >=0.1
, unliftio >=0.2
@@ -189,6 +190,7 @@ executable galley-integration
API.Teams
API.Teams.Feature
API.Teams.LegalHold
+ API.Teams.LegalHold.DisabledByDefault
API.Util
API.Util.TeamFeature
TestHelpers
@@ -213,6 +215,7 @@ executable galley-integration
, brig-types
, bytestring
, bytestring-conversion
+ , case-insensitive
, cassandra-util
, cassava
, cereal
@@ -345,6 +348,7 @@ executable galley-schema
V47_RemoveFederationIdMapping
V48_DeleteRemoteIdentifiers
V49_ReAddRemoteIdentifiers
+ V50_AddLegalholdWhitelisted
Paths_galley
hs-source-dirs:
schema/src
@@ -373,6 +377,7 @@ test-suite galley-types-tests
other-modules:
Test.Galley.API
Test.Galley.Intra.User
+ Test.Galley.Roundtrip
Paths_galley
hs-source-dirs:
test/unit
@@ -390,8 +395,10 @@ test-suite galley-types-tests
, lens
, raw-strings-qq >=1.0
, safe >=0.3
+ , servant-swagger
, ssl-util
, tasty
+ , tasty-hspec
, tasty-hunit
, tasty-quickcheck
, types-common
diff --git a/services/galley/galley.integration.yaml b/services/galley/galley.integration.yaml
index 3de9e8da1a6..d7552c6a70e 100644
--- a/services/galley/galley.integration.yaml
+++ b/services/galley/galley.integration.yaml
@@ -20,9 +20,9 @@ spar:
host: 127.0.0.1
port: 8088
-# federator:
-# host: 127.0.0.1
-# port: 8097
+federator:
+ host: 127.0.0.1
+ port: 8097
settings:
httpPoolSize: 128
@@ -44,7 +44,7 @@ settings:
featureFlags: # see #RefConfigOptions in `/docs/reference`
sso: disabled-by-default
- legalhold: disabled-by-default
+ legalhold: whitelist-teams-and-implicit-consent
teamSearchVisibility: disabled-by-default
appLock:
defaults:
diff --git a/services/galley/package.yaml b/services/galley/package.yaml
index a58e435a429..b3b28541be1 100644
--- a/services/galley/package.yaml
+++ b/services/galley/package.yaml
@@ -79,6 +79,7 @@ library:
- time >=1.4
- tinylog >=0.10
- tls >=1.3.10
+ - transformers >=0.3
- types-common >=0.16
- types-common-journal >=0.1
- unliftio >=0.2
@@ -111,9 +112,11 @@ tests:
- tasty
- tasty-hunit
- tasty-quickcheck
+ - tasty-hspec
- types-common
- QuickCheck
- wai
+ - servant-swagger
- wai-predicates
executables:
@@ -150,6 +153,7 @@ executables:
- brig-types
- bytestring
- bytestring-conversion
+ - case-insensitive
- cassandra-util
- cassava
- cereal
diff --git a/services/galley/schema/src/Main.hs b/services/galley/schema/src/Main.hs
index a239329282a..b43433838f5 100644
--- a/services/galley/schema/src/Main.hs
+++ b/services/galley/schema/src/Main.hs
@@ -52,6 +52,7 @@ import qualified V46_TeamFeatureAppLock
import qualified V47_RemoveFederationIdMapping
import qualified V48_DeleteRemoteIdentifiers
import qualified V49_ReAddRemoteIdentifiers
+import qualified V50_AddLegalholdWhitelisted
main :: IO ()
main = do
@@ -89,7 +90,8 @@ main = do
V46_TeamFeatureAppLock.migration,
V47_RemoveFederationIdMapping.migration,
V48_DeleteRemoteIdentifiers.migration,
- V49_ReAddRemoteIdentifiers.migration
+ V49_ReAddRemoteIdentifiers.migration,
+ V50_AddLegalholdWhitelisted.migration
-- When adding migrations here, don't forget to update
-- 'schemaVersion' in Galley.Data
]
diff --git a/services/galley/schema/src/V50_AddLegalholdWhitelisted.hs b/services/galley/schema/src/V50_AddLegalholdWhitelisted.hs
new file mode 100644
index 00000000000..4f38d31d69e
--- /dev/null
+++ b/services/galley/schema/src/V50_AddLegalholdWhitelisted.hs
@@ -0,0 +1,35 @@
+-- This file is part of the Wire Server implementation.
+--
+-- Copyright (C) 2020 Wire Swiss GmbH
+--
+-- This program is free software: you can redistribute it and/or modify it under
+-- the terms of the GNU Affero General Public License as published by the Free
+-- Software Foundation, either version 3 of the License, or (at your option) any
+-- later version.
+--
+-- This program is distributed in the hope that it will be useful, but WITHOUT
+-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+-- details.
+--
+-- You should have received a copy of the GNU Affero General Public License along
+-- with this program. If not, see .
+
+module V50_AddLegalholdWhitelisted
+ ( migration,
+ )
+where
+
+import Cassandra.Schema
+import Imports
+import Text.RawString.QQ
+
+migration :: Migration
+migration = Migration 50 "Add table that defines whitelisted teams for FeatureLegalHoldWhitelistTeamsAndImplicitConsent feature setting." $ do
+ schema'
+ [r|
+ CREATE TABLE legalhold_whitelisted (
+ team uuid,
+ PRIMARY KEY (team)
+ )
+ |]
diff --git a/services/galley/src/Galley/API/Create.hs b/services/galley/src/Galley/API/Create.hs
index 77b145361f2..1889d79ea80 100644
--- a/services/galley/src/Galley/API/Create.hs
+++ b/services/galley/src/Galley/API/Create.hs
@@ -29,8 +29,8 @@ import Control.Lens hiding ((??))
import Control.Monad.Catch
import Data.Id
import Data.List1 (list1)
+import Data.Qualified (Qualified (..), partitionRemoteOrLocalIds')
import Data.Range
-import Data.SOP (I (..), NS (..))
import qualified Data.Set as Set
import Data.Time
import qualified Data.UUID.Tagged as U
@@ -56,11 +56,11 @@ import Wire.API.Routes.Public.Galley (ConversationResponses)
-- Servant helpers ------------------------------------------------------
-conversationResponse :: ConversationResponse -> Union ConversationResponses
+conversationResponse :: ConversationResponse -> Galley (Union ConversationResponses)
conversationResponse (ConversationExisted c) =
- Z . I . WithStatus . Servant.addHeader (cnvId c) $ c
+ Servant.respond . WithStatus @200 . Servant.addHeader @"Location" (cnvId c) $ c
conversationResponse (ConversationCreated c) =
- S . Z . I . WithStatus . Servant.addHeader (cnvId c) $ c
+ Servant.respond . WithStatus @201 . Servant.addHeader @"Location" (cnvId c) $ c
-------------------------------------------------------------------------
@@ -76,10 +76,9 @@ createGroupConversation ::
Public.NewConvUnmanaged ->
Galley (Union ConversationResponses)
createGroupConversation user conn wrapped@(Public.NewConvUnmanaged body) =
- conversationResponse
- <$> case newConvTeam body of
- Nothing -> createRegularGroupConv user conn wrapped
- Just tinfo -> createTeamGroupConv user conn tinfo body
+ conversationResponse =<< case newConvTeam body of
+ Nothing -> createRegularGroupConv user conn wrapped
+ Just tinfo -> createTeamGroupConv user conn tinfo body
-- | An internal endpoint for creating managed group conversations. Will
-- throw an error for everything else.
@@ -97,18 +96,25 @@ internalCreateManagedConversation zusr zcon (NewConvManaged body) = do
-- | A helper for creating a regular (non-team) group conversation.
createRegularGroupConv :: UserId -> ConnId -> NewConvUnmanaged -> Galley ConversationResponse
createRegularGroupConv zusr zcon (NewConvUnmanaged body) = do
+ localDomain <- viewFederationDomain
name <- rangeCheckedMaybe (newConvName body)
- _uids <- checkedConvSize (newConvUsers body) -- currently not needed, as we only consider local IDs
- let localUserIds = newConvUsers body
- ensureConnected zusr localUserIds
- localCheckedUsers <- checkedConvSize localUserIds
+ let unqualifiedUserIds = newConvUsers body
+ qualifiedUserIds = newConvQualifiedUsers body
+ let allUsers = map (`Qualified` localDomain) unqualifiedUserIds <> qualifiedUserIds
+ checkedUsers <- checkedConvSize allUsers
+ let checkedPartitionedUsers = partitionRemoteOrLocalIds' localDomain <$> checkedUsers
+ let (remotes, locals) = fromConvSize checkedPartitionedUsers
+ ensureConnected zusr locals
+ checkRemoteUsersExist remotes
+ -- FUTUREWORK: Implement (2) and (3) as per comments for Update.addMembers. (also for createTeamGroupConv)
c <-
Data.createConversation
+ localDomain
zusr
name
(access body)
(accessRole body)
- localCheckedUsers
+ checkedPartitionedUsers
(newConvTeam body)
(newConvMessageTimer body)
(newConvReceiptMode body)
@@ -120,22 +126,30 @@ createRegularGroupConv zusr zcon (NewConvUnmanaged body) = do
-- handlers above. Allows both unmanaged and managed conversations.
createTeamGroupConv :: UserId -> ConnId -> Public.ConvTeamInfo -> Public.NewConv -> Galley ConversationResponse
createTeamGroupConv zusr zcon tinfo body = do
- let localUserIds = newConvUsers body
+ localDomain <- viewFederationDomain
name <- rangeCheckedMaybe (newConvName body)
+ let unqualifiedUserIds = newConvUsers body
+ qualifiedUserIds = newConvQualifiedUsers body
+ allUserIds = map (`Qualified` localDomain) unqualifiedUserIds <> qualifiedUserIds
let convTeam = cnvTeamId tinfo
zusrMembership <- Data.teamMember convTeam zusr
+ void $ permissionCheck CreateConversation zusrMembership
+ checkedUsers <- checkedConvSize allUserIds
+ let checkedPartitionedUsers = partitionRemoteOrLocalIds' localDomain <$> checkedUsers
+ (remotes, localUserIds) = fromConvSize checkedPartitionedUsers
convMemberships <- mapM (Data.teamMember convTeam) localUserIds
ensureAccessRole (accessRole body) (zip localUserIds convMemberships)
- void $ permissionCheck CreateConversation zusrMembership
- otherConvMems <-
+ checkedPartitionedUsersManaged <-
if cnvManaged tinfo
then do
-- ConvMaxSize MUST be < than hardlimit so the conv size check is enough
maybeAllMembers <- Data.teamMembersForFanout convTeam
let otherConvMems = filter (/= zusr) $ map (view userId) $ (maybeAllMembers ^. teamMembers)
- checkedConvSize otherConvMems
+ checkedLocalUsers <- checkedConvSize otherConvMems
+ -- NOTE: Team members are local, therefore there are no remote users in
+ -- this case
+ pure (fmap ([],) checkedLocalUsers)
else do
- otherConvMems <- checkedConvSize localUserIds
-- In teams we don't have 1:1 conversations, only regular conversations. We want
-- users without the 'AddRemoveConvMember' permission to still be able to create
-- regular conversations, therefore we check for 'AddRemoveConvMember' only if
@@ -146,13 +160,26 @@ createTeamGroupConv zusr zcon tinfo body = do
-- Not sure at the moment how to best solve this but it is unlikely
-- we can ever get rid of the team permission model anyway - the only thing I can
-- think of is that 'partners' can create convs but not be admins...
- when (length (fromConvSize otherConvMems) > 1) $ do
+ when (length allUserIds > 1) $ do
void $ permissionCheck DoNotUseDeprecatedAddRemoveConvMember zusrMembership
-- Team members are always considered to be connected, so we only check
-- 'ensureConnected' for non-team-members.
- ensureConnectedToLocals zusr (notTeamMember (fromConvSize otherConvMems) (catMaybes convMemberships))
- pure otherConvMems
- conv <- Data.createConversation zusr name (access body) (accessRole body) otherConvMems (newConvTeam body) (newConvMessageTimer body) (newConvReceiptMode body) (newConvUsersRole body)
+ ensureConnectedToLocals zusr (notTeamMember localUserIds (catMaybes convMemberships))
+ pure checkedPartitionedUsers
+ checkRemoteUsersExist remotes
+ -- FUTUREWORK: Implement (2) and (3) as per comments for Update.addMembers.
+ conv <-
+ Data.createConversation
+ localDomain
+ zusr
+ name
+ (access body)
+ (accessRole body)
+ checkedPartitionedUsersManaged
+ (newConvTeam body)
+ (newConvMessageTimer body)
+ (newConvReceiptMode body)
+ (newConvUsersRole body)
now <- liftIO getCurrentTime
-- NOTE: We only send (conversation) events to members of the conversation
notifyCreatedConversation (Just now) zusr (Just zcon) conv
@@ -162,13 +189,14 @@ createTeamGroupConv zusr zcon tinfo body = do
-- Other kinds of conversations
createSelfConversation :: UserId -> Galley (Union ConversationResponses)
-createSelfConversation zusr =
- conversationResponse <$> do
- c <- Data.conversation (Id . toUUID $ zusr)
- maybe create (conversationExisted zusr) c
+createSelfConversation zusr = do
+ c <- Data.conversation (Id . toUUID $ zusr)
+ conversationResponse
+ =<< maybe create (conversationExisted zusr) c
where
create = do
- c <- Data.createSelfConversation zusr Nothing
+ localDomain <- viewFederationDomain
+ c <- Data.createSelfConversation localDomain zusr Nothing
conversationCreated zusr c
createOne2OneConversation :: UserId -> ConnId -> NewConvUnmanaged -> Galley (Union ConversationResponses)
@@ -188,7 +216,7 @@ createOne2OneConversation zusr zcon (NewConvUnmanaged j) = do
n <- rangeCheckedMaybe (newConvName j)
c <- Data.conversation (Data.one2OneConvId x y)
resp <- maybe (create x y n $ newConvTeam j) (conversationExisted zusr) c
- pure (conversationResponse resp)
+ conversationResponse resp
where
verifyMembership tid u = do
membership <- Data.teamMember tid u
@@ -204,7 +232,8 @@ createOne2OneConversation zusr zcon (NewConvUnmanaged j) = do
Just _ -> throwM nonBindingTeam
Nothing -> throwM teamNotFound
create x y n tinfo = do
- c <- Data.createOne2OneConversation x y n (cnvTeamId <$> tinfo)
+ localDomain <- viewFederationDomain
+ c <- Data.createOne2OneConversation localDomain x y n (cnvTeamId <$> tinfo)
notifyCreatedConversation Nothing zusr (Just zcon) c
conversationCreated zusr c
@@ -221,15 +250,17 @@ createConnectConversation usr conn j = do
maybe (create x y n) (update n) conv
where
create x y n = do
- (c, e) <- Data.createConnectConversation x y n j
+ localDomain <- viewFederationDomain
+ (c, e) <- Data.createConnectConversation localDomain x y n j
notifyCreatedConversation Nothing usr conn c
- for_ (newPush ListComplete (evtFrom e) (ConvEvent e) (recipient <$> Data.convMembers c)) $ \p ->
+ for_ (newPush ListComplete usr (ConvEvent e) (recipient <$> Data.convMembers c)) $ \p ->
push1 $
p
& pushRoute .~ RouteDirect
& pushConn .~ conn
conversationCreated usr c
- update n conv =
+ update n conv = do
+ localDomain <- viewFederationDomain
let mems = Data.convMembers conv
in conversationExisted usr
=<< if
@@ -238,7 +269,7 @@ createConnectConversation usr conn j = do
connect n conv
| otherwise -> do
now <- liftIO getCurrentTime
- mm <- snd <$> Data.addMember now (Data.convId conv) usr
+ mm <- snd <$> Data.addMember localDomain now (Data.convId conv) usr
let conv' =
conv
{ Data.convMembers = Data.convMembers conv <> toList mm
@@ -255,14 +286,17 @@ createConnectConversation usr conn j = do
else return conv''
connect n conv
| Data.convType conv == ConnectConv = do
+ localDomain <- viewFederationDomain
+ let qconv = Qualified (Data.convId conv) localDomain
+ qusr = Qualified usr localDomain
n' <- case n of
Just x -> do
Data.updateConversation (Data.convId conv) x
return . Just $ fromRange x
Nothing -> return $ Data.convName conv
t <- liftIO getCurrentTime
- let e = Event ConvConnect (Data.convId conv) usr t (EdConnect j)
- for_ (newPush ListComplete (evtFrom e) (ConvEvent e) (recipient <$> Data.convMembers conv)) $ \p ->
+ let e = Event ConvConnect qconv qusr t (EdConnect j)
+ for_ (newPush ListComplete usr (ConvEvent e) (recipient <$> Data.convMembers conv)) $ \p ->
push1 $
p
& pushRoute .~ RouteDirect
@@ -290,17 +324,20 @@ handleConversationResponse = \case
notifyCreatedConversation :: Maybe UTCTime -> UserId -> Maybe ConnId -> Data.Conversation -> Galley ()
notifyCreatedConversation dtime usr conn c = do
+ localDomain <- viewFederationDomain
now <- maybe (liftIO getCurrentTime) pure dtime
- pushSome =<< mapM (toPush now) (Data.convMembers c)
+ pushSome =<< mapM (toPush localDomain now) (Data.convMembers c)
where
route
| Data.convType c == RegularConv = RouteAny
| otherwise = RouteDirect
- toPush t m = do
+ toPush dom t m = do
+ let qconv = Qualified (Data.convId c) dom
+ qusr = Qualified usr dom
c' <- conversationView (memId m) c
- let e = Event ConvCreate (Data.convId c) usr t (EdConversation c')
+ let e = Event ConvCreate qconv qusr t (EdConversation c')
return $
- newPush1 ListComplete (evtFrom e) (ConvEvent e) (list1 (recipient m) [])
+ newPush1 ListComplete usr (ConvEvent e) (list1 (recipient m) [])
& pushConn .~ conn
& pushRoute .~ route
diff --git a/services/galley/src/Galley/API/Error.hs b/services/galley/src/Galley/API/Error.hs
index 383f6c39b0a..7c7882c9014 100644
--- a/services/galley/src/Galley/API/Error.hs
+++ b/services/galley/src/Galley/API/Error.hs
@@ -35,13 +35,13 @@ internalError :: Error
internalError = internalErrorWithDescription "internal error"
internalErrorWithDescription :: LText -> Error
-internalErrorWithDescription = Error status500 "internal-error"
+internalErrorWithDescription = mkError status500 "internal-error"
convNotFound :: Error
-convNotFound = Error status404 "no-conversation" "conversation not found"
+convNotFound = mkError status404 "no-conversation" "conversation not found"
convMemberNotFound :: Error
-convMemberNotFound = Error status404 "no-conversation-member" "conversation member not found"
+convMemberNotFound = mkError status404 "no-conversation-member" "conversation member not found"
invalidSelfOp :: Error
invalidSelfOp = invalidOp "invalid operation for self conversation"
@@ -65,165 +65,174 @@ invalidTargetUserOp :: Error
invalidTargetUserOp = invalidOp "invalid target user for the given operation"
invalidOp :: LText -> Error
-invalidOp = Error status403 "invalid-op"
+invalidOp = mkError status403 "invalid-op"
invalidPayload :: LText -> Error
-invalidPayload = Error status400 "invalid-payload"
+invalidPayload = mkError status400 "invalid-payload"
badRequest :: LText -> Error
-badRequest = Error status400 "bad-request"
+badRequest = mkError status400 "bad-request"
notConnected :: Error
-notConnected = Error status403 "not-connected" "Users are not connected"
+notConnected = mkError status403 "not-connected" "Users are not connected"
+
+unknownRemoteUser :: Error
+unknownRemoteUser = mkError status400 "unknown-remote-user" "Remote user(s) not found"
tooManyMembers :: Error
-tooManyMembers = Error status403 "too-many-members" "Maximum number of members per conversation reached"
+tooManyMembers = mkError status403 "too-many-members" "Maximum number of members per conversation reached"
convAccessDenied :: Error
-convAccessDenied = Error status403 "access-denied" "Conversation access denied"
+convAccessDenied = mkError status403 "access-denied" "Conversation access denied"
accessDenied :: Error
-accessDenied = Error status403 "access-denied" "You do not have permission to access this resource"
+accessDenied = mkError status403 "access-denied" "You do not have permission to access this resource"
reAuthFailed :: Error
-reAuthFailed = Error status403 "access-denied" "This operation requires reauthentication"
+reAuthFailed = mkError status403 "access-denied" "This operation requires reauthentication"
invalidUUID4 :: Error
-invalidUUID4 = Error status400 "client-error" "Invalid UUID v4 format"
+invalidUUID4 = mkError status400 "client-error" "Invalid UUID v4 format"
unknownClient :: Error
-unknownClient = Error status403 "unknown-client" "Sending client not known"
+unknownClient = mkError status403 "unknown-client" "Sending client not known"
invalidRange :: LText -> Error
-invalidRange = Error status400 "client-error"
+invalidRange = mkError status400 "client-error"
operationDenied :: (IsPerm perm, Show perm) => perm -> Error
operationDenied p =
- Error
+ mkError
status403
"operation-denied"
("Insufficient permissions (missing " <> (pack $ show p) <> ")")
actionDenied :: Action -> Error
actionDenied a =
- Error
+ mkError
status403
"action-denied"
("Insufficient authorization (missing " <> (pack $ show a) <> ")")
noBindingTeam :: Error
-noBindingTeam = Error status403 "no-binding-team" "Operation allowed only on binding teams."
+noBindingTeam = mkError status403 "no-binding-team" "Operation allowed only on binding teams."
notAOneMemberTeam :: Error
-notAOneMemberTeam = Error status403 "not-one-member-team" "Can only delete teams with a single member."
+notAOneMemberTeam = mkError status403 "not-one-member-team" "Can only delete teams with a single member."
notATeamMember :: Error
-notATeamMember = Error status403 "no-team-member" "Requesting user is not a team member."
+notATeamMember = mkError status403 "no-team-member" "Requesting user is not a team member."
bulkGetMemberLimitExceeded :: Error
bulkGetMemberLimitExceeded =
- Error
+ mkError
status400
"too-many-uids"
("Can only process " <> cs (show @Int hardTruncationLimit) <> " user ids per request.")
broadcastLimitExceeded :: Error
broadcastLimitExceeded =
- Error
+ mkError
status400
"too-many-users-to-broadcast"
("Too many users to fan out the broadcast event to.")
noAddToManaged :: Error
-noAddToManaged = Error status403 "no-add-to-managed" "Adding users/bots directly to managed conversation is not allowed."
+noAddToManaged = mkError status403 "no-add-to-managed" "Adding users/bots directly to managed conversation is not allowed."
teamNotFound :: Error
-teamNotFound = Error status404 "no-team" "team not found"
+teamNotFound = mkError status404 "no-team" "team not found"
invalidPermissions :: Error
-invalidPermissions = Error status403 "invalid-permissions" "The specified permissions are invalid."
+invalidPermissions = mkError status403 "invalid-permissions" "The specified permissions are invalid."
invalidActions :: Error
-invalidActions = Error status403 "invalid-actions" "The specified actions are invalid."
+invalidActions = mkError status403 "invalid-actions" "The specified actions are invalid."
tooManyTeamMembers :: Error
-tooManyTeamMembers = Error status403 "too-many-team-members" "Maximum number of members per team reached"
+tooManyTeamMembers = mkError status403 "too-many-team-members" "Maximum number of members per team reached"
tooManyTeamMembersOnTeamWithLegalhold :: Error
-tooManyTeamMembersOnTeamWithLegalhold = Error status403 "too-many-members-for-legalhold" "cannot add more members to team legalhold service is enabled."
+tooManyTeamMembersOnTeamWithLegalhold = mkError status403 "too-many-members-for-legalhold" "cannot add more members to team when legalhold service is enabled."
teamMemberNotFound :: Error
-teamMemberNotFound = Error status404 "no-team-member" "team member not found"
+teamMemberNotFound = mkError status404 "no-team-member" "team member not found"
noManagedTeamConv :: Error
-noManagedTeamConv = Error status400 "no-managed-team-conv" "Managed team conversations have been deprecated."
+noManagedTeamConv = mkError status400 "no-managed-team-conv" "Managed team conversations have been deprecated."
userBindingExists :: Error
-userBindingExists = Error status403 "binding-exists" "User already bound to a different team."
+userBindingExists = mkError status403 "binding-exists" "User already bound to a different team."
noAddToBinding :: Error
-noAddToBinding = Error status403 "binding-team" "Cannot add users to binding teams, invite only."
+noAddToBinding = mkError status403 "binding-team" "Cannot add users to binding teams, invite only."
deleteQueueFull :: Error
-deleteQueueFull = Error status503 "queue-full" "The delete queue is full. No further delete requests can be processed at the moment."
+deleteQueueFull = mkError status503 "queue-full" "The delete queue is full. No further delete requests can be processed at the moment."
nonBindingTeam :: Error
-nonBindingTeam = Error status404 "non-binding-team" "not member of a binding team"
+nonBindingTeam = mkError status404 "non-binding-team" "not member of a binding team"
noBindingTeamMembers :: Error
-noBindingTeamMembers = Error status403 "non-binding-team-members" "Both users must be members of the same binding team."
+noBindingTeamMembers = mkError status403 "non-binding-team-members" "Both users must be members of the same binding team."
invalidTeamStatusUpdate :: Error
-invalidTeamStatusUpdate = Error status403 "invalid-team-status-update" "Cannot use this endpoint to update the team to the given status."
+invalidTeamStatusUpdate = mkError status403 "invalid-team-status-update" "Cannot use this endpoint to update the team to the given status."
codeNotFound :: Error
-codeNotFound = Error status404 "no-conversation-code" "conversation code not found"
+codeNotFound = mkError status404 "no-conversation-code" "conversation code not found"
cannotEnableLegalHoldServiceLargeTeam :: Error
-cannotEnableLegalHoldServiceLargeTeam = Error status403 "too-large-team-for-legalhold" "cannot 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.)"
+cannotEnableLegalHoldServiceLargeTeam = mkError status403 "too-large-team-for-legalhold" "cannot 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.)"
legalHoldServiceInvalidKey :: Error
-legalHoldServiceInvalidKey = Error status400 "legalhold-invalid-key" "legal hold service pubkey is invalid"
+legalHoldServiceInvalidKey = mkError status400 "legalhold-invalid-key" "legal hold service pubkey is invalid"
legalHoldServiceUnavailable :: Error
-legalHoldServiceUnavailable = Error status412 "legalhold-unavailable" "legal hold service does not respond or tls handshake could not be completed (did you pin the wrong public key?)"
+legalHoldServiceUnavailable = mkError status412 "legalhold-unavailable" "legal hold service does not respond or tls handshake could not be completed (did you pin the wrong public key?)"
legalHoldServiceNotRegistered :: Error
-legalHoldServiceNotRegistered = Error status400 "legalhold-not-registered" "legal hold service has not been registered for this team"
+legalHoldServiceNotRegistered = mkError status400 "legalhold-not-registered" "legal hold service has not been registered for this team"
legalHoldServiceBadResponse :: Error
-legalHoldServiceBadResponse = Error status400 "legalhold-status-bad" "legal hold service: invalid response"
+legalHoldServiceBadResponse = mkError status400 "legalhold-status-bad" "legal hold service: invalid response"
legalHoldWhitelistedOnly :: Error
-legalHoldWhitelistedOnly = Error status403 "legalhold-whitelisted-only" "legal hold is enabled for teams via server config and cannot be changed here"
+legalHoldWhitelistedOnly = mkError status403 "legalhold-whitelisted-only" "legal hold is enabled for teams via server config and cannot be changed here"
legalHoldFeatureFlagNotEnabled :: Error
-legalHoldFeatureFlagNotEnabled = Error status403 "legalhold-not-enabled" "legal hold is not enabled for this wire instance"
+legalHoldFeatureFlagNotEnabled = mkError status403 "legalhold-not-enabled" "legal hold is not enabled for this wire instance"
legalHoldNotEnabled :: Error
-legalHoldNotEnabled = Error status403 "legalhold-not-enabled" "legal hold is not enabled for this team"
+legalHoldNotEnabled = mkError status403 "legalhold-not-enabled" "legal hold is not enabled for this team"
+
+legalHoldDisableUnimplemented :: Error
+legalHoldDisableUnimplemented = mkError status403 "legalhold-disable-unimplemented" "legal hold cannot be disabled for whitelisted teams"
userLegalHoldAlreadyEnabled :: Error
-userLegalHoldAlreadyEnabled = Error status409 "legalhold-already-enabled" "legal hold is already enabled for this user"
+userLegalHoldAlreadyEnabled = mkError status409 "legalhold-already-enabled" "legal hold is already enabled for this user"
userLegalHoldNoConsent :: Error
-userLegalHoldNoConsent = Error status409 "legalhold-no-consent" "user has not given consent to using legal hold"
+userLegalHoldNoConsent = mkError status409 "legalhold-no-consent" "user has not given consent to using legal hold"
userLegalHoldIllegalOperation :: Error
-userLegalHoldIllegalOperation = Error status500 "legalhold-illegal-op" "internal server error: inconsistent change of user's legalhold state"
+userLegalHoldIllegalOperation = mkError status500 "legalhold-illegal-op" "internal server error: inconsistent change of user's legalhold state"
userLegalHoldNotPending :: Error
-userLegalHoldNotPending = Error status412 "legalhold-not-pending" "legal hold cannot be approved without being in a pending state"
+userLegalHoldNotPending = mkError status412 "legalhold-not-pending" "legal hold cannot be approved without being in a pending state"
noLegalHoldDeviceAllocated :: Error
-noLegalHoldDeviceAllocated = Error status404 "legalhold-no-device-allocated" "no legal hold device is registered for this user. POST /teams/:tid/legalhold/:uid/ to start the flow."
+noLegalHoldDeviceAllocated = mkError status404 "legalhold-no-device-allocated" "no legal hold device is registered for this user. POST /teams/:tid/legalhold/:uid/ to start the flow."
legalHoldCouldNotBlockConnections :: Error
-legalHoldCouldNotBlockConnections = Error status500 "legalhold-internal" "legal hold service: could not block connections when resolving policy conflicts."
+legalHoldCouldNotBlockConnections = mkError status500 "legalhold-internal" "legal hold service: could not block connections when resolving policy conflicts."
+
+missingLegalholdConsent :: Error
+missingLegalholdConsent = mkError status412 "missing-legalhold-consent" "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."
disableSsoNotImplemented :: Error
disableSsoNotImplemented =
- Error
+ mkError
status403
"not-implemented"
"The SSO feature flag is disabled by default. It can only be enabled once for any team, never disabled.\n\
@@ -238,27 +247,27 @@ disableSsoNotImplemented =
\open an issue on https://github.com/wireapp/wire-server."
teamSearchVisibilityNotEnabled :: Error
-teamSearchVisibilityNotEnabled = Error status403 "team-search-visibility-not-enabled" "custom search is not available for this team"
+teamSearchVisibilityNotEnabled = mkError status403 "team-search-visibility-not-enabled" "custom search is not available for this team"
customBackendNotFound :: Domain -> Error
customBackendNotFound domain =
- Error
+ mkError
status404
"custom-backend-not-found"
("custom backend not found for domain: " <> cs (domainText domain))
invalidTeamNotificationId :: Error
-invalidTeamNotificationId = Error status400 "invalid-notification-id" "Could not parse notification id (must be UUIDv1)."
+invalidTeamNotificationId = mkError status400 "invalid-notification-id" "Could not parse notification id (must be UUIDv1)."
inactivityTimeoutTooLow :: Error
-inactivityTimeoutTooLow = Error status400 "inactivity-timeout-too-low" "applock inactivity timeout must be at least 30 seconds"
+inactivityTimeoutTooLow = mkError status400 "inactivity-timeout-too-low" "applock inactivity timeout must be at least 30 seconds"
--------------------------------------------------------------------------------
-- Federation
federationNotEnabled :: forall a. Typeable a => NonEmpty (Qualified (Id a)) -> Error
federationNotEnabled qualifiedIds =
- Error
+ mkError
status403
"federation-not-enabled"
("Federation is not enabled, but remote qualified IDs (" <> idType <> ") were found: " <> rendered)
diff --git a/services/galley/src/Galley/API/Federation.hs b/services/galley/src/Galley/API/Federation.hs
index f0a4a5a89fc..9300236d7dd 100644
--- a/services/galley/src/Galley/API/Federation.hs
+++ b/services/galley/src/Galley/API/Federation.hs
@@ -16,15 +16,17 @@
-- with this program. If not, see .
module Galley.API.Federation where
-import Data.Qualified (Qualified (Qualified))
+import Data.Containers.ListUtils (nubOrd)
+import Data.Qualified (Qualified (..))
import qualified Galley.API.Mapping as Mapping
-import Galley.API.Util (viewFederationDomain)
+import Galley.API.Util (pushConversationEvent, viewFederationDomain)
import Galley.App (Galley)
import qualified Galley.Data as Data
import Imports
import Servant (ServerT)
import Servant.API.Generic (ToServantApi)
import Servant.Server.Generic (genericServerT)
+import Wire.API.Event.Conversation
import Wire.API.Federation.API.Galley (ConversationMemberUpdate (..), GetConversationsRequest (..), GetConversationsResponse (..))
import qualified Wire.API.Federation.API.Galley as FederationAPIGalley
@@ -32,8 +34,9 @@ federationSitemap :: ServerT (ToServantApi FederationAPIGalley.Api) Galley
federationSitemap =
genericServerT $
FederationAPIGalley.Api
- getConversations
- updateConversationMembership
+ { FederationAPIGalley.getConversations = getConversations,
+ FederationAPIGalley.updateConversationMemberships = updateConversationMemberships
+ }
getConversations :: GetConversationsRequest -> Galley GetConversationsResponse
getConversations (GetConversationsRequest (Qualified uid domain) gcrConvIds) = do
@@ -43,5 +46,25 @@ getConversations (GetConversationsRequest (Qualified uid domain) gcrConvIds) = d
then GetConversationsResponse . catMaybes <$> for convs (Mapping.conversationViewMaybe uid)
else error "FUTUREWORK: implement & exstend integration test when schema ready"
-updateConversationMembership :: ConversationMemberUpdate -> Galley ()
-updateConversationMembership = error "FUTUREWORK: implement after schema change"
+-- FUTUREWORK: also remove users from conversation
+updateConversationMemberships :: ConversationMemberUpdate -> Galley ()
+updateConversationMemberships cmu = do
+ localDomain <- viewFederationDomain
+ let localUsers = filter ((== localDomain) . qDomain . fst) (cmuUsersAdd cmu)
+ localUserIds = map (qUnqualified . fst) localUsers
+ when (not (null localUsers)) $ do
+ Data.addLocalMembersToRemoteConv localUserIds (cmuConvId cmu)
+ -- FUTUREWORK: the resulting event should have qualified users and conversations
+ let mems = SimpleMembers (map (uncurry SimpleMember) (cmuUsersAdd cmu))
+ let event =
+ Event
+ MemberJoin
+ (cmuConvId cmu)
+ (cmuOrigUserId cmu)
+ (cmuTime cmu)
+ (EdMembersJoin mems)
+
+ -- send notifications
+ let targets = nubOrd $ cmuAlreadyPresentUsers cmu <> localUserIds
+ -- FUTUREWORK: support bots?
+ pushConversationEvent event targets []
diff --git a/services/galley/src/Galley/API/Internal.hs b/services/galley/src/Galley/API/Internal.hs
index f42ceea8874..be3631b65ac 100644
--- a/services/galley/src/Galley/API/Internal.hs
+++ b/services/galley/src/Galley/API/Internal.hs
@@ -38,13 +38,14 @@ import Data.String.Conversions (cs)
import qualified Galley.API.Clients as Clients
import qualified Galley.API.Create as Create
import qualified Galley.API.CustomBackend as CustomBackend
+import Galley.API.LegalHold (getTeamLegalholdWhitelistedH, setTeamLegalholdWhitelistedH, unsetTeamLegalholdWhitelistedH)
import qualified Galley.API.Query as Query
import Galley.API.Teams (uncheckedDeleteTeamMember)
import qualified Galley.API.Teams as Teams
import Galley.API.Teams.Features (DoAuth (..))
import qualified Galley.API.Teams.Features as Features
import qualified Galley.API.Update as Update
-import Galley.API.Util (JSON, isMember)
+import Galley.API.Util (JSON, isMember, viewFederationDomain)
import Galley.App
import qualified Galley.Data as Data
import qualified Galley.Intra.Push as Intra
@@ -269,6 +270,19 @@ sitemap = do
.&. jsonRequest @TeamSearchVisibilityView
.&. accept "application" "json"
+ put "/i/guard-legalhold-policy-conflicts" (continue guardLegalholdPolicyConflictsH) $
+ jsonRequest @GuardLegalholdPolicyConflicts
+ .&. accept "application" "json"
+
+ put "/i/legalhold/whitelisted-teams/:tid" (continue setTeamLegalholdWhitelistedH) $
+ capture "tid"
+
+ delete "/i/legalhold/whitelisted-teams/:tid" (continue unsetTeamLegalholdWhitelistedH) $
+ capture "tid"
+
+ get "/i/legalhold/whitelisted-teams/:tid" (continue getTeamLegalholdWhitelistedH) $
+ capture "tid"
+
rmUserH :: UserId ::: Maybe ConnId -> Galley Response
rmUserH (user ::: conn) = do
empty <$ rmUser user conn
@@ -289,6 +303,7 @@ rmUser user conn = do
leaveTeams =<< Cql.liftClient (Cql.nextPage tids)
leaveConversations :: List1 UserId -> Cql.Page ConvId -> Galley ()
leaveConversations u ids = do
+ localDomain <- viewFederationDomain
cc <- Data.conversations (Cql.result ids)
pp <- for cc $ \c -> case Data.convType c of
SelfConv -> return Nothing
@@ -297,9 +312,9 @@ rmUser user conn = do
RegularConv
| user `isMember` Data.convMembers c -> do
-- FUTUREWORK: deal with remote members, too, see removeMembers
- e <- Data.removeLocalMembers c user u
+ e <- Data.removeLocalMembers localDomain c user u
return $
- (Intra.newPush ListComplete (evtFrom e) (Intra.ConvEvent e) (Intra.recipient <$> Data.convMembers c))
+ (Intra.newPush ListComplete user (Intra.ConvEvent e) (Intra.recipient <$> Data.convMembers c))
<&> set Intra.pushConn conn
. set Intra.pushRoute Intra.RouteDirect
| otherwise -> return Nothing
@@ -368,3 +383,9 @@ mkFeatureGetAndPutRoute getter setter = do
mkPutRoute (toByteString' featureName)
mkPutRoute `mapM_` Public.deprecatedFeatureName featureName
+
+guardLegalholdPolicyConflictsH :: (JsonRequest GuardLegalholdPolicyConflicts ::: JSON) -> Galley Response
+guardLegalholdPolicyConflictsH (req ::: _) = do
+ glh <- fromJsonBody req
+ Update.guardLegalholdPolicyConflicts (glhProtectee glh) (glhUserClients glh)
+ pure $ Network.Wai.Utilities.setStatus status200 empty
diff --git a/services/galley/src/Galley/API/LegalHold.hs b/services/galley/src/Galley/API/LegalHold.hs
index 9feaafed524..39c63af4709 100644
--- a/services/galley/src/Galley/API/LegalHold.hs
+++ b/services/galley/src/Galley/API/LegalHold.hs
@@ -26,11 +26,15 @@ module Galley.API.LegalHold
approveDeviceH,
disableForUserH,
isLegalHoldEnabledForTeam,
+ setTeamLegalholdWhitelistedH,
+ unsetTeamLegalholdWhitelistedH,
+ getTeamLegalholdWhitelistedH,
)
where
import Brig.Types.Client.Prekey
import Brig.Types.Connection (UpdateConnectionsInternal (..))
+import Brig.Types.Intra (ConnectionStatus (..))
import Brig.Types.Provider
import Brig.Types.Team.LegalHold hiding (userId)
import Control.Lens (view, (^.))
@@ -41,30 +45,26 @@ import Data.LegalHold (UserLegalHoldStatus (..), defUserLegalHoldStatus)
import Data.List.Split (chunksOf)
import qualified Data.Map.Strict as Map
import Data.Misc
-import Data.Proxy
-import Data.Qualified (Qualified, partitionRemoteOrLocalIds)
-import Data.Range (toRange)
import Galley.API.Error
-import Galley.API.Query (iterateConversations)
import Galley.API.Util
import Galley.App
import qualified Galley.Data as Data
+import Galley.Data.LegalHold (isTeamLegalholdWhitelisted)
import qualified Galley.Data.LegalHold as LegalHoldData
import qualified Galley.Data.TeamFeatures as TeamFeatures
import qualified Galley.External.LegalHoldService as LHService
import qualified Galley.Intra.Client as Client
-import Galley.Intra.User (putConnectionInternal)
+import Galley.Intra.User (getConnections, putConnectionInternal)
import qualified Galley.Options as Opts
import Galley.Types.Teams as Team
import Imports
-import Network.HTTP.Types (status200)
+import Network.HTTP.Types (status200, status404)
import Network.HTTP.Types.Status (status201, status204)
import Network.Wai
import Network.Wai.Predicate hiding (or, result, setStatus, _3)
import Network.Wai.Utilities as Wai
import qualified System.Logger.Class as Log
import UnliftIO.Async (pooledMapConcurrentlyN_)
-import Wire.API.Conversation (ConvMembers (..), ConvType (..), Conversation (..), OtherMember (..), cnvType)
import qualified Wire.API.Team.Feature as Public
import qualified Wire.API.Team.LegalHold as Public
@@ -82,11 +82,8 @@ isLegalHoldEnabledForTeam tid = do
Just Public.TeamFeatureEnabled -> True
Just Public.TeamFeatureDisabled -> False
Nothing -> False
- FeatureLegalHoldWhitelistTeamsAndImplicitConsent -> do
- view (options . Opts.optSettings . Opts.setLegalHoldTeamsWhitelist)
- <&> maybe
- False {- reasonable default, even though this is impossible due to "Galley.Options.validateOpts" -}
- (tid `elem`)
+ FeatureLegalHoldWhitelistTeamsAndImplicitConsent ->
+ isTeamLegalholdWhitelisted tid
createSettingsH :: UserId ::: TeamId ::: JsonRequest Public.NewLegalHoldService ::: JSON -> Galley Response
createSettingsH (zusr ::: tid ::: req ::: _) = do
@@ -133,6 +130,7 @@ removeSettingsH (zusr ::: tid ::: req ::: _) = do
removeSettings :: UserId -> TeamId -> Public.RemoveLegalHoldSettingsRequest -> Galley ()
removeSettings zusr tid (Public.RemoveLegalHoldSettingsRequest mPassword) = do
+ assertNotWhitelisting
assertLegalHoldEnabledForTeam tid
zusrMembership <- Data.teamMember tid zusr
-- let zothers = map (view userId) membs
@@ -142,6 +140,14 @@ removeSettings zusr tid (Public.RemoveLegalHoldSettingsRequest mPassword) = do
void $ permissionCheck ChangeLegalHoldTeamSettings zusrMembership
ensureReAuthorised zusr mPassword
removeSettings' tid
+ where
+ assertNotWhitelisting :: Galley ()
+ assertNotWhitelisting = do
+ view (options . Opts.optSettings . Opts.setFeatureFlags . flagLegalHold) >>= \case
+ FeatureLegalHoldDisabledPermanently -> pure ()
+ FeatureLegalHoldDisabledByDefault -> pure ()
+ FeatureLegalHoldWhitelistTeamsAndImplicitConsent -> do
+ throwM legalHoldDisableUnimplemented
-- | Remove legal hold settings from team; also disabling for all users and removing LH devices
removeSettings' ::
@@ -406,22 +412,19 @@ changeLegalholdStatus tid uid old new = do
-- FUTUREWORK: make this async?
blockConnectionsFrom1on1s :: UserId -> Galley ()
blockConnectionsFrom1on1s uid = do
- errmsgs <-
- iterateConversations uid (toRange (Proxy @500)) $ \convs -> do
- conflicts <- mconcat <$> findConflicts (filter ((== One2OneConv) . cnvType) convs)
- blockConflicts uid conflicts
+ conns <- getConnections [uid] Nothing Nothing
+ errmsgs <- do
+ conflicts <- mconcat <$> findConflicts conns
+ blockConflicts uid conflicts
case mconcat errmsgs of
[] -> pure ()
msgs@(_ : _) -> do
- Log.warn $ Log.msg @String (intercalate ", " msgs)
+ Log.warn $ Log.msg @String msgs
throwM legalHoldCouldNotBlockConnections
where
- findConflicts :: [Conversation] -> Galley [[UserId]]
- findConflicts convs = do
- let otherUids :: [Qualified UserId] =
- concatMap (fmap omQualifiedId . cmOthers . cnvMembers) convs
- ownDomain <- viewFederationDomain
- let (_remoteUsers, localUids) = partitionRemoteOrLocalIds ownDomain otherUids
+ findConflicts :: [ConnectionStatus] -> Galley [[UserId]]
+ findConflicts conns = do
+ let (FutureWork @'Public.LegalholdPlusFederationNotImplemented -> _remoteUids, localUids) = (undefined, csTo <$> conns)
-- FUTUREWORK: Handle remoteUsers here when federation is implemented
for (chunksOf 32 localUids) $ \others -> do
teamsOfUsers <- Data.usersTeams others
@@ -441,3 +444,32 @@ blockConnectionsFrom1on1s uid = do
Just team -> do
mMember <- Data.teamMember team other
pure $ maybe defUserLegalHoldStatus (view legalHoldStatus) mMember
+
+setTeamLegalholdWhitelisted :: TeamId -> Galley ()
+setTeamLegalholdWhitelisted tid = do
+ LegalHoldData.setTeamLegalholdWhitelisted tid
+
+setTeamLegalholdWhitelistedH :: TeamId -> Galley Response
+setTeamLegalholdWhitelistedH tid = do
+ empty <$ setTeamLegalholdWhitelisted tid
+
+unsetTeamLegalholdWhitelisted :: TeamId -> Galley ()
+unsetTeamLegalholdWhitelisted tid = do
+ LegalHoldData.unsetTeamLegalholdWhitelisted tid
+
+unsetTeamLegalholdWhitelistedH :: TeamId -> Galley Response
+unsetTeamLegalholdWhitelistedH tid = do
+ () <-
+ error
+ "FUTUREWORK: if we remove entries from the list, that means removing an unknown \
+ \number of LH devices as well, and possibly other things. think this through \
+ \before you enable the end-point."
+ setStatus status204 empty <$ unsetTeamLegalholdWhitelisted tid
+
+getTeamLegalholdWhitelistedH :: TeamId -> Galley Response
+getTeamLegalholdWhitelistedH tid = do
+ lhEnabled <- isTeamLegalholdWhitelisted tid
+ pure $
+ if lhEnabled
+ then setStatus status200 empty
+ else setStatus status404 empty
diff --git a/services/galley/src/Galley/API/Mapping.hs b/services/galley/src/Galley/API/Mapping.hs
index 155ff2088d8..6149c57eb0c 100644
--- a/services/galley/src/Galley/API/Mapping.hs
+++ b/services/galley/src/Galley/API/Mapping.hs
@@ -51,7 +51,7 @@ conversationView uid conv = do
+++ val " is not a member of conv "
+++ idToText (convId conv)
throwM badState
- badState = Error status500 "bad-state" "Bad internal member state."
+ badState = mkError status500 "bad-state" "Bad internal member state."
-- | View for a given user of a stored conversation.
-- Returns 'Nothing' when the user is not part of the conversation.
diff --git a/services/galley/src/Galley/API/Public.hs b/services/galley/src/Galley/API/Public.hs
index aacbf62b215..7b6088b3e85 100644
--- a/services/galley/src/Galley/API/Public.hs
+++ b/services/galley/src/Galley/API/Public.hs
@@ -76,7 +76,8 @@ servantSitemap :: ServerT GalleyAPI.ServantAPI Galley
servantSitemap =
genericServerT $
GalleyAPI.Api
- { GalleyAPI.getConversation = Query.getConversation,
+ { GalleyAPI.getUnqualifiedConversation = Query.getUnqualifiedConversation,
+ GalleyAPI.getConversation = Query.getConversation,
GalleyAPI.getConversationRoles = Query.getConversationRoles,
GalleyAPI.getConversationIds = Query.getConversationIds,
GalleyAPI.getConversations = Query.getConversations,
@@ -87,7 +88,8 @@ servantSitemap =
GalleyAPI.getTeamConversationRoles = Teams.getTeamConversationRoles,
GalleyAPI.getTeamConversations = Teams.getTeamConversations,
GalleyAPI.getTeamConversation = Teams.getTeamConversation,
- GalleyAPI.deleteTeamConversation = Teams.deleteTeamConversation
+ GalleyAPI.deleteTeamConversation = Teams.deleteTeamConversation,
+ GalleyAPI.postOtrMessage = Update.postOtrMessage
}
sitemap :: Routes ApiBuilder Galley ()
@@ -767,45 +769,6 @@ sitemap = do
errorResponse Error.convNotFound
errorResponse $ Error.invalidOp "Conversation type does not allow removing members"
- -- This endpoint can lead to the following events being sent:
- -- - OtrMessageAdd event to recipients
- post "/conversations/:cnv/otr/messages" (continue Update.postOtrMessageH) $
- zauthUserId
- .&. zauthConnId
- .&. capture "cnv"
- .&. def Public.OtrReportAllMissing filterMissing
- .&. jsonRequest @Public.NewOtrMessage
- document "POST" "postOtrMessage" $ do
- summary "Post an encrypted message to a conversation (accepts JSON)"
- parameter Path "cnv" bytes' $
- description "Conversation ID"
- parameter Query "ignore_missing" bool' $ do
- description
- "Force message delivery even when clients are missing. \
- \NOTE: can also be a comma-separated list of user IDs, \
- \in which case it specifies who exactly is allowed to \
- \have missing clients."
- optional
- parameter Query "report_missing" bool' $ do
- description
- "Don't allow message delivery when clients are missing \
- \('ignore_missing' takes precedence when present). \
- \NOTE: can also be a comma-separated list of user IDs, \
- \in which case it specifies who exactly is forbidden from \
- \having missing clients. \
- \To support large lists of user IDs exceeding the allowed \
- \URL length, you can also put this list in the body, in \
- \the optional field 'report_missing'. That body field takes \
- \precedence over both query params."
- optional
- body (ref Public.modelNewOtrMessage) $
- description "JSON body"
- returns (ref Public.modelClientMismatch)
- response 201 "Message posted" end
- response 412 "Missing clients" end
- errorResponse Error.convNotFound
- errorResponse Error.unknownClient
-
-- This endpoint can lead to the following events being sent:
-- - OtrMessageAdd event to recipients
post "/conversations/:cnv/otr/messages" (continue Update.postProtoOtrMessageH) $
diff --git a/services/galley/src/Galley/API/Query.hs b/services/galley/src/Galley/API/Query.hs
index 52eac89a470..505460cebfc 100644
--- a/services/galley/src/Galley/API/Query.hs
+++ b/services/galley/src/Galley/API/Query.hs
@@ -17,22 +17,24 @@
module Galley.API.Query
( getBotConversationH,
+ getUnqualifiedConversation,
getConversation,
getConversationRoles,
getConversationIds,
getConversations,
- iterateConversations,
getSelfH,
internalGetMemberH,
getConversationMetaH,
)
where
+import Control.Error (runExceptT)
+import Control.Monad.Catch (throwM)
import Data.CommaSeparatedList
import Data.Domain (Domain)
import Data.Id as Id
import Data.Proxy
-import Data.Qualified (Qualified (Qualified))
+import Data.Qualified (Qualified (..))
import Data.Range
import Galley.API.Error
import qualified Galley.API.Mapping as Mapping
@@ -49,6 +51,10 @@ import Network.Wai.Predicate hiding (result, setStatus)
import Network.Wai.Utilities
import qualified Wire.API.Conversation as Public
import qualified Wire.API.Conversation.Role as Public
+import Wire.API.Federation.API.Galley (gcresConvs)
+import qualified Wire.API.Federation.API.Galley as FederatedGalley
+import Wire.API.Federation.Client (executeFederated)
+import Wire.API.Federation.Error
import qualified Wire.API.Provider.Bot as Public
getBotConversationH :: BotId ::: ConvId ::: JSON -> Galley Response
@@ -69,11 +75,34 @@ getBotConversation zbot zcnv = do
| otherwise =
Just (OtherMember (Qualified (memId m) domain) (memService m) (memConvRoleName m))
-getConversation :: UserId -> ConvId -> Galley Public.Conversation
-getConversation zusr cnv = do
+getUnqualifiedConversation :: UserId -> ConvId -> Galley Public.Conversation
+getUnqualifiedConversation zusr cnv = do
c <- getConversationAndCheckMembership zusr cnv
Mapping.conversationView zusr c
+getConversation :: UserId -> Domain -> ConvId -> Galley Public.Conversation
+getConversation zusr domain cnv = do
+ localDomain <- viewFederationDomain
+ if domain == localDomain
+ then getUnqualifiedConversation zusr cnv
+ else getRemoteConversation zusr (Qualified cnv domain)
+
+getRemoteConversation :: UserId -> Qualified ConvId -> Galley Public.Conversation
+getRemoteConversation zusr (Qualified convId remoteDomain) = do
+ localDomain <- viewFederationDomain
+ let qualifiedZUser = Qualified zusr localDomain
+ req = FederatedGalley.GetConversationsRequest qualifiedZUser [convId]
+ rpc = FederatedGalley.getConversations FederatedGalley.clientRoutes req
+ -- we expect the remote galley to make adequate checks on conversation
+ -- membership and just pass through the reponse
+ conversations <-
+ runExceptT (executeFederated remoteDomain rpc)
+ >>= either (throwM . federationErrorToWai) pure
+ case gcresConvs conversations of
+ [] -> throwM convNotFound
+ [conv] -> pure conv
+ _convs -> throwM (federationUnexpectedBody "expected one conversation, got multiple")
+
getConversationRoles :: UserId -> ConvId -> Galley Public.ConversationRolesList
getConversationRoles zusr cnv = do
void $ getConversationAndCheckMembership zusr cnv
@@ -118,21 +147,6 @@ getConversations user mids mstart msize = do
| Data.isConvDeleted c = Data.deleteConversation (Data.convId c) >> pure False
| otherwise = pure True
-iterateConversations :: forall a. UserId -> Range 1 500 Int32 -> ([Public.Conversation] -> Galley a) -> Galley [a]
-iterateConversations uid pageSize handleConvs = catMaybes <$> go Nothing
- where
- go :: Maybe ConvId -> Galley [Maybe a]
- go mbConv = do
- convResult <- getConversations uid Nothing mbConv (Just pageSize)
- resultHead <- Just <$> handleConvs (convList convResult)
- resultTail <- case convList convResult of
- (conv : rest) ->
- if convHasMore convResult
- then go (Just (maximum (cnvId <$> (conv : rest))))
- else pure []
- _ -> pure []
- pure $ resultHead : resultTail
-
getSelfH :: UserId ::: ConvId -> Galley Response
getSelfH (zusr ::: cnv) = do
json <$> getSelf zusr cnv
diff --git a/services/galley/src/Galley/API/Teams.hs b/services/galley/src/Galley/API/Teams.hs
index aaae062fb75..f06c25a607a 100644
--- a/services/galley/src/Galley/API/Teams.hs
+++ b/services/galley/src/Galley/API/Teams.hs
@@ -53,6 +53,8 @@ module Galley.API.Teams
canUserJoinTeamH,
internalDeleteBindingTeamWithOneMemberH,
internalDeleteBindingTeamWithOneMember,
+ ensureNotTooLargeForLegalHold,
+ ensureNotTooLargeToActivateLegalHold,
)
where
@@ -70,6 +72,7 @@ import qualified Data.List.Extra as List
import Data.List1 (list1)
import qualified Data.Map.Strict as M
import Data.Misc (HttpsUrl)
+import Data.Qualified
import Data.Range as Range
import Data.Set (fromList)
import qualified Data.Set as Set
@@ -83,6 +86,7 @@ import qualified Galley.API.Teams.Notifications as APITeamQueue
import Galley.API.Util
import Galley.App
import qualified Galley.Data as Data
+import qualified Galley.Data.LegalHold as Data
import qualified Galley.Data.SearchVisibility as SearchVisibilityData
import Galley.Data.Services (BotMember)
import qualified Galley.Data.TeamFeatures as TeamFeatures
@@ -94,6 +98,7 @@ import qualified Galley.Intra.Spar as Spar
import qualified Galley.Intra.Team as BrigTeam
import Galley.Intra.User
import Galley.Options
+import qualified Galley.Options as Opts
import qualified Galley.Queue as Q
import Galley.Types (UserIdList (UserIdList))
import qualified Galley.Types as Conv
@@ -317,6 +322,7 @@ uncheckedDeleteTeam zusr zcon tid = do
when ((view teamBinding . tdTeam <$> team) == Just Binding) $ do
mapM_ (deleteUser . view userId) membs
Journal.teamDelete tid
+ Data.unsetTeamLegalholdWhitelisted tid
Data.deleteTeam tid
where
pushDeleteEvents :: [TeamMember] -> Event -> [Push] -> Galley ()
@@ -344,12 +350,15 @@ uncheckedDeleteTeam zusr zcon tid = do
([Push], [(BotMember, Conv.Event)]) ->
Galley ([Push], [(BotMember, Conv.Event)])
createConvDeleteEvents now teamMembs c (pp, ee) = do
- (bots, convMembs) <- botsAndUsers =<< Data.members (c ^. conversationId)
+ localDomain <- viewFederationDomain
+ let qconvId = Qualified (c ^. conversationId) localDomain
+ qorig = Qualified zusr localDomain
+ (bots, convMembs) <- botsAndUsers <$> Data.members (c ^. conversationId)
-- Only nonTeamMembers need to get any events, since on team deletion,
-- all team users are deleted immediately after these events are sent
-- and will thus never be able to see these events in practice.
let mm = nonTeamMembers convMembs teamMembs
- let e = Conv.Event Conv.ConvDelete (c ^. conversationId) zusr now Conv.EdConvDelete
+ let e = Conv.Event Conv.ConvDelete qconvId qorig now Conv.EdConvDelete
-- This event always contains all the required recipients
let p = newPush ListComplete zusr (ConvEvent e) (map recipient mm)
let ee' = bots `zip` repeat e
@@ -572,9 +581,9 @@ addTeamMember zusr zcon tid nmem = do
ensureNonBindingTeam tid
ensureUnboundUsers [uid]
ensureConnectedToLocals zusr [uid]
+ (TeamSize sizeBeforeJoin) <- BrigTeam.getSize tid
+ ensureNotTooLargeForLegalHold tid (fromIntegral sizeBeforeJoin + 1)
memList <- Data.teamMembersForFanout tid
- -- FUTUREWORK: We cannot enable legalhold on large teams right now
- ensureNotTooLargeForLegalHold tid memList
void $ addTeamMemberInternal tid (Just zusr) (Just zcon) nmem memList
-- This function is "unchecked" because there is no need to check for user binding (invite only).
@@ -587,8 +596,8 @@ uncheckedAddTeamMemberH (tid ::: req ::: _) = do
uncheckedAddTeamMember :: TeamId -> NewTeamMember -> Galley ()
uncheckedAddTeamMember tid nmem = do
mems <- Data.teamMembersForFanout tid
- -- FUTUREWORK: We cannot enable legalhold on large teams right now
- ensureNotTooLargeForLegalHold tid mems
+ (TeamSize sizeBeforeJoin) <- BrigTeam.getSize tid
+ ensureNotTooLargeForLegalHold tid (fromIntegral sizeBeforeJoin + 1)
(TeamSize sizeBeforeAdd) <- addTeamMemberInternal tid Nothing Nothing nmem mems
billingUserIds <- Journal.getBillingUserIds tid $ Just $ newTeamMemberList ((nmem ^. ntmNewTeamMember) : mems ^. teamMembers) (mems ^. teamMemberListType)
Journal.teamUpdate tid (sizeBeforeAdd + 1) billingUserIds
@@ -741,9 +750,12 @@ uncheckedDeleteTeamMember zusr zcon tid remove mems = do
pushEvent tmids edata now dc
pushEvent :: Set UserId -> Conv.EventData -> UTCTime -> Data.Conversation -> Galley ()
pushEvent exceptTo edata now dc = do
- (bots, users) <- botsAndUsers (Data.convMembers dc)
+ localDomain <- viewFederationDomain
+ let qconvId = Qualified (Data.convId dc) localDomain
+ qusr = Qualified zusr localDomain
+ let (bots, users) = botsAndUsers (Data.convMembers dc)
let x = filter (\m -> not (Conv.memId m `Set.member` exceptTo)) users
- let y = Conv.Event Conv.MemberLeave (Data.convId dc) zusr now edata
+ let y = Conv.Event Conv.MemberLeave qconvId qusr now edata
for_ (newPush (mems ^. teamMemberListType) zusr (ConvEvent y) (recipient <$> x)) $ \p ->
push1 $ p & pushConn .~ zcon
void . forkIO $ void $ External.deliver (bots `zip` repeat y)
@@ -764,11 +776,14 @@ getTeamConversation zusr tid cid = do
deleteTeamConversation :: UserId -> ConnId -> TeamId -> ConvId -> Galley (EmptyResult 200)
deleteTeamConversation zusr zcon tid cid = do
- (bots, cmems) <- botsAndUsers =<< Data.members cid
+ localDomain <- viewFederationDomain
+ let qconvId = Qualified cid localDomain
+ qusr = Qualified zusr localDomain
+ (bots, cmems) <- botsAndUsers <$> Data.members cid
ensureActionAllowed Roles.DeleteConversation =<< getSelfMember zusr cmems
flip Data.deleteCode Data.ReusableCode =<< Data.mkKey cid
now <- liftIO getCurrentTime
- let ce = Conv.Event Conv.ConvDelete cid zusr now Conv.EdConvDelete
+ let ce = Conv.Event Conv.ConvDelete qconvId qusr now Conv.EdConvDelete
let recps = fmap recipient cmems
let convPush = newPush ListComplete zusr (ConvEvent ce) recps <&> pushConn .~ Just zcon
pushSome $ maybeToList convPush
@@ -856,16 +871,38 @@ ensureNotTooLarge tid = do
throwM tooManyTeamMembers
return $ TeamSize size
--- FUTUREWORK: Large teams cannot have legalhold enabled, needs rethinking
--- due to the expensive operation of removing settings
-ensureNotTooLargeForLegalHold :: TeamId -> TeamMemberList -> Galley ()
-ensureNotTooLargeForLegalHold tid mems = do
- limit <- fromIntegral . fromRange <$> fanoutLimit
- when (length (mems ^. teamMembers) >= limit) $ do
- lhEnabled <- isLegalHoldEnabledForTeam tid
- when lhEnabled $
+-- | Ensure that a team doesn't exceed the member count limit for the LegalHold
+-- feature. A team with more members than the fanout limit is too large, because
+-- the fanout limit would prevent turning LegalHold feature _off_ again (for
+-- details see 'Galley.API.LegalHold.removeSettings').
+--
+-- If LegalHold is configured for whitelisted teams only we consider the team
+-- size unlimited, because we make the assumption that these teams won't turn
+-- LegalHold off after activation.
+-- FUTUREWORK: Find a way around the fanout limit.
+ensureNotTooLargeForLegalHold :: TeamId -> Int -> Galley ()
+ensureNotTooLargeForLegalHold tid teamSize = do
+ whenM (isLegalHoldEnabledForTeam tid) $ do
+ unlessM (teamSizeBelowLimit teamSize) $ do
throwM tooManyTeamMembersOnTeamWithLegalhold
+ensureNotTooLargeToActivateLegalHold :: TeamId -> Galley ()
+ensureNotTooLargeToActivateLegalHold tid = do
+ (TeamSize teamSize) <- BrigTeam.getSize tid
+ unlessM (teamSizeBelowLimit (fromIntegral teamSize)) $ do
+ throwM cannotEnableLegalHoldServiceLargeTeam
+
+teamSizeBelowLimit :: Int -> Galley Bool
+teamSizeBelowLimit teamSize = do
+ limit <- fromIntegral . fromRange <$> fanoutLimit
+ let withinLimit = teamSize <= limit
+ view (options . Opts.optSettings . Opts.setFeatureFlags . flagLegalHold) >>= \case
+ FeatureLegalHoldDisabledPermanently -> pure withinLimit
+ FeatureLegalHoldDisabledByDefault -> pure withinLimit
+ FeatureLegalHoldWhitelistTeamsAndImplicitConsent ->
+ -- unlimited, see docs of 'ensureNotTooLargeForLegalHold'
+ pure True
+
addTeamMemberInternal :: TeamId -> Maybe UserId -> Maybe ConnId -> NewTeamMember -> TeamMemberList -> Galley TeamSize
addTeamMemberInternal tid origin originConn (view ntmNewTeamMember -> new) memList = do
Log.debug $
@@ -875,8 +912,9 @@ addTeamMemberInternal tid origin originConn (view ntmNewTeamMember -> new) memLi
Data.addTeamMember tid new
cc <- filter (view managedConversation) <$> Data.teamConversations tid
now <- liftIO getCurrentTime
+ localDomain <- viewFederationDomain
for_ cc $ \c ->
- Data.addMember now (c ^. conversationId) (new ^. userId)
+ Data.addMember localDomain now (c ^. conversationId) (new ^. userId)
let e = newEvent MemberJoin tid now & eventData ?~ EdMemberJoin (new ^. userId)
push1 $ newPush1 (memList ^. teamMemberListType) (new ^. userId) (TeamEvent e) (recipients origin new) & pushConn .~ originConn
APITeamQueue.pushTeamEvent tid e
@@ -956,15 +994,9 @@ canUserJoinTeamH tid = canUserJoinTeam tid >> pure empty
canUserJoinTeam :: TeamId -> Galley ()
canUserJoinTeam tid = do
lhEnabled <- isLegalHoldEnabledForTeam tid
- when (lhEnabled) $
- checkTeamSize
- where
- checkTeamSize = do
- (TeamSize size) <- BrigTeam.getSize tid
- limit <- fromIntegral . fromRange <$> fanoutLimit
- -- Teams larger than fanout limit cannot use legalhold
- when (size >= limit) $ do
- throwM tooManyTeamMembersOnTeamWithLegalhold
+ when lhEnabled $ do
+ (TeamSize sizeBeforeJoin) <- BrigTeam.getSize tid
+ ensureNotTooLargeForLegalHold tid (fromIntegral sizeBeforeJoin + 1)
getTeamSearchVisibilityAvailableInternal :: TeamId -> Galley (Public.TeamFeatureStatus 'Public.TeamFeatureSearchVisibility)
getTeamSearchVisibilityAvailableInternal tid = do
diff --git a/services/galley/src/Galley/API/Teams/Features.hs b/services/galley/src/Galley/API/Teams/Features.hs
index db98f29cfc0..dd97b26cc84 100644
--- a/services/galley/src/Galley/API/Teams/Features.hs
+++ b/services/galley/src/Galley/API/Teams/Features.hs
@@ -35,22 +35,20 @@ module Galley.API.Teams.Features
)
where
-import Brig.Types.Team (TeamSize (..))
import Control.Lens
import Control.Monad.Catch
import qualified Data.Aeson as Aeson
import Data.ByteString.Conversion hiding (fromList)
import Data.Id
-import Data.Range as Range
import Data.String.Conversions (cs)
import Galley.API.Error as Galley
import Galley.API.LegalHold
+import Galley.API.Teams (ensureNotTooLargeToActivateLegalHold)
import Galley.API.Util
import Galley.App
import qualified Galley.Data as Data
import qualified Galley.Data.SearchVisibility as SearchVisibilityData
import qualified Galley.Data.TeamFeatures as TeamFeatures
-import qualified Galley.Intra.Team as BrigTeam
import Galley.Options
import Galley.Types.Teams hiding (newTeam)
import Imports
@@ -194,6 +192,9 @@ getLegalholdStatusInternal tid = do
setLegalholdStatusInternal :: TeamId -> (Public.TeamFeatureStatus 'Public.TeamFeatureLegalHold) -> Galley (Public.TeamFeatureStatus 'Public.TeamFeatureLegalHold)
setLegalholdStatusInternal tid status@(Public.tfwoStatus -> statusValue) = do
do
+ -- this extra do is to encapsulate the assertions running before the actual operation.
+ -- enabeling LH for teams is only allowed in normal operation; disabled-permanently and
+ -- whitelist-teams have no or their own way to do that, resp.
featureLegalHold <- view (options . optSettings . setFeatureFlags . flagLegalHold)
case featureLegalHold of
FeatureLegalHoldDisabledByDefault -> do
@@ -202,17 +203,13 @@ setLegalholdStatusInternal tid status@(Public.tfwoStatus -> statusValue) = do
throwM legalHoldFeatureFlagNotEnabled
FeatureLegalHoldWhitelistTeamsAndImplicitConsent -> do
throwM legalHoldWhitelistedOnly
+
+ -- we're good to update the status now.
case statusValue of
Public.TeamFeatureDisabled -> removeSettings' tid
- -- FUTUREWORK: We cannot enable legalhold on large teams right now
- Public.TeamFeatureEnabled -> checkTeamSize
+ Public.TeamFeatureEnabled -> do
+ ensureNotTooLargeToActivateLegalHold tid
TeamFeatures.setFeatureStatusNoConfig @'Public.TeamFeatureLegalHold tid status
- where
- checkTeamSize = do
- (TeamSize size) <- BrigTeam.getSize tid
- limit <- fromIntegral . fromRange <$> fanoutLimit
- when (size > limit) $ do
- throwM cannotEnableLegalHoldServiceLargeTeam
getAppLockInternal :: TeamId -> Galley (Public.TeamFeatureStatus 'Public.TeamFeatureAppLock)
getAppLockInternal tid = do
diff --git a/services/galley/src/Galley/API/Teams/Notifications.hs b/services/galley/src/Galley/API/Teams/Notifications.hs
index fd5a90f64c9..7b6128a4414 100644
--- a/services/galley/src/Galley/API/Teams/Notifications.hs
+++ b/services/galley/src/Galley/API/Teams/Notifications.hs
@@ -88,4 +88,4 @@ mkNotificationId = do
where
x10 = limitRetries 10 <> exponentialBackoff 10
fun = const (return . isNothing)
- err = Error status500 "internal-error" "unable to generate notification ID"
+ err = mkError status500 "internal-error" "unable to generate notification ID"
diff --git a/services/galley/src/Galley/API/Update.hs b/services/galley/src/Galley/API/Update.hs
index 3ddd71d53e9..24302b956d9 100644
--- a/services/galley/src/Galley/API/Update.hs
+++ b/services/galley/src/Galley/API/Update.hs
@@ -43,7 +43,7 @@ module Galley.API.Update
UpdateResponses,
-- * Talking
- postOtrMessageH,
+ postOtrMessage,
postProtoOtrMessageH,
postOtrBroadcastH,
postProtoOtrBroadcastH,
@@ -55,18 +55,26 @@ module Galley.API.Update
Galley.API.Update.addBotH,
rmBotH,
postBotMessageH,
+
+ -- * Legalhold
+ guardLegalholdPolicyConflicts,
)
where
+import Brig.Types.Intra (accountUser)
import qualified Brig.Types.User as User
import Control.Lens
import Control.Monad.Catch
import Control.Monad.State
+import Data.ByteString.Conversion (toByteString')
import Data.Code
import Data.Id
+import Data.Json.Util (toUTCTimeMillis)
+import Data.LegalHold (UserLegalHoldStatus (UserLegalHoldNoConsent), defUserLegalHoldStatus)
import Data.List.Extra (nubOrdOn)
import Data.List1
import qualified Data.Map.Strict as Map
+import Data.Misc (FutureWork (..))
import Data.Qualified
import Data.Range
import qualified Data.Set as Set
@@ -76,13 +84,14 @@ import Galley.API.Mapping
import qualified Galley.API.Teams as Teams
import Galley.API.Util
import Galley.App
+import Galley.Data (teamMember)
import qualified Galley.Data as Data
import Galley.Data.Services as Data
import Galley.Data.Types hiding (Conversation)
import qualified Galley.External as External
import qualified Galley.Intra.Client as Intra
import Galley.Intra.Push
-import Galley.Intra.User
+import Galley.Intra.User (deleteBot, getContactList, getUser, lookupActivatedUsers)
import Galley.Options
import Galley.Types
import Galley.Types.Bot hiding (addBot)
@@ -95,18 +104,25 @@ import Gundeck.Types.Push.V2 (RecipientClients (..))
import Imports
import Network.HTTP.Types
import Network.Wai
-import Network.Wai.Predicate hiding (failure, setStatus, _1, _2)
+import Network.Wai.Predicate hiding (and, failure, setStatus, _1, _2)
import Network.Wai.Utilities
import Servant (respond)
import Servant.API (NoContent (NoContent))
import Servant.API.UVerb
+import qualified System.Logger.Class as Log
import Wire.API.Conversation (InviteQualified (invQRoleName))
import qualified Wire.API.Conversation as Public
import qualified Wire.API.Conversation.Code as Public
+import qualified Wire.API.ErrorDescription as Public
import qualified Wire.API.Event.Conversation as Public
import qualified Wire.API.Message as Public
import qualified Wire.API.Message.Proto as Proto
import Wire.API.Routes.Public.Galley (UpdateResponses)
+import qualified Wire.API.Routes.Public.Galley as GalleyAPI
+import Wire.API.Team.LegalHold (LegalholdProtectee (..))
+import Wire.API.User (userTeam)
+import Wire.API.User.Client (UserClientsFull)
+import qualified Wire.API.User.Client as Client
acceptConvH :: UserId ::: Maybe ConnId ::: ConvId -> Galley Response
acceptConvH (usr ::: conn ::: cnv) = do
@@ -170,7 +186,7 @@ updateConversationAccess usr zcon cnv update = do
when (PrivateAccess `elem` targetAccess || PrivateAccessRole == targetRole) $
throwM invalidTargetAccess
-- The user who initiated access change has to be a conversation member
- (bots, users) <- botsAndUsers =<< Data.members cnv
+ (bots, users) <- botsAndUsers <$> Data.members cnv
ensureConvMember users usr
conv <- Data.conversation cnv >>= ifNothing convNotFound
-- The conversation has to be a group conversation
@@ -220,7 +236,10 @@ uncheckedUpdateConversationAccess ::
[BotMember] ->
Galley Event
uncheckedUpdateConversationAccess body usr zcon conv (currentAccess, targetAccess) (currentRole, targetRole) users bots = do
+ localDomain <- viewFederationDomain
let cnv = convId conv
+ qcnv = Qualified cnv localDomain
+ qusr = Qualified usr localDomain
-- Remove conversation codes if CodeAccess is revoked
when (CodeAccess `elem` currentAccess && CodeAccess `notElem` targetAccess) $ do
key <- mkKey cnv
@@ -248,7 +267,7 @@ uncheckedUpdateConversationAccess body usr zcon conv (currentAccess, targetAcces
_ -> return ()
-- Update Cassandra & send an event
now <- liftIO getCurrentTime
- let accessEvent = Event ConvAccessUpdate cnv usr now (EdConvAccessUpdate body)
+ let accessEvent = Event ConvAccessUpdate qcnv qusr now (EdConvAccessUpdate body)
Data.updateConversationAccess cnv targetAccess targetRole
pushEvent accessEvent users bots zcon
-- Remove users and bots
@@ -259,10 +278,10 @@ uncheckedUpdateConversationAccess body usr zcon conv (currentAccess, targetAcces
[] -> return ()
x : xs -> do
-- FUTUREWORK: deal with remote members, too, see removeMembers
- e <- Data.removeLocalMembers conv usr (list1 x xs)
+ e <- Data.removeLocalMembers localDomain conv usr (list1 x xs)
-- push event to all clients, including zconn
-- since updateConversationAccess generates a second (member removal) event here
- for_ (newPush ListComplete (evtFrom e) (ConvEvent e) (recipient <$> users)) $ \p -> push1 p
+ for_ (newPush ListComplete usr (ConvEvent e) (recipient <$> users)) $ \p -> push1 p
void . forkIO $ void $ External.deliver (newBots `zip` repeat e)
-- Return the event
pure accessEvent
@@ -279,18 +298,21 @@ updateConversationReceiptModeH (usr ::: zcon ::: cnv ::: req ::: _) = do
updateConversationReceiptMode :: UserId -> ConnId -> ConvId -> Public.ConversationReceiptModeUpdate -> Galley UpdateResult
updateConversationReceiptMode usr zcon cnv receiptModeUpdate@(Public.ConversationReceiptModeUpdate target) = do
- (bots, users) <- botsAndUsers =<< Data.members cnv
+ localDomain <- viewFederationDomain
+ let qcnv = Qualified cnv localDomain
+ qusr = Qualified usr localDomain
+ (bots, users) <- botsAndUsers <$> Data.members cnv
ensureActionAllowed ModifyConversationReceiptMode =<< getSelfMember usr users
current <- Data.lookupReceiptMode cnv
if current == Just target
then pure Unchanged
- else Updated <$> update users bots
+ else Updated <$> update qcnv qusr users bots
where
- update users bots = do
+ update qcnv qusr users bots = do
-- Update Cassandra & send an event
Data.updateConversationReceiptMode cnv target
now <- liftIO getCurrentTime
- let receiptEvent = Event ConvReceiptModeUpdate cnv usr now (EdConvReceiptModeUpdate receiptModeUpdate)
+ let receiptEvent = Event ConvReceiptModeUpdate qcnv qusr now (EdConvReceiptModeUpdate receiptModeUpdate)
pushEvent receiptEvent users bots zcon
pure receiptEvent
@@ -301,27 +323,30 @@ updateConversationMessageTimerH (usr ::: zcon ::: cnv ::: req) = do
updateConversationMessageTimer :: UserId -> ConnId -> ConvId -> Public.ConversationMessageTimerUpdate -> Galley UpdateResult
updateConversationMessageTimer usr zcon cnv timerUpdate@(Public.ConversationMessageTimerUpdate target) = do
+ localDomain <- viewFederationDomain
+ let qcnv = Qualified cnv localDomain
+ qusr = Qualified usr localDomain
-- checks and balances
- (bots, users) <- botsAndUsers =<< Data.members cnv
+ (bots, users) <- botsAndUsers <$> Data.members cnv
ensureActionAllowed ModifyConversationMessageTimer =<< getSelfMember usr users
conv <- Data.conversation cnv >>= ifNothing convNotFound
ensureGroupConv conv
let currentTimer = Data.convMessageTimer conv
if currentTimer == target
then pure Unchanged
- else Updated <$> update users bots
+ else Updated <$> update qcnv qusr users bots
where
- update users bots = do
+ update qcnv qusr users bots = do
-- update cassandra & send event
now <- liftIO getCurrentTime
- let timerEvent = Event ConvMessageTimerUpdate cnv usr now (EdConvMessageTimerUpdate timerUpdate)
+ let timerEvent = Event ConvMessageTimerUpdate qcnv qusr now (EdConvMessageTimerUpdate timerUpdate)
Data.updateConversationMessageTimer cnv target
pushEvent timerEvent users bots zcon
pure timerEvent
pushEvent :: Event -> [LocalMember] -> [BotMember] -> ConnId -> Galley ()
pushEvent e users bots zcon = do
- for_ (newPush ListComplete (evtFrom e) (ConvEvent e) (recipient <$> users)) $ \p ->
+ for_ (newPush ListComplete (qUnqualified (evtFrom e)) (ConvEvent e) (recipient <$> users)) $ \p ->
push1 $ p & pushConn ?~ zcon
void . forkIO $ void $ External.deliver (bots `zip` repeat e)
@@ -337,10 +362,13 @@ data AddCodeResult
addCode :: UserId -> ConnId -> ConvId -> Galley AddCodeResult
addCode usr zcon cnv = do
+ localDomain <- viewFederationDomain
+ let qcnv = Qualified cnv localDomain
+ qusr = Qualified usr localDomain
conv <- Data.conversation cnv >>= ifNothing convNotFound
ensureConvMember (Data.convMembers conv) usr
ensureAccess conv CodeAccess
- (bots, users) <- botsAndUsers $ Data.convMembers conv
+ let (bots, users) = botsAndUsers $ Data.convMembers conv
key <- mkKey cnv
mCode <- Data.lookupCode key ReusableCode
case mCode of
@@ -349,7 +377,7 @@ addCode usr zcon cnv = do
Data.insertCode code
now <- liftIO getCurrentTime
conversationCode <- createCode code
- let event = Event ConvCodeUpdate cnv usr now (EdConvCodeUpdate conversationCode)
+ let event = Event ConvCodeUpdate qcnv qusr now (EdConvCodeUpdate conversationCode)
pushEvent event users bots zcon
pure $ CodeAdded event
Just code -> do
@@ -367,14 +395,17 @@ rmCodeH (usr ::: zcon ::: cnv) = do
rmCode :: UserId -> ConnId -> ConvId -> Galley Public.Event
rmCode usr zcon cnv = do
+ localDomain <- viewFederationDomain
+ let qcnv = Qualified cnv localDomain
+ qusr = Qualified usr localDomain
conv <- Data.conversation cnv >>= ifNothing convNotFound
ensureConvMember (Data.convMembers conv) usr
ensureAccess conv CodeAccess
- (bots, users) <- botsAndUsers $ Data.convMembers conv
+ let (bots, users) = botsAndUsers $ Data.convMembers conv
key <- mkKey cnv
Data.deleteCode key ReusableCode
now <- liftIO getCurrentTime
- let event = Event ConvCodeDelete cnv usr now EdConvCodeDelete
+ let event = Event ConvCodeDelete qcnv qusr now EdConvCodeDelete
pushEvent event users bots zcon
pure event
@@ -443,7 +474,7 @@ joinConversation zusr zcon cnv access = do
-- NOTE: When joining conversations, all users become members
-- as this is our desired behavior for these types of conversations
-- where there is no way to control who joins, etc.
- mems <- botsAndUsers (Data.convMembers conv)
+ let mems = botsAndUsers (Data.convMembers conv)
addToConversation mems (zusr, roleNameWireMember) zcon ((,roleNameWireMember) <$> newUsers) [] conv
addMembersH :: UserId ::: ConnId ::: ConvId ::: JsonRequest Public.Invite -> Galley Response
@@ -460,13 +491,11 @@ mapUpdateToServant :: UpdateResult -> Galley (Union UpdateResponses)
mapUpdateToServant (Updated e) = Servant.respond $ WithStatus @200 e
mapUpdateToServant Unchanged = Servant.respond NoContent
--- FUTUREWORK(federation): Before 'addMembers' in its current form can be made
--- public by exposing 'addMembersToConversationV2' (currently hidden using /i/
--- prefix), i.e. by allowing remote members to be actually added in any environment,
--- we need the following checks/implementation:
--- - (1) Remote qualified users must exist before they can be added (a call to the
--- respective backend should be made): Avoid clients making up random Ids, and
--- increase the chances that the updateConversationMembership call suceeds
+-- FUTUREWORK(federation): we need the following checks/implementation:
+-- - (1) [DONE] Remote qualified users must exist before they can be added (a
+-- call to the respective backend should be made): Avoid clients making up random
+-- Ids, and increase the chances that the updateConversationMemberships call
+-- suceeds
-- - (2) A call must be made to the remote backend informing it that this user is
-- now part of that conversation. Use and implement 'updateConversationMemberships'.
-- - that call should probably be made *after* inserting the conversation membership
@@ -478,7 +507,7 @@ mapUpdateToServant Unchanged = Servant.respond NoContent
addMembers :: UserId -> ConnId -> ConvId -> Public.InviteQualified -> Galley UpdateResult
addMembers zusr zcon convId invite = do
conv <- Data.conversation convId >>= ifNothing convNotFound
- mems <- botsAndUsers (Data.convMembers conv)
+ let mems = botsAndUsers (Data.convMembers conv)
self <- getSelfMember zusr (snd mems)
ensureActionAllowed AddConversationMember self
let invitedUsers = toList $ Public.invQUsers invite
@@ -489,15 +518,14 @@ addMembers zusr zcon convId invite = do
ensureMemberLimit (toList $ Data.convMembers conv) newLocals newRemotes
ensureAccess conv InviteAccess
ensureConvRoleNotElevated self (invQRoleName invite)
- case Data.convTeam conv of
- Nothing -> do
- ensureAccessRole (Data.convAccessRole conv) (zip newLocals $ repeat Nothing)
- ensureConnectedOrSameTeam zusr newLocals
- Just ti -> teamConvChecks ti newLocals conv
+ checkLocals conv (Data.convTeam conv) newLocals
+ checkRemoteUsersExist newRemotes
addToConversation mems (zusr, memConvRoleName self) zcon ((,invQRoleName invite) <$> newLocals) ((,invQRoleName invite) <$> newRemotes) conv
where
userIsMember u = (^. userId . to (== u))
- teamConvChecks tid newUsers conv = do
+
+ checkLocals :: Data.Conversation -> Maybe TeamId -> [UserId] -> Galley ()
+ checkLocals conv (Just tid) newUsers = do
tms <- Data.teamMembersLimited tid newUsers
let userMembershipMap = map (\u -> (u, find (userIsMember u) tms)) newUsers
ensureAccessRole (Data.convAccessRole conv) userMembershipMap
@@ -505,6 +533,9 @@ addMembers zusr zcon convId invite = do
when (maybe True (view managedConversation) tcv) $
throwM noAddToManaged
ensureConnectedOrSameTeam zusr newUsers
+ checkLocals conv Nothing newUsers = do
+ ensureAccessRole (Data.convAccessRole conv) (zip newUsers $ repeat Nothing)
+ ensureConnectedOrSameTeam zusr newUsers
updateSelfMemberH :: UserId ::: ConnId ::: ConvId ::: JsonRequest Public.MemberUpdate -> Galley Response
updateSelfMemberH (zusr ::: zcon ::: cid ::: req) = do
@@ -531,7 +562,7 @@ updateOtherMember zusr zcon cid victim update = do
when (zusr == victim) $
throwM invalidTargetUserOp
conv <- getConversationAndCheckMembership zusr cid
- (bots, users) <- botsAndUsers (Data.convMembers conv)
+ let (bots, users) = botsAndUsers (Data.convMembers conv)
ensureActionAllowed ModifyOtherConversationMember =<< getSelfMember zusr users
memTarget <- getOtherMember victim users
e <- processUpdateMemberEvent zusr zcon cid users memTarget (memberUpdate {mupConvRoleName = omuConvRoleName update})
@@ -543,9 +574,10 @@ removeMemberH (zusr ::: zcon ::: cid ::: victim) = do
removeMember :: UserId -> ConnId -> ConvId -> UserId -> Galley UpdateResult
removeMember zusr zcon convId victim = do
+ localDomain <- viewFederationDomain
-- FUTUREWORK(federation, #1274): forward request to conversation's backend.
conv <- Data.conversation convId >>= ifNothing convNotFound
- (bots, users) <- botsAndUsers (Data.convMembers conv)
+ let (bots, users) = botsAndUsers (Data.convMembers conv)
genConvChecks conv users
case Data.convTeam conv of
Nothing -> pure ()
@@ -553,9 +585,9 @@ removeMember zusr zcon convId victim = do
if victim `isMember` users
then do
-- FUTUREWORK: deal with remote members, too, see removeMembers
- event <- Data.removeLocalMembers conv zusr (singleton victim)
+ event <- Data.removeLocalMembers localDomain conv zusr (singleton victim)
-- FUTUREWORK(federation, #1274): users can be on other backend, how to notify it?
- for_ (newPush ListComplete (evtFrom event) (ConvEvent event) (recipient <$> users)) $ \p ->
+ for_ (newPush ListComplete zusr (ConvEvent event) (recipient <$> users)) $ \p ->
push1 $ p & pushConn ?~ zcon
void . forkIO $ void $ External.deliver (bots `zip` repeat event)
pure $ Updated event
@@ -576,49 +608,75 @@ removeMember zusr zcon convId victim = do
data OtrResult
= OtrSent !Public.ClientMismatch
| OtrMissingRecipients !Public.ClientMismatch
+ | OtrUnknownClient !Public.UnknownClient
+ | OtrConversationNotFound !Public.ConversationNotFound
-handleOtrResult :: OtrResult -> Response
+handleOtrResult :: OtrResult -> Galley Response
handleOtrResult = \case
- OtrSent m -> json m & setStatus status201
- OtrMissingRecipients m -> json m & setStatus status412
+ OtrSent m -> pure $ json m & setStatus status201
+ OtrMissingRecipients m -> pure $ json m & setStatus status412
+ OtrUnknownClient _ -> throwM unknownClient
+ OtrConversationNotFound _ -> throwM convNotFound
postBotMessageH :: BotId ::: ConvId ::: Public.OtrFilterMissing ::: JsonRequest Public.NewOtrMessage ::: JSON -> Galley Response
postBotMessageH (zbot ::: zcnv ::: val ::: req ::: _) = do
message <- fromJsonBody req
let val' = allowOtrFilterMissingInBody val message
- handleOtrResult <$> postBotMessage zbot zcnv val' message
+ handleOtrResult =<< postBotMessage zbot zcnv val' message
+
+data LegalholdProtectee'
+ = ProtectedUser' UserId
+ | UnprotectedBot' UserId
+ deriving (Show, Eq, Ord, Generic)
+
+legalholdProtectee'2LegalholdProtectee :: LegalholdProtectee' -> LegalholdProtectee
+legalholdProtectee'2LegalholdProtectee (ProtectedUser' uid) = ProtectedUser uid
+legalholdProtectee'2LegalholdProtectee (UnprotectedBot' _uid) = UnprotectedBot
+
+legalholdProtectee'2UserId :: LegalholdProtectee' -> UserId
+legalholdProtectee'2UserId (ProtectedUser' uid) = uid
+legalholdProtectee'2UserId (UnprotectedBot' uid) = uid
postBotMessage :: BotId -> ConvId -> Public.OtrFilterMissing -> Public.NewOtrMessage -> Galley OtrResult
postBotMessage zbot zcnv val message = do
- postNewOtrMessage (botUserId zbot) Nothing zcnv val message
+ postNewOtrMessage (UnprotectedBot' $ botUserId zbot) Nothing zcnv val message
postProtoOtrMessageH :: UserId ::: ConnId ::: ConvId ::: Public.OtrFilterMissing ::: Request ::: Media "application" "x-protobuf" -> Galley Response
postProtoOtrMessageH (zusr ::: zcon ::: cnv ::: val ::: req ::: _) = do
message <- Proto.toNewOtrMessage <$> fromProtoBody req
let val' = allowOtrFilterMissingInBody val message
- handleOtrResult <$> postOtrMessage zusr zcon cnv val' message
-
-postOtrMessageH :: UserId ::: ConnId ::: ConvId ::: Public.OtrFilterMissing ::: JsonRequest Public.NewOtrMessage -> Galley Response
-postOtrMessageH (zusr ::: zcon ::: cnv ::: val ::: req) = do
- message <- fromJsonBody req
- let val' = allowOtrFilterMissingInBody val message
- handleOtrResult <$> postOtrMessage zusr zcon cnv val' message
+ handleOtrResult =<< postNewOtrMessage (ProtectedUser' zusr) (Just zcon) cnv val' message
-postOtrMessage :: UserId -> ConnId -> ConvId -> Public.OtrFilterMissing -> Public.NewOtrMessage -> Galley OtrResult
-postOtrMessage zusr zcon cnv val message =
- postNewOtrMessage zusr (Just zcon) cnv val message
+postOtrMessage :: UserId -> ConnId -> ConvId -> Maybe Public.IgnoreMissing -> Maybe Public.ReportMissing -> Public.NewOtrMessage -> Galley (Union GalleyAPI.PostOtrResponses)
+postOtrMessage zusr zcon cnv ignoreMissing reportMissing message = do
+ let queryParamIndication = resolveQueryMissingOptions ignoreMissing reportMissing
+ overallResovedMissingOptions = allowOtrFilterMissingInBody queryParamIndication message
+ translateToServant =<< postNewOtrMessage (ProtectedUser' zusr) (Just zcon) cnv overallResovedMissingOptions message
+ where
+ translateToServant :: OtrResult -> Galley (Union GalleyAPI.PostOtrResponses)
+ translateToServant (OtrSent mismatch) = Servant.respond (WithStatus @201 mismatch)
+ translateToServant (OtrMissingRecipients mismatch) = Servant.respond (WithStatus @412 mismatch)
+ translateToServant (OtrUnknownClient e) = Servant.respond e
+ translateToServant (OtrConversationNotFound e) = Servant.respond e
+
+ resolveQueryMissingOptions :: Maybe Public.IgnoreMissing -> Maybe Public.ReportMissing -> Public.OtrFilterMissing
+ resolveQueryMissingOptions Nothing Nothing = Public.OtrReportAllMissing
+ resolveQueryMissingOptions (Just Public.IgnoreMissingAll) _ = Public.OtrIgnoreAllMissing
+ resolveQueryMissingOptions (Just (Public.IgnoreMissingList uids)) _ = Public.OtrIgnoreMissing uids
+ resolveQueryMissingOptions Nothing (Just Public.ReportMissingAll) = Public.OtrReportAllMissing
+ resolveQueryMissingOptions Nothing (Just (Public.ReportMissingList uids)) = Public.OtrReportMissing uids
postProtoOtrBroadcastH :: UserId ::: ConnId ::: Public.OtrFilterMissing ::: Request ::: JSON -> Galley Response
postProtoOtrBroadcastH (zusr ::: zcon ::: val ::: req ::: _) = do
message <- Proto.toNewOtrMessage <$> fromProtoBody req
let val' = allowOtrFilterMissingInBody val message
- handleOtrResult <$> postOtrBroadcast zusr zcon val' message
+ handleOtrResult =<< postOtrBroadcast zusr zcon val' message
postOtrBroadcastH :: UserId ::: ConnId ::: Public.OtrFilterMissing ::: JsonRequest Public.NewOtrMessage -> Galley Response
postOtrBroadcastH (zusr ::: zcon ::: val ::: req) = do
message <- fromJsonBody req
let val' = allowOtrFilterMissingInBody val message
- handleOtrResult <$> postOtrBroadcast zusr zcon val' message
+ handleOtrResult =<< postOtrBroadcast zusr zcon val' message
postOtrBroadcast :: UserId -> ConnId -> Public.OtrFilterMissing -> Public.NewOtrMessage -> Galley OtrResult
postOtrBroadcast zusr zcon val message =
@@ -637,36 +695,42 @@ allowOtrFilterMissingInBody val (NewOtrMessage _ _ _ _ _ _ mrepmiss) = case mrep
-- | bots are not supported on broadcast
postNewOtrBroadcast :: UserId -> Maybe ConnId -> OtrFilterMissing -> NewOtrMessage -> Galley OtrResult
postNewOtrBroadcast usr con val msg = do
- let sender = newOtrSender msg
- let recvrs = newOtrRecipients msg
+ localDomain <- viewFederationDomain
+ let qusr = Qualified usr localDomain
+ sender = newOtrSender msg
+ recvrs = newOtrRecipients msg
now <- liftIO getCurrentTime
withValidOtrBroadcastRecipients usr sender recvrs val now $ \rs -> do
- let (_, toUsers) = foldr (newMessage usr con Nothing msg now) ([], []) rs
+ let (_, toUsers) = foldr (newMessage qusr con Nothing msg now) ([], []) rs
pushSome (catMaybes toUsers)
-postNewOtrMessage :: UserId -> Maybe ConnId -> ConvId -> OtrFilterMissing -> NewOtrMessage -> Galley OtrResult
-postNewOtrMessage usr con cnv val msg = do
- let sender = newOtrSender msg
- let recvrs = newOtrRecipients msg
+postNewOtrMessage :: LegalholdProtectee' -> Maybe ConnId -> ConvId -> OtrFilterMissing -> NewOtrMessage -> Galley OtrResult
+postNewOtrMessage protectee con cnv val msg = do
+ localDomain <- viewFederationDomain
+ let usr = legalholdProtectee'2UserId protectee
+ qusr = Qualified usr localDomain
+ qcnv = Qualified cnv localDomain
+ sender = newOtrSender msg
+ recvrs = newOtrRecipients msg
now <- liftIO getCurrentTime
- withValidOtrRecipients usr sender cnv recvrs val now $ \rs -> do
- let (toBots, toUsers) = foldr (newMessage usr con (Just cnv) msg now) ([], []) rs
+ withValidOtrRecipients protectee sender cnv recvrs val now $ \rs -> do
+ let (toBots, toUsers) = foldr (newMessage qusr con (Just qcnv) msg now) ([], []) rs
pushSome (catMaybes toUsers)
void . forkIO $ do
gone <- External.deliver toBots
mapM_ (deleteBot cnv . botMemId) gone
newMessage ::
- UserId ->
+ Qualified UserId ->
Maybe ConnId ->
-- | Conversation Id (if Nothing, recipient's self conversation is used)
- Maybe ConvId ->
+ Maybe (Qualified ConvId) ->
NewOtrMessage ->
UTCTime ->
(LocalMember, ClientId, Text) ->
([(BotMember, Event)], [Maybe Push]) ->
([(BotMember, Event)], [Maybe Push])
-newMessage usr con cnv msg now (m, c, t) ~(toBots, toUsers) =
+newMessage qusr con qcnv msg now (m, c, t) ~(toBots, toUsers) =
let o =
OtrMessage
{ otrSender = newOtrSender msg,
@@ -676,14 +740,15 @@ newMessage usr con cnv msg now (m, c, t) ~(toBots, toUsers) =
}
-- use recipient's client's self conversation on broadcast
-- (with federation, this might not work for remote members)
- conv = fromMaybe (selfConv $ memId m) cnv
- e = Event OtrMessageAdd conv usr now (EdOtrMessage o)
+ -- FUTUREWORK: for remote recipients, set the domain correctly here
+ qconv = fromMaybe ((`Qualified` (qDomain qusr)) . selfConv $ memId m) qcnv
+ e = Event OtrMessageAdd qconv qusr now (EdOtrMessage o)
r = recipient m & recipientClients .~ RecipientClientsSome (singleton c)
in case newBotMember m of
Just b -> ((b, e) : toBots, toUsers)
Nothing ->
let p =
- newPush ListComplete (evtFrom e) (ConvEvent e) [r]
+ newPush ListComplete (qUnqualified (evtFrom e)) (ConvEvent e) [r]
<&> set pushConn con
. set pushNativePriority (newOtrNativePriority msg)
. set pushRoute (bool RouteDirect RouteAny (newOtrNativePush msg))
@@ -702,17 +767,20 @@ updateConversationNameH (zusr ::: zcon ::: cnv ::: req) = do
updateConversationName :: UserId -> ConnId -> ConvId -> Public.ConversationRename -> Galley Public.Event
updateConversationName zusr zcon cnv convRename = do
+ localDomain <- viewFederationDomain
+ let qcnv = Qualified cnv localDomain
+ qusr = Qualified zusr localDomain
alive <- Data.isConvAlive cnv
unless alive $ do
Data.deleteConversation cnv
throwM convNotFound
- (bots, users) <- botsAndUsers =<< Data.members cnv
+ (bots, users) <- botsAndUsers <$> Data.members cnv
ensureActionAllowed ModifyConversationName =<< getSelfMember zusr users
now <- liftIO getCurrentTime
cn <- rangeChecked (cupName convRename)
Data.updateConversation cnv cn
- let e = Event ConvRename cnv zusr now (EdConvRename convRename)
- for_ (newPush ListComplete (evtFrom e) (ConvEvent e) (recipient <$> users)) $ \p ->
+ let e = Event ConvRename qcnv qusr now (EdConvRename convRename)
+ for_ (newPush ListComplete zusr (ConvEvent e) (recipient <$> users)) $ \p ->
push1 $ p & pushConn ?~ zcon
void . forkIO $ void $ External.deliver (bots `zip` repeat e)
return e
@@ -725,12 +793,15 @@ isTypingH (zusr ::: zcon ::: cnv ::: req) = do
isTyping :: UserId -> ConnId -> ConvId -> Public.TypingData -> Galley ()
isTyping zusr zcon cnv typingData = do
+ localDomain <- viewFederationDomain
+ let qcnv = Qualified cnv localDomain
+ qusr = Qualified zusr localDomain
mm <- Data.members cnv
unless (zusr `isMember` mm) $
throwM convNotFound
now <- liftIO getCurrentTime
- let e = Event Typing cnv zusr now (EdTyping typingData)
- for_ (newPush ListComplete (evtFrom e) (ConvEvent e) (recipient <$> mm)) $ \p ->
+ let e = Event Typing qcnv qusr now (EdTyping typingData)
+ for_ (newPush ListComplete zusr (ConvEvent e) (recipient <$> mm)) $ \p ->
push1 $
p
& pushConn ?~ zcon
@@ -754,20 +825,22 @@ addBotH (zusr ::: zcon ::: req) = do
addBot :: UserId -> ConnId -> AddBot -> Galley Event
addBot zusr zcon b = do
+ localDomain <- viewFederationDomain
+ let qusr = Qualified zusr localDomain
c <- Data.conversation (b ^. addBotConv) >>= ifNothing convNotFound
-- Check some preconditions on adding bots to a conversation
for_ (Data.convTeam c) $ teamConvChecks (b ^. addBotConv)
(bots, users) <- regularConvChecks c
t <- liftIO getCurrentTime
Data.updateClient True (botUserId (b ^. addBotId)) (b ^. addBotClient)
- (e, bm) <- Data.addBotMember zusr (b ^. addBotService) (b ^. addBotId) (b ^. addBotConv) t
- for_ (newPush ListComplete (evtFrom e) (ConvEvent e) (recipient <$> users)) $ \p ->
+ (e, bm) <- Data.addBotMember qusr (b ^. addBotService) (b ^. addBotId) (b ^. addBotConv) t
+ for_ (newPush ListComplete zusr (ConvEvent e) (recipient <$> users)) $ \p ->
push1 $ p & pushConn ?~ zcon
void . forkIO $ void $ External.deliver ((bm : bots) `zip` repeat e)
pure e
where
regularConvChecks c = do
- (bots, users) <- botsAndUsers (Data.convMembers c)
+ let (bots, users) = botsAndUsers (Data.convMembers c)
unless (zusr `isMember` users) $
throwM convNotFound
ensureGroupConv c
@@ -788,16 +861,19 @@ rmBotH (zusr ::: zcon ::: req) = do
rmBot :: UserId -> Maybe ConnId -> RemoveBot -> Galley UpdateResult
rmBot zusr zcon b = do
c <- Data.conversation (b ^. rmBotConv) >>= ifNothing convNotFound
+ localDomain <- viewFederationDomain
+ let qcnv = Qualified (Data.convId c) localDomain
+ qusr = Qualified zusr localDomain
unless (zusr `isMember` Data.convMembers c) $
throwM convNotFound
- (bots, users) <- botsAndUsers (Data.convMembers c)
+ let (bots, users) = botsAndUsers (Data.convMembers c)
if not (any ((== b ^. rmBotId) . botMemId) bots)
then pure Unchanged
else do
t <- liftIO getCurrentTime
let evd = EdMembersLeave (UserIdList [botUserId (b ^. rmBotId)])
- let e = Event MemberLeave (Data.convId c) zusr t evd
- for_ (newPush ListComplete (evtFrom e) (ConvEvent e) (recipient <$> users)) $ \p ->
+ let e = Event MemberLeave qcnv qusr t evd
+ for_ (newPush ListComplete zusr (ConvEvent e) (recipient <$> users)) $ \p ->
push1 $ p & pushConn .~ zcon
Data.removeMember (botUserId (b ^. rmBotId)) (Data.convId c)
Data.eraseClients (botUserId (b ^. rmBotId))
@@ -810,13 +886,14 @@ rmBot zusr zcon b = do
addToConversation :: ([BotMember], [LocalMember]) -> (UserId, RoleName) -> ConnId -> [(UserId, RoleName)] -> [(Remote UserId, RoleName)] -> Data.Conversation -> Galley UpdateResult
addToConversation _ _ _ [] [] _ = pure Unchanged
addToConversation (bots, others) (usr, usrRole) conn locals remotes c = do
+ localDomain <- viewFederationDomain
ensureGroupConv c
mems <- checkedMemberAddSize locals remotes
now <- liftIO getCurrentTime
- (e, mm, _remotes) <- Data.addMembersWithRole now (Data.convId c) (usr, usrRole) mems
+ (e, mm, _remotes) <- Data.addMembersWithRole localDomain now (Data.convId c) (usr, usrRole) mems
-- FUTUREWORK: send events to '_remotes' users here, too
let allMembers = nubOrdOn memId (toList mm <> others)
- for_ (newPush ListComplete (evtFrom e) (ConvEvent e) (recipient <$> allMembers)) $ \p ->
+ for_ (newPush ListComplete usr (ConvEvent e) (recipient <$> allMembers)) $ \p ->
push1 $ p & pushConn ?~ conn
void . forkIO $ void $ External.deliver (bots `zip` repeat e)
pure $ Updated e
@@ -860,11 +937,14 @@ processUpdateMemberEvent ::
MemberUpdate ->
Galley Event
processUpdateMemberEvent zusr zcon cid users target update = do
+ localDomain <- viewFederationDomain
+ let qcnv = Qualified cid localDomain
+ qusr = Qualified zusr localDomain
up <- Data.updateMember cid (memId target) update
now <- liftIO getCurrentTime
- let e = Event MemberStateUpdate cid zusr now (EdMemberUpdate up)
+ let e = Event MemberStateUpdate qcnv qusr now (EdMemberUpdate up)
let recipients = fmap recipient (target : filter ((/= memId target) . memId) users)
- for_ (newPush ListComplete (evtFrom e) (ConvEvent e) recipients) $ \p ->
+ for_ (newPush ListComplete zusr (ConvEvent e) recipients) $ \p ->
push1 $
p
& pushConn ?~ zcon
@@ -885,6 +965,7 @@ data CheckedOtrRecipients
| -- | Invalid sender (client).
InvalidOtrSenderClient
+-- | bots are not supported on broadcast
withValidOtrBroadcastRecipients ::
UserId ->
ClientId ->
@@ -913,7 +994,7 @@ withValidOtrBroadcastRecipients usr clt rcps val now go = Teams.withBindingTeam
then Clients.fromUserClients <$> Intra.lookupClients users
else Data.lookupClients users
let membs = Data.newMember <$> users
- handleOtrResponse usr clt rcps membs clts val now go
+ handleOtrResponse (ProtectedUser' usr) clt rcps membs clts val now go
where
maybeFetchLimitedTeamMemberList limit tid uListInFilter = do
-- Get the users in the filter (remote ids are not in a local team)
@@ -930,7 +1011,7 @@ withValidOtrBroadcastRecipients usr clt rcps val now go = Teams.withBindingTeam
pure (mems ^. teamMembers)
withValidOtrRecipients ::
- UserId ->
+ LegalholdProtectee' ->
ClientId ->
ConvId ->
OtrRecipients ->
@@ -938,24 +1019,26 @@ withValidOtrRecipients ::
UTCTime ->
([(LocalMember, ClientId, Text)] -> Galley ()) ->
Galley OtrResult
-withValidOtrRecipients usr clt cnv rcps val now go = do
+withValidOtrRecipients protectee clt cnv rcps val now go = do
alive <- Data.isConvAlive cnv
- unless alive $ do
- Data.deleteConversation cnv
- throwM convNotFound
- -- FUTUREWORK(federation): also handle remote members
- localMembers <- Data.members cnv
- let localMemberIds = memId <$> localMembers
- isInternal <- view $ options . optSettings . setIntraListing
- clts <-
- if isInternal
- then Clients.fromUserClients <$> Intra.lookupClients localMemberIds
- else Data.lookupClients localMemberIds
- handleOtrResponse usr clt rcps localMembers clts val now go
+ if not alive
+ then do
+ Data.deleteConversation cnv
+ pure $ OtrConversationNotFound Public.convNotFound
+ else do
+ -- FUTUREWORK(federation): also handle remote members
+ (FutureWork @'LegalholdPlusFederationNotImplemented -> _remoteMembers, localMembers) <- (undefined,) <$> Data.members cnv
+ let localMemberIds = memId <$> localMembers
+ isInternal <- view $ options . optSettings . setIntraListing
+ clts <-
+ if isInternal
+ then Clients.fromUserClients <$> Intra.lookupClients localMemberIds
+ else Data.lookupClients localMemberIds
+ handleOtrResponse protectee clt rcps localMembers clts val now go
handleOtrResponse ::
-- | Proposed sender (user)
- UserId ->
+ LegalholdProtectee' ->
-- | Proposed sender (client)
ClientId ->
-- | Proposed recipients (users & clients).
@@ -971,11 +1054,13 @@ handleOtrResponse ::
-- | Callback if OtrRecipients are valid
([(LocalMember, ClientId, Text)] -> Galley ()) ->
Galley OtrResult
-handleOtrResponse usr clt rcps membs clts val now go = case checkOtrRecipients usr clt rcps membs clts val now of
+handleOtrResponse protectee clt rcps membs clts val now go = case checkOtrRecipients (legalholdProtectee'2UserId protectee) clt rcps membs clts val now of
ValidOtrRecipients m r -> go r >> pure (OtrSent m)
- MissingOtrRecipients m -> pure (OtrMissingRecipients m)
- InvalidOtrSenderUser -> throwM convNotFound
- InvalidOtrSenderClient -> throwM unknownClient
+ MissingOtrRecipients m -> do
+ guardLegalholdPolicyConflicts (legalholdProtectee'2LegalholdProtectee protectee) (missingClients m)
+ pure (OtrMissingRecipients m)
+ InvalidOtrSenderUser -> pure $ OtrConversationNotFound Public.convNotFound
+ InvalidOtrSenderClient -> pure $ OtrUnknownClient Public.unknownClient
-- | Check OTR sender and recipients for validity and completeness
-- against a given list of valid members and clients, optionally
@@ -1046,7 +1131,7 @@ checkOtrRecipients usr sid prs vms vcs val now
mismatch :: ClientMismatch
mismatch =
ClientMismatch
- { cmismatchTime = now,
+ { cmismatchTime = toUTCTimeMillis now,
missingClients = UserClients (Clients.toMap missing),
redundantClients = UserClients (Clients.toMap redundant),
deletedClients = UserClients (Clients.toMap deleted)
@@ -1058,3 +1143,94 @@ checkOtrRecipients usr sid prs vms vcs val now
OtrIgnoreAllMissing -> Clients.nil
OtrReportMissing us -> Clients.filter (`Set.member` us) miss
OtrIgnoreMissing us -> Clients.filter (`Set.notMember` us) miss
+
+-- | If user has legalhold status `no_consent` or has client devices that have no legalhold
+-- capability, and some of the clients she is about to get connected are LH devices, respond
+-- with 412 and do not process notification.
+--
+-- This is a fallback safeguard that shouldn't get triggered if backend and clients work as
+-- intended.
+guardLegalholdPolicyConflicts :: LegalholdProtectee -> UserClients -> Galley ()
+guardLegalholdPolicyConflicts LegalholdPlusFederationNotImplemented _otherClients = pure ()
+guardLegalholdPolicyConflicts UnprotectedBot _otherClients = pure ()
+guardLegalholdPolicyConflicts (ProtectedUser self) otherClients = do
+ view (options . optSettings . setFeatureFlags . flagLegalHold) >>= \case
+ FeatureLegalHoldDisabledPermanently -> case FutureWork @'LegalholdPlusFederationNotImplemented () of
+ FutureWork () -> pure () -- FUTUREWORK: if federation is enabled, we still need to run the guard!
+ FeatureLegalHoldDisabledByDefault -> guardLegalholdPolicyConflictsUid self otherClients
+ FeatureLegalHoldWhitelistTeamsAndImplicitConsent -> guardLegalholdPolicyConflictsUid self otherClients
+
+guardLegalholdPolicyConflictsUid :: UserId -> UserClients -> Galley ()
+guardLegalholdPolicyConflictsUid self otherClients = do
+ let otherCids :: [ClientId]
+ otherCids = Set.toList . Set.unions . Map.elems . userClients $ otherClients
+
+ otherUids :: [UserId]
+ otherUids = nub $ Map.keys . userClients $ otherClients
+
+ when (nub otherUids /= [self {- if all other clients belong to us, there can be no conflict -}]) $ do
+ allClients :: UserClientsFull <- Intra.lookupClientsFull (nub $ self : otherUids)
+
+ let selfClients :: [Client.Client] =
+ allClients
+ & Client.userClientsFull
+ & Map.lookup self
+ & fromMaybe Set.empty
+ & Set.toList
+
+ otherClientHasLH :: Bool
+ otherClientHasLH =
+ let clients =
+ allClients
+ & Client.userClientsFull
+ & Map.delete self
+ & Map.elems
+ & Set.unions
+ & Set.toList
+ & filter ((`elem` otherCids) . Client.clientId)
+ in Client.LegalHoldClientType `elem` (Client.clientType <$> clients)
+
+ checkSelfHasLHClients :: Bool
+ checkSelfHasLHClients =
+ any ((== Client.LegalHoldClientType) . Client.clientType) selfClients
+
+ checkSelfHasOldClients :: Bool
+ checkSelfHasOldClients =
+ any isOld selfClients
+ where
+ isOld :: Client.Client -> Bool
+ isOld =
+ (Client.ClientSupportsLegalholdImplicitConsent `Set.notMember`)
+ . Client.fromClientCapabilityList
+ . Client.clientCapabilities
+
+ checkConsentMissing :: Galley Bool
+ checkConsentMissing = do
+ -- (we could also get the profile from brig. would make the code slightly more
+ -- concise, but not really help with the rpc back-and-forth, so, like, why?)
+ mbUser <- accountUser <$$> getUser self
+ mbTeamMember <- join <$> for (mbUser >>= userTeam) (`teamMember` self)
+ let lhStatus = maybe defUserLegalHoldStatus (view legalHoldStatus) mbTeamMember
+ pure (lhStatus == UserLegalHoldNoConsent)
+
+ Log.debug $
+ Log.field "self" (toByteString' self)
+ Log.~~ Log.field "otherClients" (toByteString' $ show otherClients)
+ Log.~~ Log.field "otherClientHasLH" (toByteString' otherClientHasLH)
+ Log.~~ Log.field "checkSelfHasOldClients" (toByteString' checkSelfHasOldClients)
+ Log.~~ Log.field "checkSelfHasLHClients" (toByteString' checkSelfHasLHClients)
+ Log.~~ Log.msg ("guardLegalholdPolicyConflicts[1]" :: Text)
+
+ -- (I've tried to order the following checks for minimum IO; did it work? ~~fisx)
+ when otherClientHasLH $ do
+ when checkSelfHasOldClients $ do
+ Log.debug $ Log.msg ("guardLegalholdPolicyConflicts[2]: old clients" :: Text)
+ throwM missingLegalholdConsent
+
+ unless checkSelfHasLHClients {- carrying a LH device implies having granted LH consent -} $ do
+ whenM checkConsentMissing $ do
+ -- We assume this is impossible, since conversations are automatically
+ -- blocked if LH consent is missing of any participant.
+ -- We add this check here as an extra failsafe.
+ Log.debug $ Log.msg ("guardLegalholdPolicyConflicts[3]: consent missing" :: Text)
+ throwM missingLegalholdConsent
diff --git a/services/galley/src/Galley/API/Util.hs b/services/galley/src/Galley/API/Util.hs
index 88d8c778116..e9b98a0ade5 100644
--- a/services/galley/src/Galley/API/Util.hs
+++ b/services/galley/src/Galley/API/Util.hs
@@ -21,12 +21,15 @@ import Brig.Types (Relation (..))
import Brig.Types.Intra (ReAuthUser (..))
import Control.Lens (view, (.~), (^.))
import Control.Monad.Catch
+import Control.Monad.Except (runExceptT)
import Data.ByteString.Conversion
import Data.Domain (Domain)
import Data.Id as Id
+import qualified Data.Map as Map
import Data.Misc (PlainTextPassword (..))
-import Data.Qualified (Remote)
+import Data.Qualified (Qualified (qUnqualified), Remote, partitionQualified)
import qualified Data.Set as Set
+import Data.Tagged (Tagged (unTagged))
import qualified Data.Text.Lazy as LT
import Data.Time
import Galley.API.Error
@@ -34,20 +37,24 @@ import Galley.App
import qualified Galley.Data as Data
import Galley.Data.Services (BotMember, newBotMember)
import qualified Galley.Data.Types as DataTypes
+import qualified Galley.External as External
import Galley.Intra.Push
import Galley.Intra.User
import Galley.Options (optSettings, setFederationDomain)
import Galley.Types
import Galley.Types.Conversations.Members (RemoteMember (rmId))
import Galley.Types.Conversations.Roles
-import Galley.Types.Teams
+import Galley.Types.Teams hiding (Event)
import Imports
import Network.HTTP.Types
import Network.Wai
import Network.Wai.Predicate hiding (Error)
import Network.Wai.Utilities
-import qualified System.Logger.Class as Log
import UnliftIO (concurrently)
+import qualified Wire.API.Federation.API.Brig as FederatedBrig
+import qualified Wire.API.Federation.Client as Federation
+import Wire.API.Federation.Error (federationErrorToWai)
+import qualified Wire.API.User as User
type JSON = Media "application" "json"
@@ -94,8 +101,8 @@ ensureConnectedToLocals :: UserId -> [UserId] -> Galley ()
ensureConnectedToLocals _ [] = pure ()
ensureConnectedToLocals u uids = do
(connsFrom, connsTo) <-
- getConnections [u] uids (Just Accepted)
- `concurrently` getConnections uids [u] (Just Accepted)
+ getConnections [u] (Just uids) (Just Accepted)
+ `concurrently` getConnections uids (Just [u]) (Just Accepted)
unless (length connsFrom == length uids && length connsTo == length uids) $
throwM notConnected
@@ -131,9 +138,9 @@ ensureConvRoleNotElevated origMember targetRole = do
(_, _) ->
throwM (badRequest "Custom roles not supported")
--- | If a team memeber is not given throw 'notATeamMember'; if the given team
+-- | If a team member is not given throw 'notATeamMember'; if the given team
-- member does not have the given permission, throw 'operationDenied'.
--- Otherwise, return unit.
+-- Otherwise, return the team member.
permissionCheck :: (IsPerm perm, Show perm) => perm -> Maybe TeamMember -> Galley TeamMember
permissionCheck p = \case
Just m -> do
@@ -167,28 +174,30 @@ permissionCheckTeamConv zusr cnv perm =
-- | Try to accept a 1-1 conversation, promoting connect conversations as appropriate.
acceptOne2One :: UserId -> Data.Conversation -> Maybe ConnId -> Galley Data.Conversation
-acceptOne2One usr conv conn = case Data.convType conv of
- One2OneConv ->
- if usr `isMember` mems
- then return conv
- else do
+acceptOne2One usr conv conn = do
+ localDomain <- viewFederationDomain
+ case Data.convType conv of
+ One2OneConv ->
+ if usr `isMember` mems
+ then return conv
+ else do
+ now <- liftIO getCurrentTime
+ mm <- snd <$> Data.addMember localDomain now cid usr
+ return $ conv {Data.convMembers = mems <> toList mm}
+ ConnectConv -> case mems of
+ [_, _] | usr `isMember` mems -> promote
+ [_, _] -> throwM convNotFound
+ _ -> do
+ when (length mems > 2) $
+ throwM badConvState
now <- liftIO getCurrentTime
- mm <- snd <$> Data.addMember now cid usr
- return $ conv {Data.convMembers = mems <> toList mm}
- ConnectConv -> case mems of
- [_, _] | usr `isMember` mems -> promote
- [_, _] -> throwM convNotFound
- _ -> do
- when (length mems > 2) $
- throwM badConvState
- now <- liftIO getCurrentTime
- (e, mm) <- Data.addMember now cid usr
- conv' <- if isJust (find ((usr /=) . memId) mems) then promote else pure conv
- let mems' = mems <> toList mm
- for_ (newPush ListComplete (evtFrom e) (ConvEvent e) (recipient <$> mems')) $ \p ->
- push1 $ p & pushConn .~ conn & pushRoute .~ RouteDirect
- return $ conv' {Data.convMembers = mems'}
- _ -> throwM $ invalidOp "accept: invalid conversation type"
+ (e, mm) <- Data.addMember localDomain now cid usr
+ conv' <- if isJust (find ((usr /=) . memId) mems) then promote else pure conv
+ let mems' = mems <> toList mm
+ for_ (newPush ListComplete (qUnqualified (evtFrom e)) (ConvEvent e) (recipient <$> mems')) $ \p ->
+ push1 $ p & pushConn .~ conn & pushRoute .~ RouteDirect
+ return $ conv' {Data.convMembers = mems'}
+ _ -> throwM $ invalidOp "accept: invalid conversation type"
where
cid = Data.convId conv
mems = Data.convMembers conv
@@ -196,7 +205,7 @@ acceptOne2One usr conv conn = case Data.convType conv of
Data.acceptConnect cid
return $ conv {Data.convType = One2OneConv}
badConvState =
- Error status500 "bad-state" $
+ mkError status500 "bad-state" $
"Connect conversation with more than 2 members: "
<> LT.pack (show cid)
@@ -212,18 +221,13 @@ isRemoteMember u = isJust . find ((u ==) . rmId)
findMember :: Data.Conversation -> UserId -> Maybe LocalMember
findMember c u = find ((u ==) . memId) (Data.convMembers c)
-botsAndUsers :: (Log.MonadLogger m, Traversable t) => t LocalMember -> m ([BotMember], [LocalMember])
-botsAndUsers = fmap fold . traverse botOrUser
+botsAndUsers :: Foldable f => f LocalMember -> ([BotMember], [LocalMember])
+botsAndUsers = foldMap botOrUser
where
botOrUser m = case memService m of
- Just _ -> do
- -- we drop invalid bots here, which shouldn't happen
- bot <- mkBotMember m
- pure (toList bot, [])
- Nothing ->
- pure ([], [m])
- mkBotMember :: Log.MonadLogger m => LocalMember -> m (Maybe BotMember)
- mkBotMember m = pure $ newBotMember m
+ -- we drop invalid bots here, which shouldn't happen
+ Just _ -> (toList (newBotMember m), [])
+ Nothing -> ([], [m])
location :: ToByteString a => a -> Response -> Response
location = addHeader hLocation . toByteString'
@@ -293,8 +297,35 @@ canDeleteMember deleter deletee
-- here, so we pick a reasonable default.)
getRole mem = fromMaybe RoleMember $ permissionsRole $ mem ^. permissions
+pushConversationEvent :: Event -> [UserId] -> [BotMember] -> Galley ()
+pushConversationEvent e users bots = do
+ for_ (newPush ListComplete (qUnqualified (evtFrom e)) (ConvEvent e) (map userRecipient users)) $ \p ->
+ push1 $ p & pushConn .~ Nothing
+ void . forkIO $ void $ External.deliver (bots `zip` repeat e)
+
--------------------------------------------------------------------------------
-- Federation
viewFederationDomain :: MonadReader Env m => m Domain
viewFederationDomain = view (options . optSettings . setFederationDomain)
+
+checkRemoteUsersExist :: [Remote UserId] -> Galley ()
+checkRemoteUsersExist =
+ -- FUTUREWORK: pooledForConcurrentlyN_ instead of sequential checks per domain
+ traverse_ (uncurry checkRemotesFor)
+ . Map.assocs
+ . partitionQualified
+ . map unTagged
+
+checkRemotesFor :: Domain -> [UserId] -> Galley ()
+checkRemotesFor domain uids = do
+ let rpc = FederatedBrig.getUsersByIds FederatedBrig.clientRoutes uids
+ users <-
+ runExceptT (Federation.executeFederated domain rpc)
+ >>= either (throwM . federationErrorToWai) pure
+ let uids' =
+ map
+ (qUnqualified . User.profileQualifiedId)
+ (filter (not . User.profileDeleted) users)
+ unless (Set.fromList uids == Set.fromList uids') $
+ throwM unknownRemoteUser
diff --git a/services/galley/src/Galley/App.hs b/services/galley/src/Galley/App.hs
index 4ee5a88768a..237196d1532 100644
--- a/services/galley/src/Galley/App.hs
+++ b/services/galley/src/Galley/App.hs
@@ -199,10 +199,11 @@ instance HasRequestId Galley where
createEnv :: Metrics -> Opts -> IO Env
createEnv m o = do
l <- Logger.mkLogger (o ^. optLogLevel) (o ^. optLogNetStrings) (o ^. optLogFormat)
+ cass <- initCassandra o l
mgr <- initHttpManager o
validateOptions l o
- Env def m o l mgr (o ^. optFederator) <$> initCassandra o l
- <*> Q.new 16000
+ Env def m o l mgr (o ^. optFederator) cass
+ <$> Q.new 16000
<*> initExtEnv
<*> maybe (return Nothing) (fmap Just . Aws.mkEnv l mgr) (o ^. optJournal)
diff --git a/services/galley/src/Galley/Data.hs b/services/galley/src/Galley/Data.hs
index 5e446966696..9e65171fc6c 100644
--- a/services/galley/src/Galley/Data.hs
+++ b/services/galley/src/Galley/Data.hs
@@ -79,6 +79,7 @@ module Galley.Data
-- * Conversation Members
addMember,
addMembersWithRole,
+ addLocalMembersToRemoteConv,
member,
members,
removeMember,
@@ -109,10 +110,11 @@ where
import Brig.Types.Code
import Cassandra hiding (Tagged)
import Cassandra.Util
-import Control.Arrow (second)
+import Control.Arrow (first, second)
import Control.Exception (ErrorCall (ErrorCall))
import Control.Lens hiding ((<|))
import Control.Monad.Catch (MonadThrow, throwM)
+import Control.Monad.Extra (ifM)
import Data.ByteString.Conversion hiding (parser)
import Data.Domain (Domain)
import Data.Id as Id
@@ -132,9 +134,9 @@ import qualified Data.UUID.Tagged as U
import Data.UUID.V4 (nextRandom)
import Galley.App
import Galley.Data.Instances ()
+import Galley.Data.LegalHold (isTeamLegalholdWhitelisted)
import qualified Galley.Data.Queries as Cql
import Galley.Data.Types as Data
-import qualified Galley.Options as Opts
import Galley.Types hiding (Conversation)
import Galley.Types.Bot (newServiceRef)
import Galley.Types.Clients (Clients)
@@ -182,7 +184,7 @@ mkResultSet page = ResultSet (result page) typ
| otherwise = ResultSetComplete
schemaVersion :: Int32
-schemaVersion = 49
+schemaVersion = 50
-- | Insert a conversation code
insertCode :: MonadClient m => Code -> m ()
@@ -570,18 +572,19 @@ conversationIdsOf usr cids = runIdentity <$$> retry x1 (query Cql.selectUserConv
createConversation ::
MonadClient m =>
+ Domain ->
UserId ->
Maybe (Range 1 256 Text) ->
[Access] ->
AccessRole ->
- ConvSizeChecked [UserId] ->
+ ConvSizeChecked ([Remote UserId], [UserId]) ->
Maybe ConvTeamInfo ->
-- | Message timer
Maybe Milliseconds ->
Maybe ReceiptMode ->
RoleName ->
m Conversation
-createConversation usr name acc role others tinfo mtimer recpt othersConversationRole = do
+createConversation localDomain usr name acc role others tinfo mtimer recpt othersConversationRole = do
conv <- Id <$> liftIO nextRandom
now <- liftIO getCurrentTime
retry x5 $ case tinfo of
@@ -592,49 +595,51 @@ createConversation usr name acc role others tinfo mtimer recpt othersConversatio
setConsistency Quorum
addPrepQuery Cql.insertConv (conv, RegularConv, usr, Set (toList acc), role, fromRange <$> name, Just (cnvTeamId ti), mtimer, recpt)
addPrepQuery Cql.insertTeamConv (cnvTeamId ti, conv, cnvManaged ti)
- -- FUTUREWORK: split users into list of remote and local users
- let remoteUsers :: [Remote UserId]
- remoteUsers = []
- (_, mems, rMems) <- addMembersUncheckedWithRole now conv (usr, roleNameWireAdmin) (toList $ list1 (usr, roleNameWireAdmin) ((,othersConversationRole) <$> fromConvSize others)) ((,othersConversationRole) <$> remoteUsers)
+ let (remoteUsers, localUsers) = fromConvSize others
+ (_, mems, rMems) <- addMembersUncheckedWithRole localDomain now conv (usr, roleNameWireAdmin) (toList $ list1 (usr, roleNameWireAdmin) ((,othersConversationRole) <$> localUsers)) ((,othersConversationRole) <$> remoteUsers)
return $ newConv conv RegularConv usr mems rMems acc role name (cnvTeamId <$> tinfo) mtimer recpt
-createSelfConversation :: MonadClient m => UserId -> Maybe (Range 1 256 Text) -> m Conversation
-createSelfConversation usr name = do
+createSelfConversation :: MonadClient m => Domain -> UserId -> Maybe (Range 1 256 Text) -> m Conversation
+createSelfConversation localDomain usr name = do
let conv = selfConv usr
now <- liftIO getCurrentTime
retry x5 $
write Cql.insertConv (params Quorum (conv, SelfConv, usr, privateOnly, privateRole, fromRange <$> name, Nothing, Nothing, Nothing))
- mems <- snd <$> addLocalMembersUnchecked now conv usr (singleton usr)
+ mems <- snd <$> addLocalMembersUnchecked localDomain now conv usr (singleton usr)
return $ newConv conv SelfConv usr (toList mems) [] [PrivateAccess] privateRole name Nothing Nothing Nothing
createConnectConversation ::
MonadClient m =>
+ Domain ->
U.UUID U.V4 ->
U.UUID U.V4 ->
Maybe (Range 1 256 Text) ->
Connect ->
m (Conversation, Event)
-createConnectConversation a b name conn = do
+createConnectConversation localDomain a b name conn = do
let conv = one2OneConvId a b
+ qconv = Qualified conv localDomain
a' = Id . U.unpack $ a
+ qa' = Qualified a' localDomain
now <- liftIO getCurrentTime
retry x5 $
write Cql.insertConv (params Quorum (conv, ConnectConv, a', privateOnly, privateRole, fromRange <$> name, Nothing, Nothing, Nothing))
-- We add only one member, second one gets added later,
-- when the other user accepts the connection request.
- mems <- snd <$> addLocalMembersUnchecked now conv a' (singleton a')
- let e = Event ConvConnect conv a' now (EdConnect conn)
+ mems <- snd <$> addLocalMembersUnchecked localDomain now conv a' (singleton a')
+ let e = Event ConvConnect qconv qa' now (EdConnect conn)
let remoteMembers = [] -- FUTUREWORK: federated connections
return (newConv conv ConnectConv a' (toList mems) remoteMembers [PrivateAccess] privateRole name Nothing Nothing Nothing, e)
createOne2OneConversation ::
MonadClient m =>
+ Domain ->
U.UUID U.V4 ->
U.UUID U.V4 ->
Maybe (Range 1 256 Text) ->
Maybe TeamId ->
m Conversation
-createOne2OneConversation a b name ti = do
+createOne2OneConversation localDomain a b name ti = do
let conv = one2OneConvId a b
a' = Id (U.unpack a)
b' = Id (U.unpack b)
@@ -646,7 +651,7 @@ createOne2OneConversation a b name ti = do
setConsistency Quorum
addPrepQuery Cql.insertConv (conv, One2OneConv, a', privateOnly, privateRole, fromRange <$> name, Just tid, Nothing, Nothing)
addPrepQuery Cql.insertTeamConv (tid, conv, False)
- mems <- snd <$> addLocalMembersUnchecked now conv a' (list1 a' [b'])
+ mems <- snd <$> addLocalMembersUnchecked localDomain now conv a' (list1 a' [b'])
let remoteMembers = [] -- FUTUREWORK: federated one2one
return $ newConv conv One2OneConv a' (toList mems) remoteMembers [PrivateAccess] privateRole name ti Nothing Nothing
@@ -792,28 +797,28 @@ lookupRemoteMembers :: (MonadClient m) => ConvId -> m [RemoteMember]
lookupRemoteMembers conv = join <$> remoteMemberLists [conv]
-- | Add a member to a local conversation, as an admin.
-addMember :: MonadClient m => UTCTime -> ConvId -> UserId -> m (Event, [LocalMember])
-addMember t c u = addLocalMembersUnchecked t c u (singleton u)
+addMember :: MonadClient m => Domain -> UTCTime -> ConvId -> UserId -> m (Event, [LocalMember])
+addMember localDomain t c u = addLocalMembersUnchecked localDomain t c u (singleton u)
-- | Add members to a local conversation.
-addMembersWithRole :: MonadClient m => UTCTime -> ConvId -> (UserId, RoleName) -> ConvMemberAddSizeChecked -> m (Event, [LocalMember], [RemoteMember])
-addMembersWithRole t c orig mems = addMembersUncheckedWithRole t c orig (sizeCheckedLocals mems) (sizeCheckedRemotes mems)
+addMembersWithRole :: MonadClient m => Domain -> UTCTime -> ConvId -> (UserId, RoleName) -> ConvMemberAddSizeChecked -> m (Event, [LocalMember], [RemoteMember])
+addMembersWithRole localDomain t c orig mems = addMembersUncheckedWithRole localDomain t c orig (sizeCheckedLocals mems) (sizeCheckedRemotes mems)
-- | Add members to a local conversation, all as admins.
-- Please make sure the conversation doesn't exceed the maximum size!
-addLocalMembersUnchecked :: MonadClient m => UTCTime -> ConvId -> UserId -> List1 UserId -> m (Event, [LocalMember])
-addLocalMembersUnchecked t conv orig usrs = addLocalMembersUncheckedWithRole t conv (orig, roleNameWireAdmin) ((,roleNameWireAdmin) <$> usrs)
+addLocalMembersUnchecked :: MonadClient m => Domain -> UTCTime -> ConvId -> UserId -> List1 UserId -> m (Event, [LocalMember])
+addLocalMembersUnchecked localDomain t conv orig usrs = addLocalMembersUncheckedWithRole localDomain t conv (orig, roleNameWireAdmin) ((,roleNameWireAdmin) <$> usrs)
-- | Add only local members to a local conversation.
-- Please make sure the conversation doesn't exceed the maximum size!
-addLocalMembersUncheckedWithRole :: MonadClient m => UTCTime -> ConvId -> (UserId, RoleName) -> List1 (UserId, RoleName) -> m (Event, [LocalMember])
-addLocalMembersUncheckedWithRole t conv orig lusers = (\(a, b, _) -> (a, b)) <$> addMembersUncheckedWithRole t conv orig (toList lusers) []
+addLocalMembersUncheckedWithRole :: MonadClient m => Domain -> UTCTime -> ConvId -> (UserId, RoleName) -> List1 (UserId, RoleName) -> m (Event, [LocalMember])
+addLocalMembersUncheckedWithRole localDomain t conv orig lusers = (\(a, b, _) -> (a, b)) <$> addMembersUncheckedWithRole localDomain t conv orig (toList lusers) []
-- | Add members to a local conversation.
-- Conversation is local, so we can add any member to it (including remote ones).
-- Please make sure the conversation doesn't exceed the maximum size!
-addMembersUncheckedWithRole :: MonadClient m => UTCTime -> ConvId -> (UserId, RoleName) -> [(UserId, RoleName)] -> [(Remote UserId, RoleName)] -> m (Event, [LocalMember], [RemoteMember])
-addMembersUncheckedWithRole t conv (orig, _origRole) lusrs rusrs = do
+addMembersUncheckedWithRole :: MonadClient m => Domain -> UTCTime -> ConvId -> (UserId, RoleName) -> [(UserId, RoleName)] -> [(Remote UserId, RoleName)] -> m (Event, [LocalMember], [RemoteMember])
+addMembersUncheckedWithRole localDomain t conv (orig, _origRole) lusrs rusrs = do
-- batch statement with 500 users are known to be above the batch size limit
-- and throw "Batch too large" errors. Therefor we chunk requests and insert
-- sequentially. (parallelizing would not aid performance as the partition
@@ -844,12 +849,27 @@ addMembersUncheckedWithRole t conv (orig, _origRole) lusrs rusrs = do
let remoteUser = qUnqualified (unTagged u)
let remoteDomain = qDomain (unTagged u)
addPrepQuery Cql.insertRemoteMember (conv, remoteDomain, remoteUser, role)
- -- FUTUREWORK: also include remote users in the event!
- let e = Event MemberJoin conv orig t (EdMembersJoin . SimpleMembers . toSimpleMembers $ lusrs)
+ let qconv = Qualified conv localDomain
+ qorig = Qualified orig localDomain
+ lmems = map (uncurry SimpleMember . first (`Qualified` localDomain)) lusrs
+ rmems = map (uncurry SimpleMember . first unTagged) rusrs
+ e = Event MemberJoin qconv qorig t (EdMembersJoin (SimpleMembers (lmems <> rmems)))
return (e, fmap (uncurry newMemberWithRole) lusrs, fmap (uncurry RemoteMember) rusrs)
- where
- toSimpleMembers :: [(UserId, RoleName)] -> [SimpleMember]
- toSimpleMembers = fmap (uncurry SimpleMember)
+
+-- | Set local users as belonging to a remote conversation. This is invoked by
+-- a remote galley (using the RPC updateConversationMembership) when users from
+-- the current backend are added to conversations on the remote end.
+addLocalMembersToRemoteConv :: MonadClient m => [UserId] -> Qualified ConvId -> m ()
+addLocalMembersToRemoteConv users qconv = do
+ -- FUTUREWORK: consider using pooledMapConcurrentlyN
+ for_ (List.chunksOf 32 users) $ \chunk ->
+ retry x5 . batch $ do
+ setType BatchLogged
+ setConsistency Quorum
+ for_ chunk $ \u ->
+ addPrepQuery
+ Cql.insertUserRemoteConv
+ (u, qDomain qconv, qUnqualified qconv)
updateMember :: MonadClient m => ConvId -> UserId -> MemberUpdate -> m MemberUpdateData
updateMember cid uid mup = do
@@ -879,11 +899,11 @@ updateMember cid uid mup = do
misConvRoleName = mupConvRoleName mup
}
-removeLocalMembers :: MonadClient m => Conversation -> UserId -> List1 UserId -> m Event
-removeLocalMembers conv orig localVictims = removeMembers conv orig localVictims []
+removeLocalMembers :: MonadClient m => Domain -> Conversation -> UserId -> List1 UserId -> m Event
+removeLocalMembers localDomain conv orig localVictims = removeMembers localDomain conv orig localVictims []
-removeMembers :: MonadClient m => Conversation -> UserId -> List1 UserId -> [Remote UserId] -> m Event
-removeMembers conv orig localVictims remoteVictims = do
+removeMembers :: MonadClient m => Domain -> Conversation -> UserId -> List1 UserId -> [Remote UserId] -> m Event
+removeMembers localDomain conv orig localVictims remoteVictims = do
t <- liftIO getCurrentTime
retry x5 . batch $ do
setType BatchLogged
@@ -896,7 +916,9 @@ removeMembers conv orig localVictims remoteVictims = do
addPrepQuery Cql.deleteUserConv (u, convId conv)
-- FUTUREWORK: the user's conversation has to be deleted on their own backend for federation
- return $ Event MemberLeave (convId conv) orig t (EdMembersLeave leavingMembers)
+ let qconvId = Qualified (convId conv) localDomain
+ qorig = Qualified orig localDomain
+ return $ Event MemberLeave qconvId qorig t (EdMembersLeave leavingMembers)
where
-- FUTUREWORK(federation, #1274): We need to tell clients about remote members leaving, too.
leavingMembers = UserIdList . toList $ localVictims
@@ -992,19 +1014,22 @@ eraseClients user = retry x5 (write Cql.rmClients (params Quorum (Identity user)
-- Internal utilities
--- | Construct 'TeamMember' from database tuple. Read 'setLegalHoldTeamsWhitelist' from 'Env'
--- to handle implicit consent (ie., fill in 'UserLegalHoldDisabled' instead of
--- 'UserLegalHoldNoConsent' if team is whitelisted.)
+-- | Construct 'TeamMember' from database tuple.
+-- If FeatureLegalHoldWhitelistTeamsAndImplicitConsent is enabled set UserLegalHoldDisabled
+-- if team is whitelisted.
--
-- Throw an exception if one of invitation timestamp and inviter is 'Nothing' and the
-- other is 'Just', which can only be caused by inconsistent database content.
-newTeamMember' :: (MonadThrow m, MonadReader Env m) => TeamId -> (UserId, Permissions, Maybe UserId, Maybe UTCTimeMillis, Maybe UserLegalHoldStatus) -> m TeamMember
+newTeamMember' :: (MonadIO m, MonadThrow m, MonadClient m, MonadReader Env m) => TeamId -> (UserId, Permissions, Maybe UserId, Maybe UTCTimeMillis, Maybe UserLegalHoldStatus) -> m TeamMember
newTeamMember' tid (uid, perms, minvu, minvt, fromMaybe defUserLegalHoldStatus -> lhStatus) = do
- whitelist <- view (options . Opts.optSettings . Opts.setLegalHoldTeamsWhitelist)
- maybeGrant whitelist <$> mk minvu minvt
+ mk minvu minvt >>= maybeGrant
where
- maybeGrant :: Maybe [TeamId] -> TeamMember -> TeamMember
- maybeGrant whitelist = bool id grantImplicitConsent (maybe False (tid `elem`) whitelist)
+ maybeGrant :: (MonadClient m, MonadReader Env m) => TeamMember -> m TeamMember
+ maybeGrant m =
+ ifM
+ (isTeamLegalholdWhitelisted tid)
+ (pure (grantImplicitConsent m))
+ (pure m)
grantImplicitConsent :: TeamMember -> TeamMember
grantImplicitConsent =
diff --git a/services/galley/src/Galley/Data/LegalHold.hs b/services/galley/src/Galley/Data/LegalHold.hs
index 7a01ffb5824..e975e223011 100644
--- a/services/galley/src/Galley/Data/LegalHold.hs
+++ b/services/galley/src/Galley/Data/LegalHold.hs
@@ -25,6 +25,9 @@ module Galley.Data.LegalHold
Galley.Data.LegalHold.selectPendingPrekeys,
Galley.Data.LegalHold.dropPendingPrekeys,
setUserLegalHoldStatus,
+ setTeamLegalholdWhitelisted,
+ isTeamLegalholdWhitelisted,
+ unsetTeamLegalholdWhitelisted,
)
where
@@ -32,11 +35,14 @@ import Brig.Types.Client.Prekey
import Brig.Types.Instances ()
import Brig.Types.Team.LegalHold
import Cassandra
-import Control.Lens (unsnoc)
+import Control.Lens (unsnoc, view)
import Data.Id
import Data.LegalHold
+import Galley.App (Env, options)
import Galley.Data.Instances ()
import Galley.Data.Queries as Q
+import qualified Galley.Options as Opts
+import Galley.Types.Teams (FeatureLegalHold (..), flagLegalHold)
import Imports
-- | Returns 'False' if legal hold is not enabled for this team
@@ -82,3 +88,19 @@ dropPendingPrekeys uid = retry x5 (write Q.dropPendingPrekeys (params Quorum (Id
setUserLegalHoldStatus :: MonadClient m => TeamId -> UserId -> UserLegalHoldStatus -> m ()
setUserLegalHoldStatus tid uid status =
retry x5 (write Q.updateUserLegalHoldStatus (params Quorum (status, tid, uid)))
+
+setTeamLegalholdWhitelisted :: MonadClient m => TeamId -> m ()
+setTeamLegalholdWhitelisted tid =
+ retry x5 (write Q.insertLegalHoldWhitelistedTeam (params Quorum (Identity tid)))
+
+unsetTeamLegalholdWhitelisted :: MonadClient m => TeamId -> m ()
+unsetTeamLegalholdWhitelisted tid =
+ retry x5 (write Q.removeLegalHoldWhitelistedTeam (params Quorum (Identity tid)))
+
+isTeamLegalholdWhitelisted :: (MonadReader Env m, MonadClient m) => TeamId -> m Bool
+isTeamLegalholdWhitelisted tid = do
+ view (options . Opts.optSettings . Opts.setFeatureFlags . flagLegalHold) >>= \case
+ FeatureLegalHoldDisabledPermanently -> pure False
+ FeatureLegalHoldDisabledByDefault -> pure False
+ FeatureLegalHoldWhitelistTeamsAndImplicitConsent ->
+ isJust <$> (runIdentity <$$> retry x5 (query1 Q.selectLegalHoldWhitelistedTeam (params Quorum (Identity tid))))
diff --git a/services/galley/src/Galley/Data/Queries.hs b/services/galley/src/Galley/Data/Queries.hs
index 00916aa20fe..6731d2731af 100644
--- a/services/galley/src/Galley/Data/Queries.hs
+++ b/services/galley/src/Galley/Data/Queries.hs
@@ -301,16 +301,16 @@ selectRemoteMembers = "select conv, user_remote_domain, user_remote_id, conversa
-- local user with remote conversations
--- FUTUREWORK: actually make use of these cql statements.
insertUserRemoteConv :: PrepQuery W (UserId, Domain, ConvId) ()
insertUserRemoteConv = "insert into user_remote_conv (user, conv_remote_domain, conv_remote_id) values (?, ?, ?)"
-deleteUserRemoteConv :: PrepQuery W (UserId, Domain, ConvId) ()
-deleteUserRemoteConv = "delete from user_remote_conv where user = ? and conv_remote_domain = ? and conv_remote_id = ?"
-
selectUserRemoteConvs :: PrepQuery R (Identity UserId) (Domain, ConvId)
selectUserRemoteConvs = "select conv_remote_domain, conv_remote_id from user_remote_conv where user = ? order by conv_remote_domain"
+-- FUTUREWORK: actually make use of these cql statements.
+deleteUserRemoteConv :: PrepQuery W (UserId, Domain, ConvId) ()
+deleteUserRemoteConv = "delete from user_remote_conv where user = ? and conv_remote_domain = ? and conv_remote_id = ?"
+
-- Clients ------------------------------------------------------------------
selectClients :: PrepQuery R (Identity [UserId]) (UserId, C.Set ClientId)
@@ -399,6 +399,24 @@ updateUserLegalHoldStatus =
where team = ? and user = ?
|]
+selectLegalHoldWhitelistedTeam :: PrepQuery R (Identity TeamId) (Identity TeamId)
+selectLegalHoldWhitelistedTeam =
+ [r|
+ select team from legalhold_whitelisted where team = ?
+ |]
+
+insertLegalHoldWhitelistedTeam :: PrepQuery W (Identity TeamId) ()
+insertLegalHoldWhitelistedTeam =
+ [r|
+ insert into legalhold_whitelisted (team) values (?)
+ |]
+
+removeLegalHoldWhitelistedTeam :: PrepQuery W (Identity TeamId) ()
+removeLegalHoldWhitelistedTeam =
+ [r|
+ delete from legalhold_whitelisted where team = ?
+ |]
+
-- Search Visibility --------------------------------------------------------
selectSearchVisibility :: PrepQuery R (Identity TeamId) (Identity (Maybe TeamSearchVisibility))
diff --git a/services/galley/src/Galley/Data/Services.hs b/services/galley/src/Galley/Data/Services.hs
index 7a711d17bea..3ab4ab474fd 100644
--- a/services/galley/src/Galley/Data/Services.hs
+++ b/services/galley/src/Galley/Data/Services.hs
@@ -34,6 +34,7 @@ where
import Cassandra
import Control.Lens
import Data.Id
+import Data.Qualified
import Data.Time.Clock
import Galley.App
import Galley.Data (newMember)
@@ -60,21 +61,26 @@ botMemId = BotId . memId . fromBotMember
botMemService :: BotMember -> ServiceRef
botMemService = fromJust . memService . fromBotMember
-addBotMember :: UserId -> ServiceRef -> BotId -> ConvId -> UTCTime -> Galley (Event, BotMember)
-addBotMember orig s bot cnv now = do
- let pid = s ^. serviceRefProvider
- let sid = s ^. serviceRefId
+addBotMember :: Qualified UserId -> ServiceRef -> BotId -> ConvId -> UTCTime -> Galley (Event, BotMember)
+addBotMember qorig s bot cnv now = do
retry x5 . batch $ do
setType BatchLogged
setConsistency Quorum
addPrepQuery insertUserConv (botUserId bot, cnv)
addPrepQuery insertBot (cnv, bot, sid, pid)
- let e = Event MemberJoin cnv orig now (EdMembersJoin . SimpleMembers $ (fmap toSimpleMember [botUserId bot]))
- let mem = (newMember (botUserId bot)) {memService = Just s}
return (e, BotMember mem)
where
+ pid = s ^. serviceRefProvider
+ sid = s ^. serviceRefId
+ -- FUTUREWORK: support adding bots to a remote conversation
+ qcnv = Qualified cnv localDomain
+ localDomain = qDomain qorig
+ -- FUTUREWORK: support remote bots
+ e = Event MemberJoin qcnv qorig now (EdMembersJoin . SimpleMembers $ (fmap toSimpleMember [botUserId bot]))
+ mem = (newMember (botUserId bot)) {memService = Just s}
+
toSimpleMember :: UserId -> SimpleMember
- toSimpleMember u = SimpleMember u roleNameWireAdmin
+ toSimpleMember u = SimpleMember (Qualified u localDomain) roleNameWireAdmin
-- Service --------------------------------------------------------------------
diff --git a/services/galley/src/Galley/Intra/Client.hs b/services/galley/src/Galley/Intra/Client.hs
index f8f67405579..b45043dac89 100644
--- a/services/galley/src/Galley/Intra/Client.hs
+++ b/services/galley/src/Galley/Intra/Client.hs
@@ -17,6 +17,7 @@
module Galley.Intra.Client
( lookupClients,
+ lookupClientsFull,
notifyClientsAboutLegalHoldRequest,
addLegalHoldClientToUser,
removeLegalHoldClientFromUser,
@@ -40,12 +41,12 @@ import Galley.API.Error
import Galley.App
import Galley.External.LegalHoldService
import Galley.Intra.Util
-import Galley.Types (UserClients, filterClients)
import Imports
import Network.HTTP.Types.Method
import Network.HTTP.Types.Status
import Network.Wai.Utilities.Error
import qualified System.Logger.Class as Logger
+import Wire.API.User.Client (UserClients, UserClientsFull, filterClients, filterClientsFull)
-- | Calls 'Brig.API.internalListClientsH'.
lookupClients :: [UserId] -> Galley UserClients
@@ -57,9 +58,22 @@ lookupClients uids = do
. path "/i/clients"
. json (UserSet $ Set.fromList uids)
. expect2xx
- clients <- parseResponse (Error status502 "server-error") r
+ clients <- parseResponse (mkError status502 "server-error") r
return $ filterClients (not . Set.null) clients
+-- | Calls 'Brig.API.internalListClientsFullH'.
+lookupClientsFull :: [UserId] -> Galley UserClientsFull
+lookupClientsFull uids = do
+ (brigHost, brigPort) <- brigReq
+ r <-
+ call "brig" $
+ method POST . host brigHost . port brigPort
+ . path "/i/clients/full"
+ . json (UserSet $ Set.fromList uids)
+ . expect2xx
+ clients <- parseResponse (mkError status502 "server-error") r
+ return $ filterClientsFull (not . Set.null) clients
+
-- | Calls 'Brig.API.legalHoldClientRequestedH'.
notifyClientsAboutLegalHoldRequest :: UserId -> UserId -> LastPrekey -> Galley ()
notifyClientsAboutLegalHoldRequest requesterUid targetUid lastPrekey' = do
@@ -106,6 +120,7 @@ addLegalHoldClientToUser uid connId prekeys lastPrekey' = do
Nothing
Nothing
Nothing
+ Nothing
-- | Calls 'Brig.API.removeLegalHoldClientH'.
removeLegalHoldClientFromUser :: UserId -> Galley ()
@@ -133,4 +148,4 @@ brigAddClient uid connId client = do
. contentJson
. json client
. expect2xx
- parseResponse (Error status502 "server-error") r
+ parseResponse (mkError status502 "server-error") r
diff --git a/services/galley/src/Galley/Intra/Team.hs b/services/galley/src/Galley/Intra/Team.hs
index 956e6522cc0..dbf56e2d6b2 100644
--- a/services/galley/src/Galley/Intra/Team.hs
+++ b/services/galley/src/Galley/Intra/Team.hs
@@ -37,4 +37,4 @@ getSize tid = do
method GET . host h . port p
. paths ["/i/teams", toByteString' tid, "size"]
. expect2xx
- parseResponse (Error status502 "server-error") r
+ parseResponse (mkError status502 "server-error") r
diff --git a/services/galley/src/Galley/Intra/User.hs b/services/galley/src/Galley/Intra/User.hs
index 0c0cb6103f2..3a8b52c3cfc 100644
--- a/services/galley/src/Galley/Intra/User.hs
+++ b/services/galley/src/Galley/Intra/User.hs
@@ -56,7 +56,7 @@ import Wire.API.User.RichInfo (RichInfo)
--
-- When a connection does not exist, it is skipped.
-- Calls 'Brig.API.getConnectionsStatusH'.
-getConnections :: [UserId] -> [UserId] -> Maybe Relation -> Galley [ConnectionStatus]
+getConnections :: [UserId] -> Maybe [UserId] -> Maybe Relation -> Galley [ConnectionStatus]
getConnections uFrom uTo rlt = do
(h, p) <- brigReq
r <-
@@ -66,7 +66,7 @@ getConnections uFrom uTo rlt = do
. maybe id rfilter rlt
. json ConnectionsStatusRequest {csrFrom = uFrom, csrTo = uTo}
. expect2xx
- parseResponse (Error status502 "server-error") r
+ parseResponse (mkError status502 "server-error") r
where
rfilter = queryItem "filter" . (pack . map toLower . show)
@@ -124,7 +124,7 @@ lookupActivatedUsers = chunkify $ \uids -> do
. path "/i/users"
. queryItem "ids" users
. expect2xx
- parseResponse (Error status502 "server-error") r
+ parseResponse (mkError status502 "server-error") r
-- | URLs with more than ~160 uids produce 400 responses, because HAProxy has a
-- URL length limit of ~6500 (determined experimentally). 100 is a
@@ -175,7 +175,7 @@ getContactList uid = do
method GET . host h . port p
. paths ["/i/users", toByteString' uid, "contacts"]
. expect2xx
- cUsers <$> parseResponse (Error status502 "server-error") r
+ cUsers <$> parseResponse (mkError status502 "server-error") r
-- | Calls 'Brig.API.Internal.getRichInfoMultiH'
getRichInfoMultiUser :: [UserId] -> Galley [(UserId, RichInfo)]
@@ -187,4 +187,4 @@ getRichInfoMultiUser = chunkify $ \uids -> do
. paths ["/i/users/rich-info"]
. queryItem "ids" (toByteString' (List uids))
. expect2xx
- parseResponse (Error status502 "server-error") resp
+ parseResponse (mkError status502 "server-error") resp
diff --git a/services/galley/src/Galley/Options.hs b/services/galley/src/Galley/Options.hs
index b014c930d89..fd74f58d53d 100644
--- a/services/galley/src/Galley/Options.hs
+++ b/services/galley/src/Galley/Options.hs
@@ -28,7 +28,6 @@ module Galley.Options
setFederationDomain,
setEnableIndexedBillingTeamMembers,
setFeatureFlags,
- setLegalHoldTeamsWhitelist,
defConcurrentDeletionEvents,
defDeleteConvThrottleMillis,
defFanoutLimit,
@@ -48,21 +47,16 @@ module Galley.Options
optLogLevel,
optLogNetStrings,
optLogFormat,
- validateOpts,
- teamWhitelistedForLHAndImplicitConsent,
)
where
-import Control.Exception (ErrorCall (ErrorCall), throwIO)
import Control.Lens hiding (Level, (.=))
import Data.Aeson.TH (deriveFromJSON)
import Data.Domain (Domain)
-import Data.Id (TeamId)
import Data.Misc
import Data.Range
-import Galley.Types.Teams (FeatureFlags (..), FeatureLegalHold (..), HardTruncationLimit, flagLegalHold, hardTruncationLimit)
+import Galley.Types.Teams (FeatureFlags (..), HardTruncationLimit, hardTruncationLimit)
import Imports
-import qualified System.Logger as Log
import System.Logger.Extended (Level, LogFormat)
import Util.Options
import Util.Options.Common
@@ -106,10 +100,7 @@ data Settings = Settings
-- the owners.
-- Defaults to false.
_setEnableIndexedBillingTeamMembers :: !(Maybe Bool),
- _setFeatureFlags :: !FeatureFlags,
- -- | Enable LH only for a selected number of teams, and give those team members implicit
- -- consent. See 'validateOpts' below for relevant constraints.
- _setLegalHoldTeamsWhitelist :: !(Maybe [TeamId])
+ _setFeatureFlags :: !FeatureFlags
}
deriving (Show, Generic)
@@ -171,34 +162,3 @@ data Opts = Opts
deriveFromJSON toOptionFieldName ''Opts
makeLenses ''Opts
-
-----------------------------------------------------------------------
-
-validateOpts :: Log.Logger -> Opts -> IO ()
-validateOpts logger opts = do
- let lhWhitelist = opts ^. optSettings . setLegalHoldTeamsWhitelist
- lhWhitelistingEnabled =
- (opts ^. optSettings . setFeatureFlags . flagLegalHold)
- == FeatureLegalHoldWhitelistTeamsAndImplicitConsent
-
- err :: String -> IO ()
- err message = do
- Log.err logger (Log.msg message)
- throwIO $ ErrorCall message
-
- unless (maybe True (not . null) lhWhitelist) $
- err
- "settings.setLegalHoldTeamsWhitelist must not contain an empty list; remove the \
- \attribute or provide at least one team."
-
- unless (if lhWhitelistingEnabled then isJust lhWhitelist else isNothing lhWhitelist) $
- -- Rationale: '_setLegalHoldTeamsWhitelist' is a way to make LH available to selected
- -- teams only; it is not required if `_setFeatureFlags` enables it for all. To avoid
- -- misconfiguration due to a wrong understanding of the settings, doing this produces an
- -- error.
- err
- "If settings.setLegalHoldTeamsWhitelist is provided, settings.featureFlags.flagLegalHold \
- \must be FeatureLegalHoldWhitelistTeamsAndImplicitConsent."
-
-teamWhitelistedForLHAndImplicitConsent :: Settings -> TeamId -> Bool
-teamWhitelistedForLHAndImplicitConsent settings tid = maybe False (tid `elem`) $ settings ^. setLegalHoldTeamsWhitelist
diff --git a/services/galley/src/Galley/Run.hs b/services/galley/src/Galley/Run.hs
index 11fd3989357..9df9a6ae218 100644
--- a/services/galley/src/Galley/Run.hs
+++ b/services/galley/src/Galley/Run.hs
@@ -36,7 +36,7 @@ import qualified Galley.API.Internal as Internal
import Galley.App
import qualified Galley.App as App
import qualified Galley.Data as Data
-import Galley.Options (Opts, optGalley, validateOpts)
+import Galley.Options (Opts, optGalley)
import qualified Galley.Queue as Q
import Imports
import Network.Wai (Application)
@@ -48,7 +48,7 @@ import Servant.API ((:<|>) ((:<|>)))
import qualified Servant.API as Servant
import Servant.API.Generic (ToServantApi, genericApi)
import qualified Servant.Server as Servant
-import qualified System.Logger.Class as Log
+import qualified System.Logger as Log
import Util.Options
import qualified Wire.API.Federation.API.Galley as FederationGalley
import qualified Wire.API.Routes.Public.Galley as GalleyAPI
@@ -77,13 +77,18 @@ mkApp o = do
m <- M.metrics
e <- App.createEnv m o
let l = e ^. App.applog
- validateOpts l o
runClient (e ^. cstate) $
versionCheck Data.schemaVersion
let finalizer = do
+ Log.info l $ Log.msg @Text "Galley application finished."
Log.flush l
Log.close l
- return (middlewares l m $ servantApp e, e, finalizer)
+ middlewares =
+ servantPlusWAIPrometheusMiddleware API.sitemap (Proxy @CombinedAPI)
+ . catchErrors l [Right m]
+ . GZip.gunzip
+ . GZip.gzip GZip.def
+ return (middlewares $ servantApp e, e, finalizer)
where
rtree = compile API.sitemap
app e r k = runGalley e r (route rtree r k)
@@ -98,12 +103,6 @@ mkApp o = do
)
r
- middlewares l m =
- servantPlusWAIPrometheusMiddleware API.sitemap (Proxy @CombinedAPI)
- . catchErrors l [Right m]
- . GZip.gunzip
- . GZip.gzip GZip.def
-
type CombinedAPI = GalleyAPI.ServantAPI :<|> Internal.ServantAPI :<|> ToServantApi FederationGalley.Api :<|> Servant.Raw
refreshMetrics :: Galley ()
diff --git a/services/galley/src/Galley/Validation.hs b/services/galley/src/Galley/Validation.hs
index 305be7f52b9..6473a52be4c 100644
--- a/services/galley/src/Galley/Validation.hs
+++ b/services/galley/src/Galley/Validation.hs
@@ -50,6 +50,7 @@ rangeCheckedMaybe (Just a) = Just <$> rangeChecked a
-- Between 0 and (setMaxConvSize - 1)
newtype ConvSizeChecked a = ConvSizeChecked {fromConvSize :: a}
+ deriving (Functor)
-- Between 1 and setMaxConvSize
data ConvMemberAddSizeChecked = ConvMemberAddSizeChecked {sizeCheckedLocals :: [(UserId, RoleName)], sizeCheckedRemotes :: [(Remote UserId, RoleName)]}
diff --git a/services/galley/test/integration/API.hs b/services/galley/test/integration/API.hs
index 8e0716e69b0..54532eb756b 100644
--- a/services/galley/test/integration/API.hs
+++ b/services/galley/test/integration/API.hs
@@ -30,13 +30,15 @@ import API.SQS
import qualified API.Teams as Teams
import qualified API.Teams.Feature as TeamFeature
import qualified API.Teams.LegalHold as Teams.LegalHold
+import qualified API.Teams.LegalHold.DisabledByDefault
import API.Util
import Bilge hiding (timeout)
import Bilge.Assert
import Brig.Types
import qualified Control.Concurrent.Async as Async
-import Control.Lens (view)
+import Control.Lens (at, ix, preview, view, (.~), (?~), (^.))
import Data.Aeson hiding (json)
+import qualified Data.ByteString as BS
import Data.ByteString.Conversion
import qualified Data.Code as Code
import Data.Domain (Domain (Domain))
@@ -50,6 +52,7 @@ import Data.Range
import qualified Data.Set as Set
import qualified Data.Text as T
import qualified Data.Text.Ascii as Ascii
+import Galley.Options (Opts, optFederator)
import Galley.Types hiding (InternalMember (..))
import Galley.Types.Conversations.Roles
import qualified Galley.Types.Teams as Teams
@@ -62,7 +65,9 @@ import qualified Test.Tasty.Cannon as WS
import Test.Tasty.HUnit
import TestHelpers
import TestSetup
+import Util.Options (Endpoint (Endpoint))
import Wire.API.Conversation.Member (Member (..))
+import Wire.API.Federation.API.Galley (GetConversationsResponse (GetConversationsResponse))
import Wire.API.User.Client (UserClientPrekeyMap, getUserClientPrekeyMap)
tests :: IO TestSetup -> TestTree
@@ -70,6 +75,7 @@ tests s =
testGroup
"Galley integration tests"
[ Teams.LegalHold.tests s,
+ API.Teams.LegalHold.DisabledByDefault.tests s,
mainTests,
Teams.tests s,
MessageTimer.tests s,
@@ -93,8 +99,14 @@ tests s =
test s "fail to get >1000 conversation ids" getConvIdsFailMaxSize,
test s "page through conversations" getConvsPagingOk,
test s "fail to create conversation when not connected" postConvFailNotConnected,
+ test s "fail to create conversation with qualified users when not connected" postConvQualifiedFailNotConnected,
test s "M:N conversation creation must have getConv usr cnv
- checkWs alice (cnv, ws) = WS.awaitMatch (5 # Second) ws $ \n -> do
+ checkWs qalice (cnv, ws) = WS.awaitMatch (5 # Second) ws $ \n -> do
ntfTransient n @?= False
let e = List1.head (WS.unpackPayload n)
- evtConv e @?= cnvId cnv
+ evtConv e @?= Qualified (cnvId cnv) (qDomain qalice)
evtType e @?= ConvCreate
- evtFrom e @?= alice
+ evtFrom e @?= qalice
case evtData e of
EdConversation c' -> assertConvEquals cnv c'
_ -> assertFailure "Unexpected event data"
+-- | This test verifies whether a message actually gets sent all the way to
+-- cannon.
postCryptoMessage1 :: TestM ()
postCryptoMessage1 = do
+ localDomain <- viewFederationDomain
c <- view tsCannon
(alice, ac) <- randomUserWithClient (someLastPrekeys !! 0)
(bob, bc) <- randomUserWithClient (someLastPrekeys !! 1)
(eve, ec) <- randomUserWithClient (someLastPrekeys !! 2)
connectUsers alice (list1 bob [eve])
conv <- decodeConvId <$> postConv alice [bob, eve] (Just "gossip") [] Nothing Nothing
+ let qalice = Qualified alice localDomain
+ qconv = Qualified conv localDomain
-- WS receive timeout
let t = 5 # Second
-- Missing eve
@@ -208,18 +232,18 @@ postCryptoMessage1 = do
postOtrMessage id alice ac conv m2 !!! do
const 201 === statusCode
assertTrue_ (eqMismatch [] [] [] . responseJsonUnsafe)
- void . liftIO $ WS.assertMatch t wsB (wsAssertOtr conv alice ac bc "ciphertext2")
- void . liftIO $ WS.assertMatch t wsE (wsAssertOtr conv alice ac ec "ciphertext2")
+ void . liftIO $ WS.assertMatch t wsB (wsAssertOtr qconv qalice ac bc "ciphertext2")
+ void . liftIO $ WS.assertMatch t wsE (wsAssertOtr qconv qalice ac ec "ciphertext2")
-- Redundant self
WS.bracketR3 c alice bob eve $ \(wsA, wsB, wsE) -> do
let m3 = [(alice, ac, "ciphertext3"), (bob, bc, "ciphertext3"), (eve, ec, "ciphertext3")]
postOtrMessage id alice ac conv m3 !!! do
const 201 === statusCode
assertTrue_ (eqMismatch [] [(alice, Set.singleton ac)] [] . responseJsonUnsafe)
- void . liftIO $ WS.assertMatch t wsB (wsAssertOtr conv alice ac bc "ciphertext3")
- void . liftIO $ WS.assertMatch t wsE (wsAssertOtr conv alice ac ec "ciphertext3")
+ void . liftIO $ WS.assertMatch t wsB (wsAssertOtr qconv qalice ac bc "ciphertext3")
+ void . liftIO $ WS.assertMatch t wsE (wsAssertOtr qconv qalice ac ec "ciphertext3")
-- Alice should not get it
- assertNoMsg wsA (wsAssertOtr conv alice ac ac "ciphertext3")
+ assertNoMsg wsA (wsAssertOtr qconv qalice ac ac "ciphertext3")
-- Deleted eve
WS.bracketR2 c bob eve $ \(wsB, wsE) -> do
deleteClient eve ec (Just defPassword) !!! const 200 === statusCode
@@ -227,19 +251,19 @@ postCryptoMessage1 = do
postOtrMessage id alice ac conv m4 !!! do
const 201 === statusCode
assertTrue_ (eqMismatch [] [] [(eve, Set.singleton ec)] . responseJsonUnsafe)
- void . liftIO $ WS.assertMatch t wsB (wsAssertOtr conv alice ac bc "ciphertext4")
+ void . liftIO $ WS.assertMatch t wsB (wsAssertOtr qconv qalice ac bc "ciphertext4")
-- Eve should not get it
- assertNoMsg wsE (wsAssertOtr conv alice ac ec "ciphertext4")
+ assertNoMsg wsE (wsAssertOtr qconv qalice ac ec "ciphertext4")
-- Deleted eve & redundant self
WS.bracketR3 c alice bob eve $ \(wsA, wsB, wsE) -> do
let m5 = [(bob, bc, "ciphertext5"), (eve, ec, "ciphertext5"), (alice, ac, "ciphertext5")]
postOtrMessage id alice ac conv m5 !!! do
const 201 === statusCode
assertTrue_ (eqMismatch [] [(alice, Set.singleton ac)] [(eve, Set.singleton ec)] . responseJsonUnsafe)
- void . liftIO $ WS.assertMatch t wsB (wsAssertOtr conv alice ac bc "ciphertext5")
+ void . liftIO $ WS.assertMatch t wsB (wsAssertOtr qconv qalice ac bc "ciphertext5")
-- Neither Alice nor Eve should get it
- assertNoMsg wsA (wsAssertOtr conv alice ac ac "ciphertext5")
- assertNoMsg wsE (wsAssertOtr conv alice ac ec "ciphertext5")
+ assertNoMsg wsA (wsAssertOtr qconv qalice ac ac "ciphertext5")
+ assertNoMsg wsE (wsAssertOtr qconv qalice ac ec "ciphertext5")
-- Missing Bob, deleted eve & redundant self
let m6 = [(eve, ec, "ciphertext6"), (alice, ac, "ciphertext6")]
postOtrMessage id alice ac conv m6 !!! do
@@ -263,13 +287,14 @@ postCryptoMessage1 = do
const 201 === statusCode
assertTrue_ (eqMismatch [] [] [] . responseJsonUnsafe)
-- Bob's first client gets both messages
- void . liftIO $ WS.assertMatch t wsB (wsAssertOtr conv alice ac bc cipher)
- void . liftIO $ WS.assertMatch t wsB (wsAssertOtr conv alice ac bc2 cipher)
+ void . liftIO $ WS.assertMatch t wsB (wsAssertOtr qconv qalice ac bc cipher)
+ void . liftIO $ WS.assertMatch t wsB (wsAssertOtr qconv qalice ac bc2 cipher)
-- Bob's second client gets only the message destined for him
- void . liftIO $ WS.assertMatch t wsB2 (wsAssertOtr conv alice ac bc2 cipher)
+ void . liftIO $ WS.assertMatch t wsB2 (wsAssertOtr qconv qalice ac bc2 cipher)
liftIO $ assertBool "unexpected equal clients" (bc /= bc2)
- assertNoMsg wsB2 (wsAssertOtr conv alice ac bc cipher)
+ assertNoMsg wsB2 (wsAssertOtr qconv qalice ac bc cipher)
+-- | This test verifies basic mismatch behaviour of the the JSON endpoint.
postCryptoMessage2 :: TestM ()
postCryptoMessage2 = do
b <- view tsBrig
@@ -287,13 +312,14 @@ postCryptoMessage2 = do
liftIO $ assertBool "client mismatch" (eqMismatch [(eve, Set.singleton ec)] [] [] (Just x))
-- Fetch all missing clients prekeys
r2 <-
- post (b . path "/users/prekeys" . json (missingClients x))
+ post (b . zUser alice . path "/users/prekeys" . json (missingClients x))
Map.lookup eve (userClientMap (getUserClientPrekeyMap p)) @=? Just [ec]
+-- | This test verifies basic mismatch behaviour of the protobuf endpoint.
postCryptoMessage3 :: TestM ()
postCryptoMessage3 = do
b <- view tsBrig
@@ -312,13 +338,15 @@ postCryptoMessage3 = do
liftIO $ assertBool "client mismatch" (eqMismatch [(eve, Set.singleton ec)] [] [] (Just x))
-- Fetch all missing clients prekeys
r2 <-
- post (b . path "/users/prekeys" . json (missingClients x))
+ post (b . zUser alice . path "/users/prekeys" . json (missingClients x))
Map.lookup eve (userClientMap (getUserClientPrekeyMap p)) @=? Just [ec]
+-- | This test verfies behaviour when an unknown client posts the message. Only
+-- tests the Protobuf endpoint.
postCryptoMessage4 :: TestM ()
postCryptoMessage4 = do
alice <- randomUser
@@ -332,78 +360,89 @@ postCryptoMessage4 = do
postProtoOtrMessage alice (ClientId "172618352518396") conv m
!!! const 403 === statusCode
+-- | This test verifies behaviour under various values of ignore_missing and
+-- report_missing. Only tests the JSON endpoint.
postCryptoMessage5 :: TestM ()
postCryptoMessage5 = do
(alice, ac) <- randomUserWithClient (someLastPrekeys !! 0)
(bob, bc) <- randomUserWithClient (someLastPrekeys !! 1)
- (eve, ec) <- randomUserWithClient (someLastPrekeys !! 2)
- connectUsers alice (list1 bob [eve])
- conv <- decodeConvId <$> postConv alice [bob, eve] (Just "gossip") [] Nothing Nothing
+ (chad, cc) <- randomUserWithClient (someLastPrekeys !! 2)
+ (eve, ec) <- randomUserWithClient (someLastPrekeys !! 3)
+ connectUsers alice (list1 bob [chad, eve])
+ conv <- decodeConvId <$> postConv alice [bob, chad, eve] (Just "gossip") [] Nothing Nothing
-- Missing eve
- let m = [(bob, bc, "hello bob")]
+ let msgMissingChadAndEve = [(bob, bc, "hello bob")]
let m' = otrRecipients [(bob, [(bc, encodeCiphertext "hello bob")])]
-- These three are equivalent (i.e. report all missing clients)
- postOtrMessage id alice ac conv m
+ postOtrMessage id alice ac conv msgMissingChadAndEve
!!! const 412 === statusCode
- postOtrMessage (queryItem "ignore_missing" "false") alice ac conv m
+ postOtrMessage (queryItem "ignore_missing" "false") alice ac conv msgMissingChadAndEve
!!! const 412 === statusCode
- postOtrMessage (queryItem "report_missing" "true") alice ac conv m
+ postOtrMessage (queryItem "report_missing" "true") alice ac conv msgMissingChadAndEve
!!! const 412 === statusCode
-- These two are equivalent (i.e. ignore all missing clients)
- postOtrMessage (queryItem "ignore_missing" "true") alice ac conv m
+ postOtrMessage (queryItem "ignore_missing" "true") alice ac conv msgMissingChadAndEve
!!! const 201 === statusCode
- postOtrMessage (queryItem "report_missing" "false") alice ac conv m
+ postOtrMessage (queryItem "report_missing" "false") alice ac conv msgMissingChadAndEve
!!! const 201 === statusCode
-- Report missing clients of a specific user only
- postOtrMessage (queryItem "report_missing" (toByteString' bob)) alice ac conv m
+ postOtrMessage (queryItem "report_missing" (toByteString' bob)) alice ac conv msgMissingChadAndEve
!!! const 201 === statusCode
-- Let's make sure that the same logic using protobuf in the body works too
postProtoOtrMessage' Nothing (queryItem "report_missing" (toByteString' bob)) alice ac conv m'
!!! const 201 === statusCode
-- Body takes precedence
- postOtrMessage' (Just [bob]) (queryItem "report_missing" (toByteString' eve)) alice ac conv m
+ postOtrMessage' (Just [bob]) (queryItem "report_missing" (listToByteString [eve, chad])) alice ac conv msgMissingChadAndEve
!!! const 201 === statusCode
-- Set it only in the body of the message
- postOtrMessage' (Just [bob]) id alice ac conv m
+ postOtrMessage' (Just [bob]) id alice ac conv msgMissingChadAndEve
!!! const 201 === statusCode
-- Let's make sure that protobuf works too, when specified in the body only
postProtoOtrMessage' (Just [bob]) id alice ac conv m'
!!! const 201 === statusCode
- _rs <-
- postOtrMessage (queryItem "report_missing" (toByteString' eve)) alice ac conv []
+ reportEveAndChad <-
+ -- send message with no clients
+ postOtrMessage (queryItem "report_missing" (listToByteString [eve, chad])) alice ac conv []
postConv alice [] (Just "gossip") [InviteAccess, LinkAccess] Nothing Nothing
+ let qconv = Qualified conv (qDomain qbob)
WS.bracketR2 c alice bob $ \(wsA, wsB) -> do
postJoinConv bob conv !!! const 200 === statusCode
postJoinConv bob conv !!! const 204 === statusCode
void . liftIO $
WS.assertMatchN (5 # Second) [wsA, wsB] $
- wsAssertMemberJoinWithRole conv bob [bob] roleNameWireMember
+ wsAssertMemberJoinWithRole qconv qbob [qbob] roleNameWireMember
postJoinCodeConvOk :: TestM ()
postJoinCodeConvOk = do
c <- view tsCannon
alice <- randomUser
- bob <- randomUser
+ qbob <- randomQualifiedUser
+ let bob = qUnqualified qbob
eve <- ephemeralUser
dave <- ephemeralUser
conv <- decodeConvId <$> postConv alice [] (Just "gossip") [CodeAccess] (Just ActivatedAccessRole) Nothing
+ let qconv = Qualified conv (qDomain qbob)
cCode <- decodeConvCodeEvent <$> postConvCode alice conv
-- currently ConversationCode is used both as return type for POST ../code and as body for ../join
-- POST /code gives code,key,uri
@@ -423,7 +462,7 @@ postJoinCodeConvOk = do
postJoinCodeConv eve payload !!! const 403 === statusCode
void . liftIO $
WS.assertMatchN (5 # Second) [wsA, wsB] $
- wsAssertMemberJoinWithRole conv bob [bob] roleNameWireMember
+ wsAssertMemberJoinWithRole qconv qbob [qbob] roleNameWireMember
-- changing access to non-activated should give eve access
let nonActivatedAccess = ConversationAccessUpdate [CodeAccess] NonActivatedAccessRole
putAccessUpdate alice conv nonActivatedAccess !!! const 200 === statusCode
@@ -436,8 +475,10 @@ postJoinCodeConvOk = do
postConvertCodeConv :: TestM ()
postConvertCodeConv = do
c <- view tsCannon
- alice <- randomUser
+ qalice <- randomQualifiedUser
+ let alice = qUnqualified qalice
conv <- decodeConvId <$> postConv alice [] (Just "gossip") [InviteAccess] Nothing Nothing
+ let qconv = Qualified conv (qDomain qalice)
-- Cannot do code operations if conversation not in code access
postConvCode alice conv !!! const 403 === statusCode
deleteConvCode alice conv !!! const 403 === statusCode
@@ -453,7 +494,7 @@ postConvertCodeConv = do
putAccessUpdate alice conv nonActivatedAccess !!! const 204 === statusCode
void . liftIO $
WS.assertMatchN (5 # Second) [wsA] $
- wsAssertConvAccessUpdate conv alice nonActivatedAccess
+ wsAssertConvAccessUpdate qconv qalice nonActivatedAccess
-- Create/get/update/delete codes
getConvCode alice conv !!! const 404 === statusCode
c1 <- decodeConvCodeEvent <$> (postConvCode alice conv addUserToTeam alice tid
assertQueue "team member (bob) join" $ tUpdate 2 [alice]
refreshIndex
@@ -493,22 +536,24 @@ postConvertTeamConv = do
conv <- createTeamConvAccess alice tid [bob, eve] (Just "blaa") acc (Just NonActivatedAccessRole) Nothing Nothing
-- mallory joins by herself
mallory <- ephemeralUser
+ let qmallory = Qualified mallory localDomain
+ qconv = Qualified conv localDomain
j <- decodeConvCodeEvent <$> postConvCode alice conv
WS.bracketR3 c alice bob eve $ \(wsA, wsB, wsE) -> do
postJoinCodeConv mallory j !!! const 200 === statusCode
void . liftIO $
WS.assertMatchN (5 # Second) [wsA, wsB, wsE] $
- wsAssertMemberJoinWithRole conv mallory [mallory] roleNameWireMember
+ wsAssertMemberJoinWithRole qconv qmallory [qmallory] roleNameWireMember
WS.bracketRN c [alice, bob, eve, mallory] $ \[wsA, wsB, wsE, wsM] -> do
let teamAccess = ConversationAccessUpdate [InviteAccess, CodeAccess] TeamAccessRole
putAccessUpdate alice conv teamAccess !!! const 200 === statusCode
void . liftIO $
WS.assertMatchN (5 # Second) [wsA, wsB, wsE, wsM] $
- wsAssertConvAccessUpdate conv alice teamAccess
+ wsAssertConvAccessUpdate qconv qalice teamAccess
-- non-team members get kicked out
void . liftIO $
WS.assertMatchN (5 # Second) [wsA, wsB, wsE, wsM] $
- wsAssertMemberLeave conv alice [eve, mallory]
+ wsAssertMemberLeave qconv qalice [eve, mallory]
-- joining (for mallory) is no longer possible
postJoinCodeConv mallory j !!! const 403 === statusCode
-- team members (dave) can still join
@@ -634,6 +679,15 @@ postConvFailNotConnected = do
const 403 === statusCode
const (Just "not-connected") === fmap label . responseJsonUnsafe
+postConvQualifiedFailNotConnected :: TestM ()
+postConvQualifiedFailNotConnected = do
+ alice <- randomUser
+ bob <- randomQualifiedUser
+ jane <- randomQualifiedUser
+ postConvQualified alice [bob, jane] Nothing [] Nothing Nothing !!! do
+ const 403 === statusCode
+ const (Just "not-connected") === fmap label . responseJsonUnsafe
+
postConvFailNumMembers :: TestM ()
postConvFailNumMembers = do
n <- fromIntegral <$> view tsMaxConvSize
@@ -644,6 +698,16 @@ postConvFailNumMembers = do
const 400 === statusCode
const (Just "client-error") === fmap label . responseJsonUnsafe
+postConvQualifiedFailNumMembers :: TestM ()
+postConvQualifiedFailNumMembers = do
+ n <- fromIntegral <$> view tsMaxConvSize
+ alice <- randomUser
+ bob : others <- replicateM n randomQualifiedUser
+ connectLocalQualifiedUsers alice (list1 bob others)
+ postConvQualified alice (bob : others) Nothing [] Nothing Nothing !!! do
+ const 400 === statusCode
+ const (Just "client-error") === fmap label . responseJsonUnsafe
+
-- | If somebody has blocked a user, that user shouldn't be able to create a
-- group conversation which includes them.
postConvFailBlocked :: TestM ()
@@ -658,6 +722,67 @@ postConvFailBlocked = do
const 403 === statusCode
const (Just "not-connected") === fmap label . responseJsonUnsafe
+--- | If somebody has blocked a user, that user shouldn't be able to create a
+-- group conversation which includes them.
+postConvQualifiedFailBlocked :: TestM ()
+postConvQualifiedFailBlocked = do
+ alice <- randomUser
+ bob <- randomQualifiedUser
+ jane <- randomQualifiedUser
+ connectLocalQualifiedUsers alice (list1 bob [jane])
+ putConnectionQualified jane alice Blocked
+ !!! const 200 === statusCode
+ postConvQualified alice [bob, jane] Nothing [] Nothing Nothing !!! do
+ const 403 === statusCode
+ const (Just "not-connected") === fmap label . responseJsonUnsafe
+
+postConvQualifiedNonExistentDomain :: TestM ()
+postConvQualifiedNonExistentDomain = do
+ alice <- randomUser
+ bob <- flip Qualified (Domain "non-existent.example.com") <$> randomId
+ postConvQualified alice [bob] Nothing [] Nothing Nothing !!! do
+ const 422 === statusCode
+
+postConvQualifiedNonExistentUser :: TestM ()
+postConvQualifiedNonExistentUser = do
+ alice <- randomUser
+ bobId <- randomId
+ charlieId <- randomId
+ let remoteDomain = Domain "far-away.example.com"
+ bob = Qualified bobId remoteDomain
+ charlie = Qualified charlieId remoteDomain
+ opts <- view tsGConf
+ _g <- view tsGalley
+ (resp, _) <-
+ withTempMockFederator
+ opts
+ remoteDomain
+ (const [mkProfile charlie (Name "charlie")])
+ (postConvQualified alice [bob, charlie] (Just "remote gossip") [] Nothing Nothing)
+ liftIO $ do
+ statusCode resp @?= 400
+ let err = responseJsonUnsafe resp :: Object
+ (err ^. at "label") @?= Just "unknown-remote-user"
+
+postConvQualifiedFederationNotEnabled :: TestM ()
+postConvQualifiedFederationNotEnabled = do
+ g <- view tsGalley
+ alice <- randomUser
+ bob <- flip Qualified (Domain "some-remote-backend.example.com") <$> randomId
+ opts <- view tsGConf
+ let federatorNotConfigured :: Opts = opts & optFederator .~ Nothing
+ withSettingsOverrides federatorNotConfigured $
+ postConvHelper g alice [bob] !!! do
+ const 400 === statusCode
+ const (Just "federation-not-enabled") === fmap label . responseJsonUnsafe
+
+-- like postConvQualified
+-- FUTUREWORK: figure out how to use functions in the TestM monad inside withSettingsOverrides and remove this duplication
+postConvHelper :: (MonadIO m, MonadHttp m) => (Request -> Request) -> UserId -> [Qualified UserId] -> m ResponseLBS
+postConvHelper g zusr newUsers = do
+ let conv = NewConvUnmanaged $ NewConv [] newUsers (Just "gossip") (Set.fromList []) Nothing Nothing Nothing Nothing roleNameWireAdmin
+ post $ g . path "/conversations" . zUser zusr . zConn "conn" . zType "access" . json conv
+
postSelfConvOk :: TestM ()
postSelfConvOk = do
alice <- randomUser
@@ -682,7 +807,7 @@ postConvO2OFailWithSelf :: TestM ()
postConvO2OFailWithSelf = do
g <- view tsGalley
alice <- randomUser
- let inv = NewConvUnmanaged (NewConv [alice] Nothing mempty Nothing Nothing Nothing Nothing roleNameWireAdmin)
+ let inv = NewConvUnmanaged (NewConv [alice] [] Nothing mempty Nothing Nothing Nothing Nothing roleNameWireAdmin)
post (g . path "/conversations/one2one" . zUser alice . zConn "conn" . zType "access" . json inv) !!! do
const 403 === statusCode
const (Just "invalid-op") === fmap label . responseJsonUnsafe
@@ -843,6 +968,17 @@ getConvOk = do
getConv bob conv !!! const 200 === statusCode
getConv chuck conv !!! const 200 === statusCode
+getConvQualifiedOk :: TestM ()
+getConvQualifiedOk = do
+ alice <- randomUser
+ bob <- randomQualifiedUser
+ chuck <- randomQualifiedUser
+ connectLocalQualifiedUsers alice (list1 bob [chuck])
+ conv <- decodeConvId <$> postConvQualified alice [bob, chuck] (Just "gossip") [] Nothing Nothing
+ getConv alice conv !!! const 200 === statusCode
+ getConv (qUnqualified bob) conv !!! const 200 === statusCode
+ getConv (qUnqualified chuck) conv !!! const 200 === statusCode
+
accessConvMeta :: TestM ()
accessConvMeta = do
g <- view tsGalley
@@ -864,46 +1000,177 @@ leaveConnectConversation = do
let c = fromMaybe (error "invalid connect conversation") (cnvId <$> responseJsonUnsafe bdy)
deleteMember alice alice c !!! const 403 === statusCode
--- This test adds a non existent remote user to a local conversation and expects
--- a 200. This is of course not correct. When we implement a remote call, we
--- must mock it by mocking the federator and expecting a successful response
--- from the remote. Additionally, another test must be added to deal with error
--- scenarios of federation.
+-- FUTUREWORK: Add more tests for scenarios of federation.
-- See also the comment in Galley.API.Update.addMembers for some other checks that are necessary.
testAddRemoteMember :: TestM ()
testAddRemoteMember = do
- alice <- randomUser
+ qalice <- randomQualifiedUser
+ let alice = qUnqualified qalice
+ let localDomain = qDomain qalice
bobId <- randomId
- let remoteBob = Qualified bobId (Domain "far-away.example.com")
+ let remoteDomain = Domain "far-away.example.com"
+ remoteBob = Qualified bobId remoteDomain
convId <- decodeConvId <$> postConv alice [] (Just "remote gossip") [] Nothing Nothing
- e <- responseJsonUnsafe <$> (postQualifiedMembers alice (remoteBob :| []) convId (pure resp getConv alice convId
+ evtFrom e @?= qalice
+ conv <- responseJsonUnsafeWithMsg "conversation" <$> getConvQualified alice qconvId
liftIO $ do
let actual = cmOthers $ cnvMembers conv
let expected = [OtherMember remoteBob Nothing roleNameWireAdmin]
assertEqual "other members should include remoteBob" expected actual
+testGetRemoteConversation :: TestM ()
+testGetRemoteConversation = do
+ aliceQ <- randomQualifiedUser
+ let alice = qUnqualified aliceQ
+ bobId <- randomId
+ convId <- randomId
+ let remoteDomain = Domain "far-away.example.com"
+ remoteConv = Qualified convId remoteDomain
+
+ let aliceAsOtherMember = OtherMember aliceQ Nothing roleNameWireAdmin
+ bobAsMember = Member bobId Nothing False Nothing Nothing False Nothing False Nothing roleNameWireAdmin
+ remoteConversationResponse =
+ GetConversationsResponse
+ [ Conversation
+ { cnvId = convId,
+ cnvType = RegularConv,
+ cnvCreator = alice,
+ cnvAccess = [],
+ cnvAccessRole = ActivatedAccessRole,
+ cnvName = Just "federated gossip",
+ cnvMembers = ConvMembers bobAsMember [aliceAsOtherMember],
+ cnvTeam = Nothing,
+ cnvMessageTimer = Nothing,
+ cnvReceiptMode = Nothing
+ }
+ ]
+ opts <- view tsGConf
+ g <- view tsGalley
+ (resp, _) <-
+ withTempMockFederator
+ opts
+ remoteDomain
+ (const remoteConversationResponse)
+ (getConvQualified' g alice remoteConv)
+ conv :: Conversation <- responseJsonUnsafe <$> (pure resp postConv alice [] (Just "remote gossip") [] Nothing Nothing
+ opts <- view tsGConf
+ g <- view tsGalley
+ (resp, _) <-
+ withTempMockFederator
+ opts
+ remoteDomain
+ (const [mkProfile remoteCharlie (Name "charlie")])
+ (postQualifiedMembers' g alice (remoteBob :| [remoteCharlie]) convId)
+ liftIO $ statusCode resp @?= 400
+ let err = responseJsonUnsafe resp :: Object
+ liftIO $ (err ^. at "label") @?= Just "unknown-remote-user"
+
+testAddDeletedRemoteUser :: TestM ()
+testAddDeletedRemoteUser = do
+ alice <- randomUser
+ bobId <- randomId
+ let remoteDomain = Domain "far-away.example.com"
+ remoteBob = Qualified bobId remoteDomain
+ convId <- decodeConvId <$> postConv alice [] (Just "remote gossip") [] Nothing Nothing
+ opts <- view tsGConf
+ g <- view tsGalley
+ (resp, _) <-
+ withTempMockFederator
+ opts
+ remoteDomain
+ (const [(mkProfile remoteBob (Name "bob")) {profileDeleted = True}])
+ (postQualifiedMembers' g alice (remoteBob :| []) convId)
+ liftIO $ statusCode resp @?= 400
+ let err = responseJsonUnsafe resp :: Object
+ liftIO $ (err ^. at "label") @?= Just "unknown-remote-user"
+
+testAddRemoteMemberInvalidDomain :: TestM ()
+testAddRemoteMemberInvalidDomain = do
+ alice <- randomUser
+ bobId <- randomId
+ let remoteBob = Qualified bobId (Domain "invalid.example.com")
+ convId <- decodeConvId <$> postConv alice [] (Just "remote gossip") [] Nothing Nothing
+ postQualifiedMembers alice (remoteBob :| []) convId
+ !!! do
+ const 422 === statusCode
+ const (Just "/federation/get-users-by-ids")
+ === preview (ix "data" . ix "path") . responseJsonUnsafe @Value
+ const (Just "invalid.example.com")
+ === preview (ix "data" . ix "domain") . responseJsonUnsafe @Value
+
+-- This test is a safeguard to ensure adding remote members will fail
+-- on environments where federation isn't configured (such as our production as of May 2021)
+testAddRemoteMemberFederationDisabled :: TestM ()
+testAddRemoteMemberFederationDisabled = do
+ g <- view tsGalley
+ alice <- randomUser
+ remoteBob <- flip Qualified (Domain "some-remote-backend.example.com") <$> randomId
+ convId <- decodeConvId <$> postConv alice [] (Just "remote gossip") [] Nothing Nothing
+ opts <- view tsGConf
+ -- federator endpoint not configured is equivalent to federation being disabled
+ -- This is the case on staging/production in May 2021.
+ let federatorNotConfigured :: Opts = opts & optFederator .~ Nothing
+ withSettingsOverrides federatorNotConfigured $ do
+ postQualifiedMembers' g alice (remoteBob :| []) convId !!! do
+ const 400 === statusCode
+ const (Just "federation-not-enabled") === fmap label . responseJsonUnsafe
+ -- federator endpoint being configured in brig and/or galley, but not being
+ -- available (i.e. no service listing on that IP/port) can happen due to a
+ -- misconfiguration of federator. That should give a 500.
+ -- Port 1 should always be wrong hopefully.
+ let federatorUnavailable :: Opts = opts & optFederator ?~ Endpoint "127.0.0.1" 1
+ withSettingsOverrides federatorUnavailable $ do
+ postQualifiedMembers' g alice (remoteBob :| []) convId !!! do
+ const 500 === statusCode
+ const (Just "federation-not-available") === fmap label . responseJsonUnsafe
+
postMembersOk :: TestM ()
postMembersOk = do
- alice <- randomUser
+ qalice <- randomQualifiedUser
+ let alice = qUnqualified qalice
bob <- randomUser
chuck <- randomUser
- eve <- randomUser
+ qeve <- randomQualifiedUser
+ let eve = qUnqualified qeve
connectUsers alice (list1 bob [chuck, eve])
connectUsers eve (singleton bob)
conv <- decodeConvId <$> postConv alice [bob, chuck] (Just "gossip") [] Nothing Nothing
+ let qconv = Qualified conv (qDomain qalice)
e <- responseJsonUnsafe <$> (postMembers alice (singleton eve) conv do
_ <- getSelfMember u conv postO2OConv alice bob (Just "gossip")
+ let qconv = Qualified conv (qDomain qbob)
-- This endpoint should be deprecated but clients still use it
WS.bracketR2 c alice bob $ \(wsA, wsB) -> do
void $ putConversationName bob conv "gossip++" !!! const 200 === statusCode
void . liftIO . WS.assertMatchN (5 # Second) [wsA, wsB] $ \n -> do
let e = List1.head (WS.unpackPayload n)
ntfTransient n @?= False
- evtConv e @?= conv
+ evtConv e @?= qconv
evtType e @?= ConvRename
- evtFrom e @?= bob
+ evtFrom e @?= qbob
evtData e @?= EdConvRename (ConversationRename "gossip++")
putMemberOtrMuteOk :: TestM ()
@@ -1053,9 +1322,11 @@ putMemberOk :: MemberUpdate -> TestM ()
putMemberOk update = do
c <- view tsCannon
alice <- randomUser
- bob <- randomUser
+ qbob <- randomQualifiedUser
+ let bob = qUnqualified qbob
connectUsers alice (singleton bob)
conv <- decodeConvId <$> postO2OConv alice bob (Just "gossip")
+ let qconv = Qualified conv (qDomain qbob)
getConv alice conv !!! const 200 === statusCode
-- Expected member state
let memberBob =
@@ -1077,9 +1348,9 @@ putMemberOk update = do
void . liftIO . WS.assertMatch (5 # Second) ws $ \n -> do
let e = List1.head (WS.unpackPayload n)
ntfTransient n @?= False
- evtConv e @?= conv
+ evtConv e @?= qconv
evtType e @?= MemberStateUpdate
- evtFrom e @?= bob
+ evtFrom e @?= qbob
case evtData e of
EdMemberUpdate mis -> do
assertEqual "otr_muted" (mupOtrMute update) (misOtrMuted mis)
@@ -1106,11 +1377,13 @@ putMemberOk update = do
putReceiptModeOk :: TestM ()
putReceiptModeOk = do
c <- view tsCannon
- alice <- randomUser
+ qalice <- randomQualifiedUser
+ let alice = qUnqualified qalice
bob <- randomUser
jane <- randomUser
connectUsers alice (list1 bob [jane])
cnv <- decodeConvId <$> postConv alice [bob, jane] (Just "gossip") [] Nothing Nothing
+ let qcnv = Qualified cnv (qDomain qalice)
WS.bracketR3 c alice bob jane $ \(_wsA, wsB, _wsJ) -> do
-- By default, nothing is set
getConv alice cnv !!! do
@@ -1122,7 +1395,7 @@ putReceiptModeOk = do
getConv alice cnv !!! do
const 200 === statusCode
const (Just $ Just (ReceiptMode 0)) === fmap cnvReceiptMode . responseJsonUnsafe
- void . liftIO $ checkWs alice (cnv, wsB)
+ void . liftIO $ checkWs qalice (qcnv, wsB)
-- No changes
putReceiptMode alice cnv (ReceiptMode 0) !!! const 204 === statusCode
-- No event should have been generated
@@ -1136,12 +1409,12 @@ putReceiptModeOk = do
const 200 === statusCode
const (Just (Just (ReceiptMode 0))) === fmap cnvReceiptMode . responseJsonUnsafe
where
- checkWs alice (cnv, ws) = WS.awaitMatch (5 # Second) ws $ \n -> do
+ checkWs qalice (qcnv, ws) = WS.awaitMatch (5 # Second) ws $ \n -> do
ntfTransient n @?= False
let e = List1.head (WS.unpackPayload n)
- evtConv e @?= cnv
+ evtConv e @?= qcnv
evtType e @?= ConvReceiptModeUpdate
- evtFrom e @?= alice
+ evtFrom e @?= qalice
case evtData e of
EdConvReceiptModeUpdate (ConversationReceiptModeUpdate (ReceiptMode mode)) ->
assertEqual "modes should match" mode 0
@@ -1185,14 +1458,16 @@ removeUser = do
conv1 <- decodeConvId <$> postConv alice [bob'] (Just "gossip") [] Nothing Nothing
conv2 <- decodeConvId <$> postConv alice [bob', carl'] (Just "gossip2") [] Nothing Nothing
conv3 <- decodeConvId <$> postConv alice [carl'] (Just "gossip3") [] Nothing Nothing
+ let qconv1 = Qualified conv1 (qDomain bob)
+ qconv2 = Qualified conv2 (qDomain bob)
WS.bracketR3 c alice bob' carl' $ \(wsA, wsB, wsC) -> do
deleteUser bob'
void . liftIO $
WS.assertMatchN (5 # Second) [wsA, wsB] $
- matchMemberLeave conv1 bob'
+ matchMemberLeave qconv1 bob
void . liftIO $
WS.assertMatchN (5 # Second) [wsA, wsB, wsC] $
- matchMemberLeave conv2 bob'
+ matchMemberLeave qconv2 bob
-- Check memberships
mems1 <- fmap cnvMembers . responseJsonUnsafe <$> getConv alice conv1
mems2 <- fmap cnvMembers . responseJsonUnsafe <$> getConv alice conv2
@@ -1211,4 +1486,4 @@ removeUser = do
evtConv e @?= conv
evtType e @?= MemberLeave
evtFrom e @?= u
- evtData e @?= EdMembersLeave (UserIdList [u])
+ evtData e @?= EdMembersLeave (UserIdList [qUnqualified u])
diff --git a/services/galley/test/integration/API/Federation.hs b/services/galley/test/integration/API/Federation.hs
index 5605082437b..ee8a792eafe 100644
--- a/services/galley/test/integration/API/Federation.hs
+++ b/services/galley/test/integration/API/Federation.hs
@@ -20,17 +20,24 @@ module API.Federation where
import API.Util
import Bilge
import Bilge.Assert
-import Control.Lens
-import Data.Id (Id (..))
+import qualified Cassandra as Cql
+import Control.Lens hiding ((#))
+import Data.Domain
+import Data.Id (Id (..), randomId)
import Data.List1
import Data.Qualified (Qualified (..))
+import Data.Time.Clock
+import Data.Timeout (TimeoutUnit (..), (#))
import Data.UUID.V4 (nextRandom)
+import qualified Galley.Data.Queries as Cql
import Galley.Types
import Imports
import Test.Tasty
+import qualified Test.Tasty.Cannon as WS
import Test.Tasty.HUnit
import TestHelpers
import TestSetup
+import Wire.API.Conversation.Role
import Wire.API.Federation.API.Galley (GetConversationsRequest (..), GetConversationsResponse (..))
import qualified Wire.API.Federation.API.Galley as FedGalley
@@ -38,8 +45,16 @@ tests :: IO TestSetup -> TestTree
tests s =
testGroup
"federation"
- [ test s "GET /federation/get-conversations : All Found" getConversationsAllFound,
- test s "GET /federation/get-conversations : Conversations user is not a part of are excluded from result" getConversationsNotPartOf
+ [ test s "POST /federation/get-conversations : All Found" getConversationsAllFound,
+ test s "POST /federation/get-conversations : Conversations user is not a part of are excluded from result" getConversationsNotPartOf,
+ test
+ s
+ "POST /federation/update-conversation-memberships : Add local user to remote conversation"
+ addLocalUser,
+ test
+ s
+ "POST /federation/update-conversation-memberships : Notify local user about other members joining"
+ notifyLocalUser
]
getConversationsAllFound :: TestM ()
@@ -98,3 +113,66 @@ getConversationsNotPartOf = do
let randoQualified = Qualified rando localDomain
GetConversationsResponse cs <- FedGalley.getConversations fedGalleyClient (GetConversationsRequest randoQualified [cnvId cnv1])
liftIO $ assertEqual "conversation list not empty" [] cs
+
+addLocalUser :: TestM ()
+addLocalUser = do
+ localDomain <- viewFederationDomain
+ c <- view tsCannon
+ alice <- randomUser
+ let qalice = Qualified alice localDomain
+ let dom = Domain "bobland.example.com"
+ bob <- randomId
+ let qbob = Qualified bob dom
+ conv <- randomId
+ let qconv = Qualified conv dom
+ fedGalleyClient <- view tsFedGalleyClient
+ now <- liftIO getCurrentTime
+ let cmu =
+ FedGalley.ConversationMemberUpdate
+ { FedGalley.cmuTime = now,
+ FedGalley.cmuOrigUserId = qbob,
+ FedGalley.cmuConvId = qconv,
+ FedGalley.cmuAlreadyPresentUsers = [],
+ FedGalley.cmuUsersAdd = [(qalice, roleNameWireMember)],
+ FedGalley.cmuUsersRemove = []
+ }
+ WS.bracketR c alice $ \ws -> do
+ FedGalley.updateConversationMemberships fedGalleyClient cmu
+ void . liftIO $
+ WS.assertMatch (5 # Second) ws $
+ wsAssertMemberJoinWithRole qconv qbob [qalice] roleNameWireMember
+ cassState <- view tsCass
+ convs <-
+ Cql.runClient cassState
+ . Cql.query Cql.selectUserRemoteConvs
+ $ Cql.params Cql.Quorum (Identity alice)
+ liftIO $ [(dom, conv)] @?= convs
+
+notifyLocalUser :: TestM ()
+notifyLocalUser = do
+ c <- view tsCannon
+ alice <- randomUser
+ bob <- randomId
+ charlie <- randomId
+ conv <- randomId
+ let bdom = Domain "bob.example.com"
+ cdom = Domain "charlie.example.com"
+ qbob = Qualified bob bdom
+ qconv = Qualified conv bdom
+ qcharlie = Qualified charlie cdom
+ fedGalleyClient <- view tsFedGalleyClient
+ now <- liftIO getCurrentTime
+ let cmu =
+ FedGalley.ConversationMemberUpdate
+ { FedGalley.cmuTime = now,
+ FedGalley.cmuOrigUserId = qbob,
+ FedGalley.cmuConvId = qconv,
+ FedGalley.cmuAlreadyPresentUsers = [alice],
+ FedGalley.cmuUsersAdd = [(qcharlie, roleNameWireMember)],
+ FedGalley.cmuUsersRemove = []
+ }
+ WS.bracketR c alice $ \ws -> do
+ FedGalley.updateConversationMemberships fedGalleyClient cmu
+ void . liftIO $
+ WS.assertMatch (5 # Second) ws $
+ wsAssertMemberJoinWithRole qconv qbob [qcharlie] roleNameWireMember
diff --git a/services/galley/test/integration/API/MessageTimer.hs b/services/galley/test/integration/API/MessageTimer.hs
index 690d605f54c..0328e97942f 100644
--- a/services/galley/test/integration/API/MessageTimer.hs
+++ b/services/galley/test/integration/API/MessageTimer.hs
@@ -27,6 +27,7 @@ import Control.Lens (view)
import qualified Data.LegalHold as LH
import Data.List1
import Data.Misc
+import Data.Qualified
import Galley.Types
import Galley.Types.Conversations.Roles
import qualified Galley.Types.Teams as Teams
@@ -140,6 +141,7 @@ messageTimerChangeO2O = do
messageTimerEvent :: TestM ()
messageTimerEvent = do
+ localDomain <- viewFederationDomain
ca <- view tsCannon
-- Create a conversation
[alice, bob] <- randomUsers 2
@@ -151,11 +153,13 @@ messageTimerEvent = do
-- Set timer to 1 second and check that all participants got the event
WS.bracketR2 ca alice bob $ \(wsA, wsB) -> do
let update = ConversationMessageTimerUpdate timer1sec
+ qcid = Qualified cid localDomain
+ qalice = Qualified alice localDomain
putMessageTimerUpdate alice cid update
!!! const 200 === statusCode
void . liftIO $
WS.assertMatchN (5 # Second) [wsA, wsB] $
- wsAssertConvMessageTimerUpdate cid alice update
+ wsAssertConvMessageTimerUpdate qcid qalice update
----------------------------------------------------------------------------
-- Utilities
diff --git a/services/galley/test/integration/API/Roles.hs b/services/galley/test/integration/API/Roles.hs
index 6da96d747d3..115c366056f 100644
--- a/services/galley/test/integration/API/Roles.hs
+++ b/services/galley/test/integration/API/Roles.hs
@@ -24,6 +24,7 @@ import Control.Lens (view)
import Data.ByteString.Conversion (toByteString')
import Data.Id
import Data.List1
+import Data.Qualified
import Galley.Types
import Galley.Types.Conversations.Roles
import Imports
@@ -63,12 +64,16 @@ testAllConversationRoles = do
handleConversationRoleAdmin :: TestM ()
handleConversationRoleAdmin = do
+ localDomain <- viewFederationDomain
c <- view tsCannon
alice <- randomUser
bob <- randomUser
chuck <- randomUser
eve <- randomUser
jack <- randomUser
+ let qalice = Qualified alice localDomain
+ qeve = Qualified eve localDomain
+ qjack = Qualified jack localDomain
connectUsers alice (list1 bob [chuck, eve, jack])
connectUsers eve (singleton bob)
connectUsers bob (singleton jack)
@@ -77,34 +82,39 @@ handleConversationRoleAdmin = do
rsp <- postConvWithRole alice [bob, chuck] (Just "gossip") [] Nothing Nothing role
void $ assertConvWithRole rsp RegularConv alice alice [bob, chuck] (Just "gossip") Nothing role
let cid = decodeConvId rsp
+ qcid = Qualified cid localDomain
-- Make sure everyone gets the correct event
postMembersWithRole alice (singleton eve) cid role !!! const 200 === statusCode
void . liftIO $
WS.assertMatchN (5 # Second) [wsA, wsB, wsC] $
- wsAssertMemberJoinWithRole cid alice [eve] role
+ wsAssertMemberJoinWithRole qcid qalice [qeve] role
-- Add a member to help out with testing
postMembersWithRole alice (singleton jack) cid roleNameWireMember !!! const 200 === statusCode
void . liftIO $
WS.assertMatchN (5 # Second) [wsA, wsB, wsC] $
- wsAssertMemberJoinWithRole cid alice [jack] roleNameWireMember
+ wsAssertMemberJoinWithRole qcid qalice [qjack] roleNameWireMember
return cid
-- Added bob as a wire_admin and do the checks
wireAdminChecks cid alice bob jack
-- Demote bob and run the member checks
WS.bracketR3 c alice bob chuck $ \(wsA, wsB, wsC) -> do
let updateDown = OtherMemberUpdate (Just roleNameWireMember)
+ qcid = Qualified cid localDomain
putOtherMember alice bob updateDown cid !!! assertActionSucceeded
void . liftIO . WS.assertMatchN (5 # Second) [wsA, wsB, wsC] $ do
- wsAssertMemberUpdateWithRole cid alice bob roleNameWireMember
+ wsAssertMemberUpdateWithRole qcid qalice bob roleNameWireMember
wireMemberChecks cid bob alice jack
handleConversationRoleMember :: TestM ()
handleConversationRoleMember = do
+ localDomain <- viewFederationDomain
c <- view tsCannon
alice <- randomUser
+ let qalice = Qualified alice localDomain
bob <- randomUser
chuck <- randomUser
eve <- randomUser
+ let qeve = Qualified eve localDomain
jack <- randomUser
connectUsers alice (list1 bob [chuck, eve])
connectUsers bob (singleton chuck)
@@ -114,22 +124,24 @@ handleConversationRoleMember = do
rsp <- postConvWithRole alice [bob, chuck] (Just "gossip") [] Nothing Nothing role
void $ assertConvWithRole rsp RegularConv alice alice [bob, chuck] (Just "gossip") Nothing role
let cid = decodeConvId rsp
+ qcid = Qualified cid localDomain
-- Make sure everyone gets the correct event
postMembersWithRole alice (singleton eve) cid role !!! const 200 === statusCode
void . liftIO $
WS.assertMatchN (5 # Second) [wsA, wsB, wsC] $
- wsAssertMemberJoinWithRole cid alice [eve] role
+ wsAssertMemberJoinWithRole qcid qalice [qeve] role
return cid
-- Added bob as a wire_member and do the checks
wireMemberChecks cid bob alice chuck
-- Let's promote bob
WS.bracketR3 c alice bob chuck $ \(wsA, wsB, wsC) -> do
+ let qcid = Qualified cid localDomain
let updateUp = OtherMemberUpdate (Just roleNameWireAdmin)
-- Chuck cannot update, member only
putOtherMember chuck bob updateUp cid !!! assertActionDenied
putOtherMember alice bob updateUp cid !!! assertActionSucceeded
void . liftIO . WS.assertMatchN (5 # Second) [wsA, wsB, wsC] $ do
- wsAssertMemberUpdateWithRole cid alice bob roleNameWireAdmin
+ wsAssertMemberUpdateWithRole qcid qalice bob roleNameWireAdmin
wireAdminChecks cid bob alice chuck
-- | Given an admin, another admin and a member run all
diff --git a/services/galley/test/integration/API/Teams.hs b/services/galley/test/integration/API/Teams.hs
index 5634570dfcc..fccc6065b2c 100644
--- a/services/galley/test/integration/API/Teams.hs
+++ b/services/galley/test/integration/API/Teams.hs
@@ -23,7 +23,7 @@ module API.Teams
where
import API.SQS
-import API.Util
+import API.Util hiding (deleteTeam)
import qualified API.Util as Util
import qualified API.Util.TeamFeature as Util
import Bilge hiding (timeout)
@@ -44,6 +44,7 @@ import qualified Data.LegalHold as LH
import Data.List1
import qualified Data.List1 as List1
import Data.Misc (HttpsUrl, PlainTextPassword (..))
+import Data.Qualified
import Data.Range
import qualified Data.Set as Set
import Data.String.Conversions (cs)
@@ -71,7 +72,7 @@ import Test.Tasty
import Test.Tasty.Cannon (TimeoutUnit (..), (#))
import qualified Test.Tasty.Cannon as WS
import Test.Tasty.HUnit
-import TestHelpers (test)
+import TestHelpers (test, viewFederationDomain)
import TestSetup (TestM, TestSetup, tsBrig, tsCannon, tsGConf, tsGalley)
import UnliftIO (mapConcurrently, mapConcurrently_)
import Wire.API.Team.Export (TeamExportUser (..))
@@ -362,7 +363,7 @@ testEnableSSOPerTeam = do
let putSSOEnabledInternalCheckNotImplemented :: HasCallStack => TestM ()
putSSOEnabledInternalCheckNotImplemented = do
g <- view tsGalley
- Wai.Error status label _ <-
+ Wai.Error status label _ _ <-
responseJsonUnsafe
<$> put
( g
@@ -392,7 +393,7 @@ testEnableTeamSearchVisibilityPerTeam = do
liftIO $ assertEqual msg enabledness statusValue
let putSearchVisibilityCheckNotAllowed :: (HasCallStack, Monad m, MonadIO m, MonadHttp m) => m ()
putSearchVisibilityCheckNotAllowed = do
- Wai.Error status label _ <- responseJsonUnsafe <$> putSearchVisibility g owner tid SearchVisibilityNoNameOutsideTeam
+ Wai.Error status label _ _ <- responseJsonUnsafe <$> putSearchVisibility g owner tid SearchVisibilityNoNameOutsideTeam
liftIO $ do
assertEqual "bad status" status403 status
assertEqual "bad label" "team-search-visibility-not-enabled" label
@@ -595,6 +596,7 @@ testAddTeamMemberInternal = do
testRemoveNonBindingTeamMember :: TestM ()
testRemoveNonBindingTeamMember = do
+ localDomain <- viewFederationDomain
c <- view tsCannon
g <- view tsGalley
owner <- Util.randomUser
@@ -635,12 +637,13 @@ testRemoveNonBindingTeamMember = do
-- Ensure that `mem1` is still a user (tid is not a binding team)
Util.ensureDeletedState False owner (mem1 ^. userId)
mapConcurrently_ (checkTeamMemberLeave tid (mem1 ^. userId)) [wsOwner, wsMem1, wsMem2]
- checkConvMemberLeaveEvent cid2 (mem1 ^. userId) wsMext1
- checkConvMemberLeaveEvent cid3 (mem1 ^. userId) wsMext3
+ checkConvMemberLeaveEvent (Qualified cid2 localDomain) (mem1 ^. userId) wsMext1
+ checkConvMemberLeaveEvent (Qualified cid3 localDomain) (mem1 ^. userId) wsMext3
WS.assertNoEvent timeout ws
testRemoveBindingTeamMember :: Bool -> TestM ()
testRemoveBindingTeamMember ownerHasPassword = do
+ localDomain <- viewFederationDomain
g <- view tsGalley
c <- view tsCannon
-- Owner who creates the team must have an email, This is why we run all tests with a second
@@ -719,7 +722,7 @@ testRemoveBindingTeamMember ownerHasPassword = do
!!! const 202
=== statusCode
checkTeamMemberLeave tid (mem1 ^. userId) wsOwner
- checkConvMemberLeaveEvent cid1 (mem1 ^. userId) wsMext
+ checkConvMemberLeaveEvent (Qualified cid1 localDomain) (mem1 ^. userId) wsMext
assertQueue "team member leave" $ tUpdate 2 [ownerWithPassword, owner]
WS.assertNoEvent timeout [wsMext]
-- Mem1 is now gone from Wire
@@ -880,7 +883,7 @@ testAddManagedConv = do
let tinfo = ConvTeamInfo tid True
let conv =
NewConvManaged $
- NewConv [owner] (Just "blah") (Set.fromList []) Nothing (Just tinfo) Nothing Nothing roleNameWireAdmin
+ NewConv [owner] [] (Just "blah") (Set.fromList []) Nothing (Just tinfo) Nothing Nothing roleNameWireAdmin
post
( g
. path "/conversations"
@@ -1001,6 +1004,7 @@ testUpdateTeamConv (rolePermissions -> perms) convRole = do
testDeleteTeam :: TestM ()
testDeleteTeam = do
+ localDomain <- viewFederationDomain
g <- view tsGalley
c <- view tsCannon
owner <- Util.randomUser
@@ -1024,7 +1028,7 @@ testDeleteTeam = do
checkTeamDeleteEvent tid wsOwner
checkTeamDeleteEvent tid wsMember
-- team members should not receive conversation delete events
- checkConvDeleteEvent cid1 wsExtern
+ checkConvDeleteEvent (Qualified cid1 localDomain) wsExtern
WS.assertNoEvent timeout [wsOwner, wsExtern, wsMember]
get (g . paths ["teams", toByteString' tid] . zUser owner)
!!! const 404 === statusCode
@@ -1187,6 +1191,7 @@ testDeleteBindingTeam ownerHasPassword = do
testDeleteTeamConv :: TestM ()
testDeleteTeamConv = do
+ localDomain <- viewFederationDomain
g <- view tsGalley
c <- view tsCannon
owner <- Util.randomUser
@@ -1216,8 +1221,9 @@ testDeleteTeamConv = do
-- i.e., as both a regular "conversation.delete" to all
-- conversation members and as "team.conversation-delete"
-- to all team members not part of the conversation
- checkConvDeleteEvent cid2 wsOwner
- checkConvDeleteEvent cid2 wsMember
+ let qcid2 = Qualified cid2 localDomain
+ checkConvDeleteEvent qcid2 wsOwner
+ checkConvDeleteEvent qcid2 wsMember
WS.assertNoEvent timeout [wsOwner, wsMember]
delete
( g
@@ -1231,9 +1237,10 @@ testDeleteTeamConv = do
-- i.e., as both a regular "conversation.delete" to all
-- conversation members and as "team.conversation-delete"
-- to all team members not part of the conversation
- checkConvDeleteEvent cid1 wsOwner
- checkConvDeleteEvent cid1 wsMember
- checkConvDeleteEvent cid1 wsExtern
+ let qcid1 = Qualified cid1 localDomain
+ checkConvDeleteEvent qcid1 wsOwner
+ checkConvDeleteEvent qcid1 wsMember
+ checkConvDeleteEvent qcid1 wsExtern
WS.assertNoEvent timeout [wsOwner, wsMember, wsExtern]
for_ [cid1, cid2] $ \x ->
for_ [owner, member ^. userId, extern] $ \u -> do
@@ -1281,6 +1288,7 @@ testUpdateTeam = do
testTeamAddRemoveMemberAboveThresholdNoEvents :: HasCallStack => TestM ()
testTeamAddRemoveMemberAboveThresholdNoEvents = do
+ localDomain <- viewFederationDomain
o <- view tsGConf
c <- view tsCannon
let fanoutLimit = fromIntegral . fromRange $ Galley.currentFanoutLimit o
@@ -1329,7 +1337,7 @@ testTeamAddRemoveMemberAboveThresholdNoEvents = do
cid1 <- Util.createTeamConv owner tid [] (Just "blaa") Nothing Nothing
Util.postMembers owner (list1 extern []) cid1 !!! const 200 === statusCode
-- Test team deletion (should contain only conv. removal and user.deletion for _non_ team members)
- deleteTeam tid owner [] [cid1] extern
+ deleteTeam tid owner [] [Qualified cid1 localDomain] extern
ensureQueueEmpty
where
modifyUserProfileAndExpectEvent :: HasCallStack => Bool -> UserId -> [UserId] -> TestM ()
@@ -1400,7 +1408,7 @@ testTeamAddRemoveMemberAboveThresholdNoEvents = do
-- User deletion events
mapM_ (checkUserDeleteEvent victim) wsOthers
Util.ensureDeletedState True owner victim
- deleteTeam :: HasCallStack => TeamId -> UserId -> [UserId] -> [ConvId] -> UserId -> TestM ()
+ deleteTeam :: HasCallStack => TeamId -> UserId -> [UserId] -> [Qualified ConvId] -> UserId -> TestM ()
deleteTeam tid owner otherRealUsersInTeam teamCidsThatExternBelongsTo extern = do
c <- view tsCannon
g <- view tsGalley
@@ -1704,7 +1712,7 @@ checkTeamDeleteEvent tid w = WS.assertMatch_ timeout w $ \notif -> do
e ^. eventTeam @?= tid
e ^. eventData @?= Nothing
-checkConvDeleteEvent :: HasCallStack => ConvId -> WS.WebSocket -> TestM ()
+checkConvDeleteEvent :: HasCallStack => Qualified ConvId -> WS.WebSocket -> TestM ()
checkConvDeleteEvent cid w = WS.assertMatch_ timeout w $ \notif -> do
ntfTransient notif @?= False
let e = List1.head (WS.unpackPayload notif)
@@ -1712,7 +1720,7 @@ checkConvDeleteEvent cid w = WS.assertMatch_ timeout w $ \notif -> do
evtConv e @?= cid
evtData e @?= Conv.EdConvDelete
-checkConvMemberLeaveEvent :: HasCallStack => ConvId -> UserId -> WS.WebSocket -> TestM ()
+checkConvMemberLeaveEvent :: HasCallStack => Qualified ConvId -> UserId -> WS.WebSocket -> TestM ()
checkConvMemberLeaveEvent cid usr w = WS.assertMatch_ timeout w $ \notif -> do
ntfTransient notif @?= False
let e = List1.head (WS.unpackPayload notif)
@@ -1724,6 +1732,9 @@ checkConvMemberLeaveEvent cid usr w = WS.assertMatch_ timeout w $ \notif -> do
postCryptoBroadcastMessageJson :: TestM ()
postCryptoBroadcastMessageJson = do
+ localDomain <- viewFederationDomain
+ let q :: Id a -> Qualified (Id a)
+ q = (`Qualified` localDomain)
c <- view tsCannon
-- Team1: Alice, Bob. Team2: Charlie. Regular user: Dan. Connect Alice,Charlie,Dan
(alice, tid) <- Util.createBindingTeam
@@ -1750,18 +1761,21 @@ postCryptoBroadcastMessageJson = do
const 201 === statusCode
assertTrue_ (eqMismatch [] [] [] . responseJsonUnsafe)
-- Bob should get the broadcast (team member of alice)
- void . liftIO $ WS.assertMatch t wsB (wsAssertOtr (selfConv bob) alice ac bc "ciphertext1")
+ void . liftIO $ WS.assertMatch t wsB (wsAssertOtr (q (selfConv bob)) (q alice) ac bc "ciphertext1")
-- Charlie should get the broadcast (contact of alice and user of teams feature)
- void . liftIO $ WS.assertMatch t wsC (wsAssertOtr (selfConv charlie) alice ac cc "ciphertext2")
+ void . liftIO $ WS.assertMatch t wsC (wsAssertOtr (q (selfConv charlie)) (q alice) ac cc "ciphertext2")
-- Dan should get the broadcast (contact of alice and not user of teams feature)
- void . liftIO $ WS.assertMatch t wsD (wsAssertOtr (selfConv dan) alice ac dc "ciphertext3")
+ void . liftIO $ WS.assertMatch t wsD (wsAssertOtr (q (selfConv dan)) (q alice) ac dc "ciphertext3")
-- Alice's first client should not get the broadcast
- assertNoMsg wsA1 (wsAssertOtr (selfConv alice) alice ac ac "ciphertext0")
+ assertNoMsg wsA1 (wsAssertOtr (q (selfConv alice)) (q alice) ac ac "ciphertext0")
-- Alice's second client should get the broadcast
- void . liftIO $ WS.assertMatch t wsA2 (wsAssertOtr (selfConv alice) alice ac ac2 "ciphertext0")
+ void . liftIO $ WS.assertMatch t wsA2 (wsAssertOtr (q (selfConv alice)) (q alice) ac ac2 "ciphertext0")
postCryptoBroadcastMessageJsonFilteredTooLargeTeam :: TestM ()
postCryptoBroadcastMessageJsonFilteredTooLargeTeam = do
+ localDomain <- viewFederationDomain
+ let q :: Id a -> Qualified (Id a)
+ q = (`Qualified` localDomain)
opts <- view tsGConf
g <- view tsCannon
c <- view tsCannon
@@ -1806,15 +1820,15 @@ postCryptoBroadcastMessageJsonFilteredTooLargeTeam = do
const 201 === statusCode
assertTrue_ (eqMismatch [] [] [] . responseJsonUnsafe)
-- Bob should get the broadcast (team member of alice)
- void . liftIO $ WS.assertMatch t wsB (wsAssertOtr (selfConv bob) alice ac bc "ciphertext1")
+ void . liftIO $ WS.assertMatch t wsB (wsAssertOtr (q (selfConv bob)) (q alice) ac bc "ciphertext1")
-- Charlie should get the broadcast (contact of alice and user of teams feature)
- void . liftIO $ WS.assertMatch t wsC (wsAssertOtr (selfConv charlie) alice ac cc "ciphertext2")
+ void . liftIO $ WS.assertMatch t wsC (wsAssertOtr (q (selfConv charlie)) (q alice) ac cc "ciphertext2")
-- Dan should get the broadcast (contact of alice and not user of teams feature)
- void . liftIO $ WS.assertMatch t wsD (wsAssertOtr (selfConv dan) alice ac dc "ciphertext3")
+ void . liftIO $ WS.assertMatch t wsD (wsAssertOtr (q (selfConv dan)) (q alice) ac dc "ciphertext3")
-- Alice's first client should not get the broadcast
- assertNoMsg wsA1 (wsAssertOtr (selfConv alice) alice ac ac "ciphertext0")
+ assertNoMsg wsA1 (wsAssertOtr (q (selfConv alice)) (q alice) ac ac "ciphertext0")
-- Alice's second client should get the broadcast
- void . liftIO $ WS.assertMatch t wsA2 (wsAssertOtr (selfConv alice) alice ac ac2 "ciphertext0")
+ void . liftIO $ WS.assertMatch t wsA2 (wsAssertOtr (q (selfConv alice)) (q alice) ac ac2 "ciphertext0")
postCryptoBroadcastMessageJsonReportMissingBody :: TestM ()
postCryptoBroadcastMessageJsonReportMissingBody = do
@@ -1833,6 +1847,9 @@ postCryptoBroadcastMessageJsonReportMissingBody = do
postCryptoBroadcastMessageJson2 :: TestM ()
postCryptoBroadcastMessageJson2 = do
+ localDomain <- viewFederationDomain
+ let q :: Id a -> Qualified (Id a)
+ q = (`Qualified` localDomain)
c <- view tsCannon
-- Team1: Alice, Bob. Team2: Charlie. Connect Alice,Charlie
(alice, tid) <- Util.createBindingTeam
@@ -1857,18 +1874,18 @@ postCryptoBroadcastMessageJson2 = do
Util.postOtrBroadcastMessage id alice ac m2 !!! do
const 201 === statusCode
assertTrue "No devices expected" (eqMismatch [] [] [] . responseJsonUnsafe)
- void . liftIO $ WS.assertMatch t wsB (wsAssertOtr (selfConv bob) alice ac bc "ciphertext2")
- void . liftIO $ WS.assertMatch t wsE (wsAssertOtr (selfConv charlie) alice ac cc "ciphertext2")
+ void . liftIO $ WS.assertMatch t wsB (wsAssertOtr (q (selfConv bob)) (q alice) ac bc "ciphertext2")
+ void . liftIO $ WS.assertMatch t wsE (wsAssertOtr (q (selfConv charlie)) (q alice) ac cc "ciphertext2")
-- Redundant self
WS.bracketR3 c alice bob charlie $ \(wsA, wsB, wsE) -> do
let m3 = [(alice, ac, "ciphertext3"), (bob, bc, "ciphertext3"), (charlie, cc, "ciphertext3")]
Util.postOtrBroadcastMessage id alice ac m3 !!! do
const 201 === statusCode
assertTrue "2: Only Alice and her device" (eqMismatch [] [(alice, Set.singleton ac)] [] . responseJsonUnsafe)
- void . liftIO $ WS.assertMatch t wsB (wsAssertOtr (selfConv bob) alice ac bc "ciphertext3")
- void . liftIO $ WS.assertMatch t wsE (wsAssertOtr (selfConv charlie) alice ac cc "ciphertext3")
+ void . liftIO $ WS.assertMatch t wsB (wsAssertOtr (q (selfConv bob)) (q alice) ac bc "ciphertext3")
+ void . liftIO $ WS.assertMatch t wsE (wsAssertOtr (q (selfConv charlie)) (q alice) ac cc "ciphertext3")
-- Alice should not get it
- assertNoMsg wsA (wsAssertOtr (selfConv alice) alice ac ac "ciphertext3")
+ assertNoMsg wsA (wsAssertOtr (q (selfConv alice)) (q alice) ac ac "ciphertext3")
-- Deleted charlie
WS.bracketR2 c bob charlie $ \(wsB, wsE) -> do
deleteClient charlie cc (Just defPassword) !!! const 200 === statusCode
@@ -1876,12 +1893,15 @@ postCryptoBroadcastMessageJson2 = do
Util.postOtrBroadcastMessage id alice ac m4 !!! do
const 201 === statusCode
assertTrue "3: Only Charlie and his device" (eqMismatch [] [] [(charlie, Set.singleton cc)] . responseJsonUnsafe)
- void . liftIO $ WS.assertMatch t wsB (wsAssertOtr (selfConv bob) alice ac bc "ciphertext4")
+ void . liftIO $ WS.assertMatch t wsB (wsAssertOtr (q (selfConv bob)) (q alice) ac bc "ciphertext4")
-- charlie should not get it
- assertNoMsg wsE (wsAssertOtr (selfConv charlie) alice ac cc "ciphertext4")
+ assertNoMsg wsE (wsAssertOtr (q (selfConv charlie)) (q alice) ac cc "ciphertext4")
postCryptoBroadcastMessageProto :: TestM ()
postCryptoBroadcastMessageProto = do
+ localDomain <- viewFederationDomain
+ let q :: Id a -> Qualified (Id a)
+ q = (`Qualified` localDomain)
-- similar to postCryptoBroadcastMessageJson, postCryptoBroadcastMessageJsonReportMissingBody except uses protobuf
c <- view tsCannon
@@ -1906,11 +1926,11 @@ postCryptoBroadcastMessageProto = do
const 201 === statusCode
assertTrue_ (eqMismatch [] [] [] . responseJsonUnsafe)
-- Bob should get the broadcast (team member of alice)
- void . liftIO $ WS.assertMatch t wsB (wsAssertOtr' (encodeCiphertext "data") (selfConv bob) alice ac bc ciphertext)
+ void . liftIO $ WS.assertMatch t wsB (wsAssertOtr' (encodeCiphertext "data") (q (selfConv bob)) (q alice) ac bc ciphertext)
-- Charlie should get the broadcast (contact of alice and user of teams feature)
- void . liftIO $ WS.assertMatch t wsC (wsAssertOtr' (encodeCiphertext "data") (selfConv charlie) alice ac cc ciphertext)
+ void . liftIO $ WS.assertMatch t wsC (wsAssertOtr' (encodeCiphertext "data") (q (selfConv charlie)) (q alice) ac cc ciphertext)
-- Dan should get the broadcast (contact of alice and not user of teams feature)
- void . liftIO $ WS.assertMatch t wsD (wsAssertOtr' (encodeCiphertext "data") (selfConv dan) alice ac dc ciphertext)
+ void . liftIO $ WS.assertMatch t wsD (wsAssertOtr' (encodeCiphertext "data") (q (selfConv dan)) (q alice) ac dc ciphertext)
-- Alice should not get her own broadcast
WS.assertNoEvent timeout ws
let inbody = Just [bob] -- body triggers report
@@ -1929,8 +1949,10 @@ postCryptoBroadcastMessageNoTeam = do
postCryptoBroadcastMessage100OrMaxConns :: TestM ()
postCryptoBroadcastMessage100OrMaxConns = do
+ localDomain <- viewFederationDomain
c <- view tsCannon
(alice, ac) <- randomUserWithClient (someLastPrekeys !! 0)
+ let qalice = Qualified alice localDomain
_ <- createBindingTeamInternal "foo" alice
assertQueue "" tActivate
((bob, bc), others) <- createAndConnectUserWhileLimitNotReached alice (100 :: Int) [] (someLastPrekeys !! 1)
@@ -1942,9 +1964,11 @@ postCryptoBroadcastMessage100OrMaxConns = do
Util.postOtrBroadcastMessage id alice ac msg !!! do
const 201 === statusCode
assertTrue_ (eqMismatch [] [] [] . responseJsonUnsafe)
- void . liftIO $ WS.assertMatch t (Imports.head ws) (wsAssertOtr (selfConv bob) alice ac bc "ciphertext")
- for_ (zip (tail ws) others) $ \(wsU, (u, clt)) ->
- liftIO $ WS.assertMatch t wsU (wsAssertOtr (selfConv u) alice ac clt "ciphertext")
+ let qbobself = Qualified (selfConv bob) localDomain
+ void . liftIO $ WS.assertMatch t (Imports.head ws) (wsAssertOtr qbobself qalice ac bc "ciphertext")
+ for_ (zip (tail ws) others) $ \(wsU, (u, clt)) -> do
+ let qself = Qualified (selfConv u) localDomain
+ liftIO $ WS.assertMatch t wsU (wsAssertOtr qself qalice ac clt "ciphertext")
where
createAndConnectUserWhileLimitNotReached alice remaining acc pk = do
(uid, cid) <- randomUserWithClient pk
diff --git a/services/galley/test/integration/API/Teams/LegalHold.hs b/services/galley/test/integration/API/Teams/LegalHold.hs
index a24bbb8ee3a..8a1e09c999d 100644
--- a/services/galley/test/integration/API/Teams/LegalHold.hs
+++ b/services/galley/test/integration/API/Teams/LegalHold.hs
@@ -25,11 +25,13 @@ module API.Teams.LegalHold
where
import API.SQS
+import qualified API.SQS as SQS
import API.Util
import Bilge hiding (accept, head, timeout, trace)
import Bilge.Assert
+import qualified Bilge.TestSession as BilgeTest
import Brig.Types.Client
-import Brig.Types.Intra (ConnectionStatus (ConnectionStatus))
+import Brig.Types.Intra (ConnectionStatus (ConnectionStatus), UserSet (..))
import Brig.Types.Provider
import Brig.Types.Team.LegalHold hiding (userId)
import Brig.Types.Test.Arbitrary ()
@@ -52,10 +54,11 @@ import Data.Json.Util (toUTCTimeMillis)
import Data.LegalHold
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.List1 as List1
+import qualified Data.Map.Strict as Map
import Data.Misc (PlainTextPassword)
import Data.PEM
-import Data.Proxy (Proxy (Proxy))
import Data.Range
+import qualified Data.Set as Set
import Data.String.Conversions (LBS, cs)
import Data.Text.Encoding (encodeUtf8)
import qualified Data.Time.Clock as Time
@@ -63,24 +66,19 @@ import qualified Galley.App as Galley
import qualified Galley.Data as Data
import qualified Galley.Data.LegalHold as LegalHoldData
import Galley.External.LegalHoldService (validateServiceKey)
-import Galley.Options (optSettings, setFeatureFlags, setLegalHoldTeamsWhitelist)
+import Galley.Options (optSettings, setFeatureFlags)
import qualified Galley.Types.Clients as Clients
import Galley.Types.Teams
import Gundeck.Types.Notification (ntfPayload)
import Imports
import Network.HTTP.Types.Status (status200, status400, status404)
-import Network.Wai
import Network.Wai as Wai
import qualified Network.Wai.Handler.Warp as Warp
import qualified Network.Wai.Handler.Warp.Internal as Warp
import qualified Network.Wai.Handler.WarpTLS as Warp
-import qualified Network.Wai.Test as WaiTest
import qualified Network.Wai.Utilities.Error as Error
import qualified Network.Wai.Utilities.Response as Wai
-import Servant.Swagger (validateEveryToJSON)
-import System.Environment (withArgs)
import System.IO (hPutStrLn)
-import Test.Hspec (hspec)
import Test.QuickCheck.Instances ()
import Test.Tasty
import qualified Test.Tasty.Cannon as WS
@@ -90,46 +88,50 @@ import TestSetup
import Wire.API.Connection (UserConnection)
import qualified Wire.API.Connection as Conn
import qualified Wire.API.Message as Msg
-import qualified Wire.API.Routes.Public.LegalHold as LegalHoldAPI
import qualified Wire.API.Team.Feature as Public
import Wire.API.User (UserProfile (..))
+import Wire.API.User.Client (UserClients (..), UserClientsFull (userClientsFull))
+import qualified Wire.API.User.Client as Client
-onlyIfLhEnabled :: TestM () -> TestM ()
-onlyIfLhEnabled action = do
+onlyIfLhWhitelisted :: TestM () -> TestM ()
+onlyIfLhWhitelisted action = do
featureLegalHold <- view (tsGConf . optSettings . setFeatureFlags . flagLegalHold)
case featureLegalHold of
FeatureLegalHoldDisabledPermanently ->
- liftIO $ hPutStrLn stderr "*** legalhold feature flag disabled, not running this test case"
+ liftIO $ hPutStrLn stderr errmsg
FeatureLegalHoldDisabledByDefault ->
- action
- FeatureLegalHoldWhitelistTeamsAndImplicitConsent ->
- error
- "`FeatureLegalHoldWhitelistTeamsAndImplicitConsent` requires setting up a mock galley \
- \with `withLHWhitelist`; don't call `onlyIfLhEnabled` if you do that, that's redundant."
+ liftIO $ hPutStrLn stderr errmsg
+ FeatureLegalHoldWhitelistTeamsAndImplicitConsent -> action
+ where
+ errmsg =
+ "*** skipping test. This test only works if you manually adjust the server config files\
+ \(the 'withLHWhitelist' trick does not work because it does not allow \
+ \brig to talk to the dynamically spawned galley)."
tests :: IO TestSetup -> TestTree
-tests s =
+tests s = testGroup "Legalhold" [testsPublic s, testsInternal s]
+
+testsPublic :: IO TestSetup -> TestTree
+testsPublic s =
-- See also Client Tests in Brig; where behaviour around deleting/adding LH clients is tested
testGroup
- "Teams LegalHold API"
- [ test s "swagger / json consistency" (onlyIfLhEnabled testSwaggerJsonConsistency),
- -- device handling (CRUD)
- test s "POST /teams/{tid}/legalhold/{uid}" (onlyIfLhEnabled testRequestLegalHoldDevice),
- test s "PUT /teams/{tid}/legalhold/approve" (onlyIfLhEnabled testApproveLegalHoldDevice),
+ "Teams LegalHold API (with flag whitelist-teams-and-implicit-consent)"
+ [ -- device handling (CRUD)
+ test s "POST /teams/{tid}/legalhold/{uid}" (onlyIfLhWhitelisted testRequestLegalHoldDevice),
+ test s "PUT /teams/{tid}/legalhold/approve" (onlyIfLhWhitelisted testApproveLegalHoldDevice),
test s "(user denies approval: nothing needs to be done in backend)" (pure ()),
- test s "GET /teams/{tid}/legalhold/{uid}" (onlyIfLhEnabled testGetLegalHoldDeviceStatus),
- test s "DELETE /teams/{tid}/legalhold/{uid}" (onlyIfLhEnabled testDisableLegalHoldForUser),
+ test s "GET /teams/{tid}/legalhold/{uid}" (onlyIfLhWhitelisted testGetLegalHoldDeviceStatus),
+ test s "DELETE /teams/{tid}/legalhold/{uid}" (onlyIfLhWhitelisted testDisableLegalHoldForUser),
-- legal hold settings
- test s "POST /teams/{tid}/legalhold/settings" (onlyIfLhEnabled testCreateLegalHoldTeamSettings),
- test s "GET /teams/{tid}/legalhold/settings" (onlyIfLhEnabled testGetLegalHoldTeamSettings),
- test s "DELETE /teams/{tid}/legalhold/settings" (onlyIfLhEnabled testRemoveLegalHoldFromTeam),
- test s "GET, PUT [/i]?/teams/{tid}/legalhold" (onlyIfLhEnabled testEnablePerTeam),
- test s "GET, PUT [/i]?/teams/{tid}/legalhold - too large" (onlyIfLhEnabled testEnablePerTeamTooLarge),
+ test s "POST /teams/{tid}/legalhold/settings" (onlyIfLhWhitelisted testCreateLegalHoldTeamSettings),
+ test s "GET /teams/{tid}/legalhold/settings" (onlyIfLhWhitelisted testGetLegalHoldTeamSettings),
+ test s "Not implemented: DELETE /teams/{tid}/legalhold/settings" (onlyIfLhWhitelisted testRemoveLegalHoldFromTeam),
+ test s "GET [/i]?/teams/{tid}/legalhold" (onlyIfLhWhitelisted testEnablePerTeam),
-- behavior of existing end-points
- test s "POST /clients" (onlyIfLhEnabled testCannotCreateLegalHoldDeviceOldAPI),
- test s "GET /teams/{tid}/members" (onlyIfLhEnabled testGetTeamMembersIncludesLHStatus),
- test s "POST /register - cannot add team members with LH - too large" (onlyIfLhEnabled testAddTeamUserTooLargeWithLegalhold),
- test s "GET legalhold status in user profile" testGetLegalholdStatus,
+ test s "POST /clients" (onlyIfLhWhitelisted testCannotCreateLegalHoldDeviceOldAPI),
+ test s "GET /teams/{tid}/members" (onlyIfLhWhitelisted testGetTeamMembersIncludesLHStatus),
+ test s "POST /register - can add team members above fanout limit when whitelisting is enabled" (onlyIfLhWhitelisted testAddTeamUserTooLargeWithLegalholdWhitelisted),
+ test s "GET legalhold status in user profile" (onlyIfLhWhitelisted testGetLegalholdStatus),
{- TODO:
conversations/{cnv}/otr/messages - possibly show the legal hold device (if missing) as a different device type (or show that on device level, depending on how client teams prefer)
GET /team/{tid}/members - show legal hold status of all members
@@ -138,67 +140,54 @@ tests s =
testGroup
"settings.legalholdEnabledTeams"
[ testGroup
- "teams not listed"
- [ test s "happy flow" testNotInWhitelist
- ],
- testGroup
"teams listed"
[ test s "happy flow" testInWhitelist,
- test s "handshake between LH device and user without consent is blocked" testNoConsentBlockDeviceHandshake,
- test
- s
- "If LH is activated for other user in 1:1 conv, 1:1 conv is blocked (connect after, personal peer)"
- (testNoConsentBlockOne2OneConv False False False False),
- test
- s
- "If LH is activated for other user in 1:1 conv, 1:1 conv is blocked (connect after, team peer)"
- (testNoConsentBlockOne2OneConv False True False False),
- test
- s
- "If LH is activated for other user in 1:1 conv, 1:1 conv is blocked (connect after, team peer, approve LH device)"
- (testNoConsentBlockOne2OneConv False True True False),
- test
- s
- "If LH is activated for other user in 1:1 conv, 1:1 conv is blocked (connect after, team peer, leave conn pending)"
- (testNoConsentBlockOne2OneConv False True False True),
- test
- s
- "If LH is activated for other user in 1:1 conv, 1:1 conv is blocked (connect after, team peer, approve LH device, leave conn pending)"
- (testNoConsentBlockOne2OneConv False True True True),
- test
- s
- "If LH is activated for other user in 1:1 conv, 1:1 conv is blocked (connect before, personal peer)"
- (testNoConsentBlockOne2OneConv True False False False),
- test
- s
- "If LH is activated for other user in 1:1 conv, 1:1 conv is blocked (connect before, team peer)"
- (testNoConsentBlockOne2OneConv True True False False),
- test
- s
- "If LH is activated for other user in 1:1 conv, 1:1 conv is blocked (connect before, team peer, approve LH device)"
- (testNoConsentBlockOne2OneConv True True True False),
+ test s "handshake between LH device and user with old clients is blocked" testOldClientsBlockDeviceHandshake,
+ testGroup "no-consent" $
+ flip fmap [(a, b, c, d) | a <- [minBound ..], b <- [minBound ..], c <- [minBound ..], d <- [minBound ..]] $
+ \args@(a, b, c, d) ->
+ test s (show args) $ testNoConsentBlockOne2OneConv a b c d,
test
s
"If LH is activated for other user in group conv, this user gets removed with helpful message"
testNoConsentBlockGroupConv,
- test s "bench hack" testBenchHack
+ test s "bench hack" testBenchHack,
+ test s "User cannot fetch prekeys of LH users if consent is missing" (testClaimKeys TCKConsentMissing),
+ test s "User cannot fetch prekeys of LH users: if user has old client" (testClaimKeys TCKOldClient),
+ test s "User can fetch prekeys of LH users if consent is given and user has only new clients" (testClaimKeys TCKConsentAndNewClients)
]
]
]
--- | Make sure the ToSchema and ToJSON instances are in sync for all of the swagger docs.
--- (this is more of a unit test, but galley doesn't have any, and it seems not worth it to
--- start another test suite just for this one line.)
---
--- UPDATE(fisx): galley does have unit tests now! (and of course the "not worth it" was
--- deeply misguided from me.)
-testSwaggerJsonConsistency :: TestM ()
-testSwaggerJsonConsistency = do
- liftIO . withArgs [] . hspec $ validateEveryToJSON (Proxy @LegalHoldAPI.ServantAPI)
+testsInternal :: IO TestSetup -> TestTree
+testsInternal s =
+ testGroup
+ "Legalhold Internal API"
+ [test s "PUT, DELETE /i/legalhold/whitelisted-teams" (onlyIfLhWhitelisted testWhitelistingTeams)]
+
+testWhitelistingTeams :: TestM ()
+testWhitelistingTeams = do
+ let testTeamWhitelisted :: HasCallStack => TeamId -> TestM Bool
+ testTeamWhitelisted tid = do
+ res <- getLHWhitelistedTeam tid
+ pure (Bilge.responseStatus res == status200)
+
+ let expectWhitelisted :: HasCallStack => Bool -> TeamId -> TestM ()
+ expectWhitelisted yes tid = do
+ let msg = if yes then "team should be whitelisted" else "team should not be whitelisted"
+ aFewTimesAssertBool msg (== yes) (testTeamWhitelisted tid)
+
+ tid <- withTeam $ \_owner tid -> do
+ expectWhitelisted False tid
+ putLHWhitelistTeam tid !!! const 200 === statusCode
+ expectWhitelisted True tid
+ pure tid
+
+ expectWhitelisted False tid
+ ensureQueueEmpty
testRequestLegalHoldDevice :: TestM ()
-testRequestLegalHoldDevice = do
- (owner, tid) <- createBindingTeam
+testRequestLegalHoldDevice = withTeam $ \owner tid -> do
member <- randomUser
addTeamMemberInternal tid member (rolePermissions RoleMember) Nothing
ensureQueueEmpty
@@ -207,10 +196,10 @@ testRequestLegalHoldDevice = do
cannon <- view tsCannon
-- Assert that the appropriate LegalHold Request notification is sent to the user's
-- clients
- WS.bracketR2 cannon member member $ \(ws, ws') -> withDummyTestServiceForTeam owner tid $ \_chan -> do
+ WS.bracketR2 cannon member member $ \(ws, ws') -> withDummyTestServiceForTeamNoService $ \_chan -> do
do
-- test device creation without consent
- requestLegalHoldDevice member member tid !!! testResponse 403 (Just "operation-denied")
+ requestLegalHoldDevice member member tid !!! testResponse 403 (Just "legalhold-not-enabled")
UserLegalHoldStatusResponse userStatus _ _ <- getUserStatusTyped member tid
liftIO $
assertEqual
@@ -219,7 +208,7 @@ testRequestLegalHoldDevice = do
userStatus
do
- requestLegalHoldDevice owner member tid !!! testResponse 409 (Just "legalhold-no-consent")
+ requestLegalHoldDevice owner member tid !!! testResponse 403 (Just "legalhold-not-enabled")
UserLegalHoldStatusResponse userStatus _ _ <- getUserStatusTyped member tid
liftIO $
assertEqual
@@ -227,18 +216,9 @@ testRequestLegalHoldDevice = do
UserLegalHoldNoConsent
userStatus
- do
- -- test granting consent
- lhs <- view legalHoldStatus <$> getTeamMember member tid member
- liftIO $ assertEqual "" lhs UserLegalHoldNoConsent
-
- grantConsent tid member
- lhs' <- view legalHoldStatus <$> getTeamMember member tid member
- liftIO $ assertEqual "" lhs' UserLegalHoldDisabled
-
- grantConsent tid member
- lhs'' <- view legalHoldStatus <$> getTeamMember member tid member
- liftIO $ assertEqual "" lhs'' UserLegalHoldDisabled
+ putLHWhitelistTeam tid !!! const 200 === statusCode
+ newService <- newLegalHoldService
+ postSettings owner tid newService !!! testResponse 201 Nothing
do
requestLegalHoldDevice member member tid !!! testResponse 403 (Just "operation-denied")
@@ -279,10 +259,12 @@ testRequestLegalHoldDevice = do
assertNotification ws pluck
-- all devices get notified.
assertNotification ws' pluck
+ ensureQueueEmpty
testApproveLegalHoldDevice :: TestM ()
testApproveLegalHoldDevice = do
(owner, tid) <- createBindingTeam
+ ensureQueueEmpty
member <- do
usr <- randomUser
addTeamMemberInternal tid usr (rolePermissions RoleMember) Nothing
@@ -296,13 +278,10 @@ testApproveLegalHoldDevice = do
connectUsers member (List1.singleton usr)
pure usr
stranger <- randomUser
- grantConsent tid owner
- grantConsent tid member
- grantConsent tid member2
+ putLHWhitelistTeam tid !!! const 200 === statusCode
ensureQueueEmpty
- -- not allowed to approve if team setting is disabled
approveLegalHoldDevice (Just defPassword) owner member tid
- !!! testResponse 403 (Just "legalhold-not-enabled")
+ !!! testResponse 403 (Just "access-denied")
cannon <- view tsCannon
WS.bracketRN cannon [owner, member, member, member2, outsideContact, stranger] $
\[ows, mws, mws', member2Ws, outsideContactWs, strangerWs] -> withDummyTestServiceForTeam owner tid $ \chan -> do
@@ -362,8 +341,9 @@ testGetLegalHoldDeviceStatus = do
"unexpected status"
(UserLegalHoldStatusResponse UserLegalHoldNoConsent Nothing Nothing)
status
- withDummyTestServiceForTeam owner tid $ \_chan -> do
- grantConsent tid member
+
+ putLHWhitelistTeam tid !!! const 200 === statusCode
+ withDummyTestServiceForTeamNoService $ \_chan -> do
do
UserLegalHoldStatusResponse userStatus lastPrekey' clientId' <- getUserStatusTyped member tid
liftIO $
@@ -371,7 +351,10 @@ testGetLegalHoldDeviceStatus = do
assertEqual "User legal hold status should start as disabled" UserLegalHoldDisabled userStatus
assertEqual "last_prekey should be Nothing when LH is disabled" Nothing lastPrekey'
assertEqual "client.id should be Nothing when LH is disabled" Nothing clientId'
+
do
+ newService <- newLegalHoldService
+ postSettings owner tid newService !!! testResponse 201 Nothing
requestLegalHoldDevice owner member tid !!! testResponse 201 Nothing
assertZeroLegalHoldDevices member
UserLegalHoldStatusResponse userStatus lastPrekey' clientId' <- getUserStatusTyped member tid
@@ -400,14 +383,13 @@ testGetLegalHoldDeviceStatus = do
requestLegalHoldDevice owner member tid !!! testResponse 409 (Just "legalhold-already-enabled")
testDisableLegalHoldForUser :: TestM ()
-testDisableLegalHoldForUser = do
- (owner, tid) <- createBindingTeam
+testDisableLegalHoldForUser = withTeam $ \owner tid -> do
member <- randomUser
addTeamMemberInternal tid member (rolePermissions RoleMember) Nothing
ensureQueueEmpty
cannon <- view tsCannon
+ putLHWhitelistTeam tid !!! const 200 === statusCode
WS.bracketR2 cannon owner member $ \(ows, mws) -> withDummyTestServiceForTeam owner tid $ \chan -> do
- grantConsent tid member
requestLegalHoldDevice owner member tid !!! testResponse 201 Nothing
approveLegalHoldDevice (Just defPassword) member member tid !!! testResponse 200 Nothing
assertNotification mws $ \case
@@ -442,15 +424,16 @@ data IsWorking = Working | NotWorking
deriving (Eq, Show)
testCreateLegalHoldTeamSettings :: TestM ()
-testCreateLegalHoldTeamSettings = do
- (owner, tid) <- createBindingTeam
+testCreateLegalHoldTeamSettings = withTeam $ \owner tid -> do
+ putLHWhitelistTeam tid !!! const 200 === statusCode
member <- randomUser
addTeamMemberInternal tid member (rolePermissions RoleMember) Nothing
ensureQueueEmpty
newService <- newLegalHoldService
- -- not allowed to create if team setting is disabled
- postSettings owner tid newService !!! testResponse 403 (Just "legalhold-not-enabled")
- putEnabled tid Public.TeamFeatureEnabled -- enable it for this team
+ -- not allowed to create if team is not whitelisted
+ postSettings owner tid newService !!! testResponse 412 (Just "legalhold-unavailable")
+
+ putLHWhitelistTeam tid !!! const 200 === statusCode
-- not allowed for users with corresp. permission bit missing
postSettings member tid newService !!! testResponse 403 (Just "operation-denied")
@@ -519,7 +502,8 @@ testGetLegalHoldTeamSettings = do
assertEqual "bad body" ViewLegalHoldServiceDisabled (responseJsonUnsafe resp)
getSettings owner tid >>= respOk
getSettings member tid >>= respOk
- putEnabled tid Public.TeamFeatureEnabled -- enable it for this team
+
+ putLHWhitelistTeam tid !!! const 200 === statusCode
-- returns 200 with corresp. status if legalhold for team is enabled, but not configured
do
@@ -545,58 +529,14 @@ testGetLegalHoldTeamSettings = do
testRemoveLegalHoldFromTeam :: TestM ()
testRemoveLegalHoldFromTeam = do
(owner, tid) <- createBindingTeam
- stranger <- randomUser
member <- randomUser
addTeamMemberInternal tid member noPermissions Nothing
ensureQueueEmpty
-- fails if LH for team is disabled
- deleteSettings (Just defPassword) owner tid !!! testResponse 403 (Just "legalhold-not-enabled")
- withDummyTestServiceForTeam owner tid $ \chan -> do
- newService <- newLegalHoldService
- postSettings owner tid newService !!! testResponse 201 Nothing
- -- enable legalhold for member
- do
- grantConsent tid member
- requestLegalHoldDevice owner member tid !!! testResponse 201 Nothing
- approveLegalHoldDevice (Just defPassword) member member tid !!! testResponse 200 Nothing
- UserLegalHoldStatusResponse userStatus _ _ <- getUserStatusTyped member tid
- liftIO $
- assertEqual
- "After approval user legalhold status should be Enabled"
- UserLegalHoldEnabled
- userStatus
- -- returns 403 if user is not in team or has unsufficient permissions.
- deleteSettings (Just defPassword) stranger tid !!! testResponse 403 (Just "no-team-member")
- deleteSettings (Just defPassword) member tid !!! testResponse 403 (Just "operation-denied")
- -- Fails without password
- deleteSettings Nothing owner tid !!! testResponse 403 (Just "access-denied")
- let delete'' expectRemoteLHCall = do
- deleteSettings (Just defPassword) owner tid !!! testResponse 204 Nothing
- when expectRemoteLHCall . liftIO . assertMatchChan chan $ \(req, _) -> do
- putStrLn (show (pathInfo req, pathInfo req == ["legalhold", "remove"]))
- putStrLn (show (requestMethod req, requestMethod req == "POST"))
- assertEqual "path" ["legalhold", "remove"] (pathInfo req)
- assertEqual "method" "POST" (requestMethod req)
- resp <- getSettings owner tid
- liftIO $ assertEqual "bad body" ViewLegalHoldServiceNotConfigured (responseJsonUnsafe resp)
- -- returns 204 if legal hold is successfully removed from team
- -- is idempotent (deleting twice in a row works) from BE's PoV
- -- NOTE: Only if LH is active will there be a remote call to the LH service
- delete'' True
- delete'' False
- -- deletion of settings should disable for all team members and remove their clients
- do
- UserLegalHoldStatusResponse userStatus _ _ <- getUserStatusTyped member tid
- liftIO $
- assertEqual
- "After approval user legalhold status should be Disabled"
- UserLegalHoldDisabled
- userStatus
- assertZeroLegalHoldDevices member
+ deleteSettings (Just defPassword) owner tid !!! testResponse 403 (Just "legalhold-disable-unimplemented")
testEnablePerTeam :: TestM ()
-testEnablePerTeam = do
- (owner, tid) <- createBindingTeam
+testEnablePerTeam = withTeam $ \owner tid -> do
member <- randomUser
addTeamMemberInternal tid member (rolePermissions RoleMember) Nothing
ensureQueueEmpty
@@ -604,65 +544,39 @@ testEnablePerTeam = do
status :: Public.TeamFeatureStatus 'Public.TeamFeatureLegalHold <- responseJsonUnsafe <$> (getEnabled tid (getEnabled tid do
- grantConsent tid member
+ putLHWhitelistTeam tid !!! const 200 === statusCode
requestLegalHoldDevice owner member tid !!! const 201 === statusCode
approveLegalHoldDevice (Just defPassword) member member tid !!! testResponse 200 Nothing
do
UserLegalHoldStatusResponse status _ _ <- getUserStatusTyped member tid
liftIO $ assertEqual "User legal hold status should be enabled" UserLegalHoldEnabled status
do
- putEnabled tid Public.TeamFeatureDisabled -- disable again
+ putEnabled' id tid Public.TeamFeatureDisabled !!! testResponse 403 (Just "legalhold-whitelisted-only")
status :: Public.TeamFeatureStatus 'Public.TeamFeatureLegalHold <- responseJsonUnsafe <$> (getEnabled tid TestM ()
+testAddTeamUserTooLargeWithLegalholdWhitelisted = withTeam $ \owner tid -> do
o <- view tsGConf
- let fanoutLimit = fromIntegral . fromRange $ Galley.currentFanoutLimit o
- (tid, _owner, _others) <- createBindingTeamWithMembers (fanoutLimit + 1)
-
- status :: Public.TeamFeatureStatus 'Public.TeamFeatureLegalHold <- responseJsonUnsafe <$> (getEnabled tid (getEnabled tid do
+ addUserToTeam' owner tid !!! do
+ const 201 === statusCode
+ ensureQueueEmpty
testCannotCreateLegalHoldDeviceOldAPI :: TestM ()
testCannotCreateLegalHoldDeviceOldAPI = do
member <- randomUser
+ ensureQueueEmpty
(owner, tid) <- createBindingTeam
ensureQueueEmpty
-- user without team can't add LH device
@@ -711,62 +625,19 @@ testGetTeamMembersIncludesLHStatus = do
(findMemberStatus members')
check UserLegalHoldNoConsent "disabled when it is disabled for the team"
- withDummyTestServiceForTeam owner tid $ \_chan -> do
+ withDummyTestServiceForTeamNoService $ \_chan -> do
check UserLegalHoldNoConsent "no_consent on new team members"
- grantConsent tid member
+
+ putLHWhitelistTeam tid !!! const 200 === statusCode
+ newService <- newLegalHoldService
+ postSettings owner tid newService !!! testResponse 201 Nothing
+
check UserLegalHoldDisabled "disabled on team members that have granted consent"
requestLegalHoldDevice owner member tid !!! testResponse 201 Nothing
check UserLegalHoldPending "pending after requesting device"
approveLegalHoldDevice (Just defPassword) member member tid !!! testResponse 200 Nothing
check UserLegalHoldEnabled "enabled after confirming device"
-testNotInWhitelist :: TestM ()
-testNotInWhitelist = do
- g <- view tsGalley
- (owner, tid) <- createBindingTeam
- member <- randomUser
- addTeamMemberInternal tid member (rolePermissions RoleMember) Nothing
- ensureQueueEmpty
- (_, tid') <- createBindingTeam -- we enable for this team, and then see how the above team behaves.
- cannon <- view tsCannon
- WS.bracketR2 cannon member member $ \(_ws, _ws') -> withDummyTestServiceForTeam owner tid $ \_chan -> do
- do
- -- enable NOT-whitelisted team should fail
- withLHWhitelist tid' (putEnabledM' g id tid Public.TeamFeatureEnabled)
- !!! testResponse 403 (Just "legalhold-whitelisted-only")
-
- -- disable whitelisted team should fail
- withLHWhitelist tid (putEnabledM' g id tid Public.TeamFeatureDisabled)
- !!! testResponse 403 (Just "legalhold-whitelisted-only")
-
- -- enable whitelisted team should fail (no need for this to work)
- withLHWhitelist tid (putEnabledM' g id tid Public.TeamFeatureEnabled)
- !!! testResponse 403 (Just "legalhold-whitelisted-only")
-
- -- disable NOT-whitelisted team should fail (no need for this to work)
- withLHWhitelist tid' (putEnabledM' g id tid Public.TeamFeatureDisabled)
- !!! testResponse 403 (Just "legalhold-whitelisted-only")
-
- do
- -- members have not granted consent implicitly...
- lhs <- view legalHoldStatus <$> withLHWhitelist tid' (getTeamMember' g member tid member)
- liftIO $ assertEqual "" lhs UserLegalHoldNoConsent
-
- -- ... but can do so explicitly.
- withLHWhitelist tid' (grantConsent' g tid member)
- lhs' <- withLHWhitelist tid' $ view legalHoldStatus <$> getTeamMember' g member tid member
- liftIO $ assertEqual "" lhs' UserLegalHoldDisabled
-
- do
- -- nobody can request LH device.
- withLHWhitelist tid' (requestLegalHoldDevice' g owner member tid) !!! testResponse 403 (Just "legalhold-not-enabled")
- UserLegalHoldStatusResponse userStatus _ _ <- withLHWhitelist tid' (getUserStatusTyped' g member tid)
- liftIO $
- assertEqual
- "requestLegalHoldDevice should leave user status in Disabled"
- UserLegalHoldDisabled
- userStatus
-
testInWhitelist :: TestM ()
testInWhitelist = do
g <- view tsGalley
@@ -775,6 +646,9 @@ testInWhitelist = do
addTeamMemberInternal tid member (rolePermissions RoleMember) Nothing
ensureQueueEmpty
cannon <- view tsCannon
+
+ putLHWhitelistTeam tid !!! const 200 === statusCode
+
WS.bracketR2 cannon member member $ \(_ws, _ws') -> withDummyTestServiceForTeam owner tid $ \_chan -> do
do
-- members have granted consent (implicitly)...
@@ -782,7 +656,7 @@ testInWhitelist = do
liftIO $ assertEqual "" lhs UserLegalHoldDisabled
-- ... and can do so again (idempotency).
- _ <- withLHWhitelist tid (grantConsent' g tid member)
+ _ <- withLHWhitelist tid (void $ putLHWhitelistTeam' g tid)
lhs' <- withLHWhitelist tid $ view legalHoldStatus <$> getTeamMember' g member tid member
liftIO $ assertEqual "" lhs' UserLegalHoldDisabled
@@ -823,11 +697,104 @@ testInWhitelist = do
assertEqual "last_prekey should be set when LH is pending" (Just (head someLastPrekeys)) lastPrekey'
assertEqual "client.id should be set when LH is pending" (Just someClientId) clientId'
-testNoConsentBlockDeviceHandshake :: TestM ()
-testNoConsentBlockDeviceHandshake = do
- -- "handshake between LH device and user without consent is blocked"
+testOldClientsBlockDeviceHandshake :: TestM ()
+testOldClientsBlockDeviceHandshake = do
+ -- "handshake between LH device and user with old devices is blocked"
+ --
+ -- this specifically checks the place that handles otr messages and responds with status
+ -- 412 and a list of missing clients.
+ --
+ -- if any of those clients are LH, this test provodes a "missing-legalhold-consent" error
+ -- instead, without any information about the LH clients. the condition is actually "has
+ -- old device or has not granted consent", but the latter part is blocked earlier in 1:1 and
+ -- group conversations, and hard to test at the device level.)
+ --
-- tracked here: https://wearezeta.atlassian.net/browse/SQSERVICES-454
- pure ()
+
+ (legalholder, tid) <- createBindingTeam
+ legalholder2 <- view userId <$> addUserToTeam legalholder tid
+ ensureQueueEmpty
+ (peer, tid2) <-
+ -- has to be a team member, granting LH consent for personal users is not supported.
+ createBindingTeam
+ ensureQueueEmpty
+
+ let doEnableLH :: HasCallStack => UserId -> UserId -> TestM ClientId
+ doEnableLH owner uid = do
+ requestLegalHoldDevice owner uid tid !!! testResponse 201 Nothing
+ approveLegalHoldDevice (Just defPassword) uid uid tid !!! testResponse 200 Nothing
+ UserLegalHoldStatusResponse userStatus _ _ <- getUserStatusTyped uid tid
+ liftIO $ assertEqual "approving should change status" UserLegalHoldEnabled userStatus
+ getInternalClientsFull (UserSet $ Set.fromList [uid])
+ <&> userClientsFull
+ <&> Map.elems
+ <&> Set.unions
+ <&> Set.toList
+ <&> (\[x] -> x)
+ <&> clientId
+
+ putLHWhitelistTeam tid !!! const 200 === statusCode
+
+ withDummyTestServiceForTeam legalholder tid $ \_chan -> do
+ putLHWhitelistTeam tid !!! const 200 === statusCode
+
+ legalholderLHDevice <- doEnableLH legalholder legalholder
+ _legalholder2LHDevice <- doEnableLH legalholder legalholder2
+
+ let caps = Set.singleton Client.ClientSupportsLegalholdImplicitConsent
+ legalholderClient <- do
+ clnt <- randomClientWithCaps legalholder (someLastPrekeys !! 1) (Just caps)
+ ensureClientCaps legalholder clnt (Client.ClientCapabilityList caps)
+ pure clnt
+ legalholder2Client <- do
+ clnt <- randomClient legalholder2 (someLastPrekeys !! 3)
+ -- this another way to do it (instead of providing caps during client creation).
+ ensureClientCaps legalholder2 clnt (Client.ClientCapabilityList mempty)
+ upgradeClientToLH legalholder2 clnt
+ ensureClientCaps legalholder2 clnt (Client.ClientCapabilityList caps)
+ pure clnt
+ putLHWhitelistTeam tid2 !!! const 200 === statusCode
+ connectUsers peer (List1.list1 legalholder [legalholder2])
+
+ convId <-
+ decodeConvId
+ <$> ( postConv peer [legalholder, legalholder2] (Just "gossip") [] Nothing Nothing
+ UserId -> ClientId -> TestM ResponseLBS
+ runit sender senderClient = do
+ postOtrMessage id sender senderClient convId rcps
+ where
+ rcps =
+ [ (legalholder, legalholderClient, "ciphered"),
+ (legalholder, legalholderLHDevice, "ciphered"),
+ (legalholder2, legalholder2Client, "ciphered")
+ -- legalholder2 LH device missing
+ ]
+
+ errWith :: (HasCallStack, Typeable a, FromJSON a) => Int -> (a -> Bool) -> ResponseLBS -> TestM ()
+ errWith wantStatus wantBody rsp = liftIO $ do
+ assertEqual "" wantStatus (statusCode rsp)
+ assertBool
+ (show $ responseBody rsp)
+ ( case responseJsonMaybe rsp of
+ Nothing -> False
+ Just bdy -> wantBody bdy
+ )
+
+ -- LH devices are treated as clients that have the ClientSupportsLegalholdImplicitConsent
+ -- capability (so LH doesn't break for users who have LH devices; it sounds silly, but
+ -- it's good to test this, since it did require adding a few lines of production code in
+ -- 'addClient' about client capabilities).
+ runit legalholder legalholderClient >>= errWith 412 (\(_ :: Msg.ClientMismatch) -> True)
+
+ -- If user has a client without the ClientSupportsLegalholdImplicitConsent
+ -- capability then message sending is prevented to legalhold devices.
+ peerClient <- randomClient peer (someLastPrekeys !! 2)
+ runit peer peerClient >>= errWith 412 (\err -> Error.label err == "missing-legalhold-consent")
+ upgradeClientToLH peer peerClient
+ runit peer peerClient >>= errWith 412 (\(_ :: Msg.ClientMismatch) -> True)
-- If LH is activated for other user in 1:1 conv, 1:1 conv is blocked
testNoConsentBlockOne2OneConv :: HasCallStack => Bool -> Bool -> Bool -> Bool -> TestM ()
@@ -837,18 +804,27 @@ testNoConsentBlockOne2OneConv connectFirst teamPeer approveLH testPendingConnect
peer :: UserId <- if teamPeer then fst <$> createBindingTeam else randomUser
galley <- view tsGalley
- let doEnableLH :: HasCallStack => TestM ()
+ putLHWhitelistTeam tid !!! const 200 === statusCode
+
+ let doEnableLH :: HasCallStack => TestM (Maybe ClientId)
doEnableLH = do
-- register & (possibly) approve LH device for legalholder
withLHWhitelist tid (requestLegalHoldDevice' galley legalholder legalholder tid) !!! testResponse 201 Nothing
when approveLH $
withLHWhitelist tid (approveLegalHoldDevice' galley (Just defPassword) legalholder legalholder tid) !!! testResponse 200 Nothing
UserLegalHoldStatusResponse userStatus _ _ <- withLHWhitelist tid (getUserStatusTyped' galley legalholder tid)
- liftIO $
- assertEqual
- "approving should change status"
- (if approveLH then UserLegalHoldEnabled else UserLegalHoldPending)
- userStatus
+ liftIO $ assertEqual "approving should change status" (if approveLH then UserLegalHoldEnabled else UserLegalHoldPending) userStatus
+ if approveLH
+ then
+ getInternalClientsFull (UserSet $ Set.fromList [legalholder])
+ <&> userClientsFull
+ <&> Map.elems
+ <&> Set.unions
+ <&> Set.toList
+ <&> (\[x] -> x)
+ <&> clientId
+ <&> Just
+ else pure Nothing
doDisableLH :: HasCallStack => TestM ()
doDisableLH = do
@@ -863,7 +839,7 @@ testNoConsentBlockOne2OneConv connectFirst teamPeer approveLH testPendingConnect
then do
postConnection legalholder peer !!! const 201 === statusCode
- _mbConn :: Maybe UserConnection <-
+ mbConn :: Maybe UserConnection <-
if testPendingConnection
then pure Nothing
else do
@@ -872,14 +848,12 @@ testNoConsentBlockOne2OneConv connectFirst teamPeer approveLH testPendingConnect
ensureQueueEmpty
- doEnableLH
+ mbLegalholderLHDevice <- doEnableLH
assertConnections legalholder [ConnectionStatus legalholder peer Conn.MissingLegalholdConsent]
assertConnections peer [ConnectionStatus peer legalholder Conn.MissingLegalholdConsent]
forM_ [legalholderWs, peerWs] $ \ws -> do
- -- (if this fails, it may be because there are other messages in the queue, but i
- -- think we implemented this in a way that doens't trip over wrong orderings.)
assertNotification ws $
\case
(Ev.ConnectionEvent (Ev.ConnectionUpdated (Conn.ucStatus -> rel) _prev _name)) -> do
@@ -893,11 +867,19 @@ testNoConsentBlockOne2OneConv connectFirst teamPeer approveLH testPendingConnect
assertConnections legalholder [ConnectionStatus legalholder peer Conn.MissingLegalholdConsent]
assertConnections peer [ConnectionStatus peer legalholder Conn.MissingLegalholdConsent]
- -- FUTUREWORK: test if message sending is blocked
- -- for_ (mbConn >>= Conn.ucConvId) $ \convId -> do
- -- (again, other label / 4xx status code would also be fine.)
- -- postOtrMessageJson peer convId !!! testResponse 412 (Just "missing-legalhold-consent")
- -- postOtrMessageProto peer convId !!! testResponse 412 (Just "missing-legalhold-consent")
+ -- peer can't send message to legalhodler. the conversation appears gone.
+ peerClient <- randomClient peer (someLastPrekeys !! 2)
+ for_ ((,) <$> (mbConn >>= Conn.ucConvId) <*> mbLegalholderLHDevice) $ \(convId, legalholderLHDevice) -> do
+ postOtrMessage
+ id
+ peer
+ peerClient
+ convId
+ [ (legalholder, legalholderLHDevice, "cipher")
+ ]
+ !!! do
+ const 404 === statusCode
+ const (Right "no-conversation") === fmap Error.label . responseJsonEither
do
doDisableLH
@@ -912,11 +894,26 @@ testNoConsentBlockOne2OneConv connectFirst teamPeer approveLH testPendingConnect
if testPendingConnection then Conn.Pending else Conn.Accepted
]
- -- FUTUREWORK: test if message sending works again
- -- postOtrMessageJson undefined undefined !!! const 201 === statusCode
- pure ()
+ forM_ [legalholderWs, peerWs] $ \ws -> do
+ assertNotification ws $
+ \case
+ (Ev.ConnectionEvent (Ev.ConnectionUpdated (Conn.ucStatus -> rel) _prev _name)) -> do
+ assertBool "" (rel `elem` [Conn.Sent, Conn.Pending, Conn.Accepted])
+ _ -> assertBool "wrong event type" False
+
+ -- conversation reappears. peer can send message to legalholder again
+ for_ ((,) <$> (mbConn >>= Conn.ucConvId) <*> mbLegalholderLHDevice) $ \(convId, legalholderLHDevice) -> do
+ postOtrMessage
+ id
+ peer
+ peerClient
+ convId
+ [ (legalholder, legalholderLHDevice, "cipher")
+ ]
+ !!! do
+ const 201 === statusCode
else do
- doEnableLH
+ void doEnableLH
postConnection legalholder peer !!! do testResponse 412 (Just "missing-legalhold-consent")
postConnection peer legalholder !!! do testResponse 412 (Just "missing-legalhold-consent")
@@ -926,6 +923,70 @@ testNoConsentBlockGroupConv = do
-- tracked here: https://wearezeta.atlassian.net/browse/SQSERVICES-428
pure ()
+data TestClaimKeys
+ = TCKConsentMissing
+ | TCKOldClient
+ | TCKConsentAndNewClients
+
+testClaimKeys :: TestClaimKeys -> TestM ()
+testClaimKeys testcase = do
+ -- "cannot fetch prekeys of LH users if requester did not give consent or has old clients"
+ (legalholder, tid) <- createBindingTeam
+ ensureQueueEmpty
+ (peer, teamPeer) <- createBindingTeam
+ ensureQueueEmpty
+
+ let doEnableLH :: HasCallStack => TeamId -> UserId -> UserId -> TestM ClientId
+ doEnableLH team owner uid = do
+ requestLegalHoldDevice owner uid team !!! testResponse 201 Nothing
+ approveLegalHoldDevice (Just defPassword) uid uid team !!! testResponse 200 Nothing
+ UserLegalHoldStatusResponse userStatus _ _ <- getUserStatusTyped uid team
+ liftIO $ assertEqual "approving should change status" UserLegalHoldEnabled userStatus
+ getInternalClientsFull (UserSet $ Set.fromList [uid])
+ <&> userClientsFull
+ <&> Map.elems
+ <&> Set.unions
+ <&> Set.toList
+ <&> (\[x] -> x)
+ <&> clientId
+
+ let makePeerClient :: TestM ()
+ makePeerClient = case testcase of
+ TCKConsentMissing -> do
+ peerClient <- randomClient peer (someLastPrekeys !! 2)
+ upgradeClientToLH peer peerClient
+ TCKOldClient -> do
+ void $ randomClient peer (someLastPrekeys !! 2)
+ putLHWhitelistTeam teamPeer !!! const 200 === statusCode
+ TCKConsentAndNewClients -> do
+ peerClient <- randomClient peer (someLastPrekeys !! 2)
+ upgradeClientToLH peer peerClient
+ putLHWhitelistTeam teamPeer !!! const 200 === statusCode
+
+ let assertResponse :: Assertions ()
+ assertResponse = case testcase of
+ TCKConsentMissing -> bad
+ TCKOldClient -> bad
+ TCKConsentAndNewClients -> good
+ where
+ good = testResponse 200 Nothing
+ bad = testResponse 412 (Just "missing-legalhold-consent")
+
+ let fetchKeys :: ClientId -> TestM ()
+ fetchKeys legalholderLHDevice = do
+ getUsersPrekeysClientUnqualified peer legalholder legalholderLHDevice !!! assertResponse
+ getUsersPrekeyBundleUnqualified peer legalholder !!! assertResponse
+ let userClients = UserClients (Map.fromList [(legalholder, Set.fromList [legalholderLHDevice])])
+ getMultiUserPrekeyBundleUnqualified peer userClients !!! assertResponse
+
+ putLHWhitelistTeam tid !!! const 200 === statusCode
+
+ withDummyTestServiceForTeam legalholder tid $ \_chan -> do
+ legalholderLHDevice <- doEnableLH tid legalholder legalholder
+
+ makePeerClient
+ fetchKeys legalholderLHDevice
+
testBenchHack :: HasCallStack => TestM ()
testBenchHack = do
{- representative sample run on an old laptop:
@@ -1025,8 +1086,8 @@ renewToken tok = do
. cookieRaw "zuid" (toByteString' tok)
. expect2xx
-putEnabled :: HasCallStack => TeamId -> Public.TeamFeatureStatusValue -> TestM ()
-putEnabled tid enabled = do
+_putEnabled :: HasCallStack => TeamId -> Public.TeamFeatureStatusValue -> TestM ()
+_putEnabled tid enabled = do
g <- view tsGalley
putEnabledM g tid enabled
@@ -1157,22 +1218,6 @@ disableLegalHoldForUser' g mPassword tid zusr uid = do
. zType "access"
. json (DisableLegalHoldForUserRequest mPassword)
-_postOtrMessageJson :: UserId -> ConvId -> TestM ResponseLBS
-_postOtrMessageJson zusr cnvid = do
- g <- view tsGalley
- otrmsg :: Msg.NewOtrMessage <- pure (error "TODO")
- post $
- g
- . paths ["conversations", toByteString' cnvid, "otr", "messages"]
- . zUser zusr
- . zConn "conn"
- . zType "access"
- -- TODO: @.&. def Public.OtrReportAllMissing filterMissing@
- . json otrmsg
-
-_postOtrMessageProto :: UserId -> ConvId -> TestM ResponseLBS
-_postOtrMessageProto = (error "TODO")
-
assertExactlyOneLegalHoldDevice :: HasCallStack => UserId -> TestM ()
assertExactlyOneLegalHoldDevice uid = do
clients :: [Client] <-
@@ -1196,24 +1241,6 @@ assertZeroLegalHoldDevices uid = do
---------------------------------------------------------------------
--- Device helpers
-grantConsent :: HasCallStack => TeamId -> UserId -> TestM ()
-grantConsent tid zusr = do
- g <- view tsGalley
- grantConsent' g tid zusr
-
-grantConsent' :: (HasCallStack, MonadHttp m, MonadIO m) => GalleyR -> TeamId -> UserId -> m ()
-grantConsent' = grantConsent'' expect2xx
-
-grantConsent'' :: (HasCallStack, MonadHttp m, MonadIO m) => (Bilge.Request -> Bilge.Request) -> GalleyR -> TeamId -> UserId -> m ()
-grantConsent'' expectation g tid zusr = do
- void . post $
- g
- . paths ["teams", toByteString' tid, "legalhold", "consent"]
- . zUser zusr
- . zConn "conn"
- . zType "access"
- . expectation
-
requestLegalHoldDevice :: HasCallStack => UserId -> UserId -> TeamId -> TestM ResponseLBS
requestLegalHoldDevice zusr uid tid = do
g <- view tsGalley
@@ -1254,8 +1281,6 @@ readServiceKey fp = liftIO $ do
let Right [k] = pemParseBS bs
return (ServiceKeyPEM k)
--- FUTUREWORK: run this test suite against an actual LH service (by changing URL and key in
--- the config file), and see if it works as well as with our mock service.
withDummyTestServiceForTeam ::
forall a.
HasCallStack =>
@@ -1264,14 +1289,25 @@ withDummyTestServiceForTeam ::
-- | the test
(Chan (Wai.Request, LBS) -> TestM a) ->
TestM a
-withDummyTestServiceForTeam owner tid go = do
+withDummyTestServiceForTeam owner tid go =
+ withDummyTestServiceForTeamNoService $ \chan -> do
+ newService <- newLegalHoldService
+ postSettings owner tid newService !!! testResponse 201 Nothing
+ go chan
+
+-- FUTUREWORK: run this test suite against an actual LH service (by changing URL and key in
+-- the config file), and see if it works as well as with our mock service.
+withDummyTestServiceForTeamNoService ::
+ forall a.
+ HasCallStack =>
+ -- | the test
+ (Chan (Wai.Request, LBS) -> TestM a) ->
+ TestM a
+withDummyTestServiceForTeamNoService go = do
withTestService dummyService runTest
where
runTest :: Chan (Wai.Request, LBS) -> TestM a
runTest chan = do
- newService <- newLegalHoldService
- putEnabled tid Public.TeamFeatureEnabled -- enable it for this team
- postSettings owner tid newService !!! testResponse 201 Nothing
go chan
dummyService :: Chan (Wai.Request, LBS) -> Wai.Application
@@ -1290,6 +1326,7 @@ withDummyTestServiceForTeam owner tid go = do
initiateResp :: Wai.Response
initiateResp =
Wai.json $
+ -- FUTUREWORK: use another key to prevent collisions with keys used by tests
NewLegalHoldClient somePrekeys (head $ someLastPrekeys)
respondOk :: Wai.Response
@@ -1304,13 +1341,33 @@ withDummyTestServiceForTeam owner tid go = do
getRequestHeader :: String -> Wai.Request -> Maybe ByteString
getRequestHeader name req = lookup (fromString name) $ requestHeaders req
-withLHWhitelist :: forall a. HasCallStack => TeamId -> WaiTest.Session a -> TestM a
+-- | FUTUREWORK: this function calls an internal end-point to whitelist a team. It only
+-- appears to bracket this state change and undo it in a finalizer.
+--
+-- We should probably not have this function, just do the call inline, and use the 'TestM'
+-- actions again rather than the polymorphic ones that we have here.
+--
+-- it's here for historical reason because we did this in galley.yaml
+-- at some point in the past rather than in an internal end-point, and that required spawning
+-- another galley 'Application' with 'withSettingsOverrides'.
+withLHWhitelist :: forall a. HasCallStack => TeamId -> BilgeTest.SessionT TestM a -> TestM a
withLHWhitelist tid action = do
+ void $ putLHWhitelistTeam tid
opts <- view tsGConf
- let opts' =
- opts & optSettings . setLegalHoldTeamsWhitelist .~ Just [tid]
- & optSettings . setFeatureFlags . flagLegalHold .~ FeatureLegalHoldWhitelistTeamsAndImplicitConsent
- withSettingsOverrides opts' action
+ withSettingsOverrides opts action
+
+-- | If you play with whitelists, you should use this one. Every whitelisted team that does
+-- not get fully deleted will blow up the whitelist that is cached in every warp handler.
+withTeam :: forall a. HasCallStack => (HasCallStack => UserId -> TeamId -> TestM a) -> TestM a
+withTeam action =
+ bracket
+ createBindingTeam
+ (uncurry deleteTeam >=> const waitForDeleteEvent)
+ (uncurry action)
+ where
+ waitForDeleteEvent :: TestM ()
+ waitForDeleteEvent =
+ tryAssertQueue 10 "waitForDeleteEvent" SQS.tDelete
-- | Run a test with an mock legal hold service application. The mock service is also binding
-- to a TCP socket for the backend to connect to. The mock service can expose internal
@@ -1396,18 +1453,20 @@ testGetLegalholdStatus = do
check member2 personal Nothing UserLegalHoldNoConsent
check personal personal Nothing UserLegalHoldNoConsent
- onlyIfLhEnabled $
- withDummyTestServiceForTeam owner1 tid1 $ \_chan -> do
- grantConsent tid1 member1
- check owner1 member1 (Just tid1) UserLegalHoldDisabled
- check member2 member1 (Just tid1) UserLegalHoldDisabled
- check personal member1 (Just tid1) UserLegalHoldDisabled
+ putLHWhitelistTeam tid1 !!! const 200 === statusCode
+
+ withDummyTestServiceForTeam owner1 tid1 $ \_chan -> do
+ check owner1 member1 (Just tid1) UserLegalHoldDisabled
+ check member2 member1 (Just tid1) UserLegalHoldDisabled
+ check personal member1 (Just tid1) UserLegalHoldDisabled
- requestDev owner1 member1 tid1
- check personal member1 (Just tid1) UserLegalHoldPending
+ requestDev owner1 member1 tid1
+ check personal member1 (Just tid1) UserLegalHoldPending
- approveDev member1 tid1
- check personal member1 (Just tid1) UserLegalHoldEnabled
+ approveDev member1 tid1
+ check personal member1 (Just tid1) UserLegalHoldEnabled
+
+ ensureQueueEmpty
----------------------------------------------------------------------
-- test helpers
@@ -1468,6 +1527,7 @@ instance FromJSON Ev.ClientEvent where
Nothing
Nothing
Nothing
+ (Client.ClientCapabilityList mempty)
instance FromJSON Ev.ConnectionEvent where
parseJSON = Aeson.withObject "ConnectionEvent" $ \o -> do
@@ -1517,3 +1577,39 @@ assertMatchChan c match = go []
Nothing -> do
refill buf
error "Timeout"
+
+getLHWhitelistedTeam :: HasCallStack => TeamId -> TestM ResponseLBS
+getLHWhitelistedTeam tid = do
+ galley <- view tsGalley
+ getLHWhitelistedTeam' galley tid
+
+getLHWhitelistedTeam' :: (HasCallStack, MonadHttp m, MonadIO m) => GalleyR -> TeamId -> m ResponseLBS
+getLHWhitelistedTeam' g tid = do
+ get
+ ( g
+ . paths ["i", "legalhold", "whitelisted-teams", toByteString' tid]
+ )
+
+putLHWhitelistTeam :: HasCallStack => TeamId -> TestM ResponseLBS
+putLHWhitelistTeam tid = do
+ galley <- view tsGalley
+ putLHWhitelistTeam' galley tid
+
+putLHWhitelistTeam' :: (HasCallStack, MonadHttp m, MonadIO m) => GalleyR -> TeamId -> m ResponseLBS
+putLHWhitelistTeam' g tid = do
+ put
+ ( g
+ . paths ["i", "legalhold", "whitelisted-teams", toByteString' tid]
+ )
+
+_deleteLHWhitelistTeam :: HasCallStack => TeamId -> TestM ResponseLBS
+_deleteLHWhitelistTeam tid = do
+ galley <- view tsGalley
+ deleteLHWhitelistTeam' galley tid
+
+deleteLHWhitelistTeam' :: (HasCallStack, MonadHttp m, MonadIO m) => GalleyR -> TeamId -> m ResponseLBS
+deleteLHWhitelistTeam' g tid = do
+ delete
+ ( g
+ . paths ["i", "legalhold", "whitelisted-teams", toByteString' tid]
+ )
diff --git a/services/galley/test/integration/API/Teams/LegalHold/DisabledByDefault.hs b/services/galley/test/integration/API/Teams/LegalHold/DisabledByDefault.hs
new file mode 100644
index 00000000000..d36c1719b13
--- /dev/null
+++ b/services/galley/test/integration/API/Teams/LegalHold/DisabledByDefault.hs
@@ -0,0 +1,1310 @@
+-- This file is part of the Wire Server implementation.
+--
+-- Copyright (C) 2020 Wire Swiss GmbH
+--
+-- This program is free software: you can redistribute it and/or modify it under
+-- the terms of the GNU Affero General Public License as published by the Free
+-- Software Foundation, either version 3 of the License, or (at your option) any
+-- later version.
+--
+-- This program is distributed in the hope that it will be useful, but WITHOUT
+-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+-- details.
+--
+-- You should have received a copy of the GNU Affero General Public License along
+-- with this program. If not, see .
+{-# LANGUAGE DeriveAnyClass #-}
+{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
+{-# OPTIONS_GHC -Wno-orphans #-}
+
+module API.Teams.LegalHold.DisabledByDefault
+ ( tests,
+ )
+where
+
+import API.SQS
+import API.Util
+import Bilge hiding (accept, head, timeout, trace)
+import Bilge.Assert
+import Brig.Types.Client
+import Brig.Types.Intra (UserSet (..))
+import Brig.Types.Provider
+import Brig.Types.Team.LegalHold hiding (userId)
+import Brig.Types.Test.Arbitrary ()
+import qualified Brig.Types.User.Event as Ev
+import qualified Cassandra.Exec as Cql
+import qualified Control.Concurrent.Async as Async
+import Control.Concurrent.Chan
+import Control.Concurrent.Timeout hiding (threadDelay)
+import Control.Exception (asyncExceptionFromException)
+import Control.Lens
+import Control.Monad.Catch
+import Control.Retry (RetryPolicy, RetryStatus, exponentialBackoff, limitRetries, retrying)
+import qualified Data.Aeson as Aeson
+import Data.Aeson.Types (FromJSON, withObject, (.:))
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Char8 as BS
+import Data.ByteString.Conversion
+import Data.Id
+import Data.Json.Util (toUTCTimeMillis)
+import Data.LegalHold
+import qualified Data.List.NonEmpty as NonEmpty
+import qualified Data.List1 as List1
+import qualified Data.Map.Strict as Map
+import Data.Misc (PlainTextPassword)
+import Data.PEM
+import Data.Range
+import qualified Data.Set as Set
+import Data.String.Conversions (LBS, cs)
+import Data.Text.Encoding (encodeUtf8)
+import qualified Galley.App as Galley
+import qualified Galley.Data as Data
+import qualified Galley.Data.LegalHold as LegalHoldData
+import Galley.External.LegalHoldService (validateServiceKey)
+import Galley.Options (optSettings, setFeatureFlags)
+import qualified Galley.Types.Clients as Clients
+import Galley.Types.Teams
+import Gundeck.Types.Notification (ntfPayload)
+import Imports
+import Network.HTTP.Types.Status (status200, status400, status404)
+import Network.Wai
+import Network.Wai as Wai
+import qualified Network.Wai.Handler.Warp as Warp
+import qualified Network.Wai.Handler.Warp.Internal as Warp
+import qualified Network.Wai.Handler.WarpTLS as Warp
+import qualified Network.Wai.Utilities.Error as Error
+import qualified Network.Wai.Utilities.Response as Wai
+import System.IO (hPutStrLn)
+import Test.QuickCheck.Instances ()
+import Test.Tasty
+import qualified Test.Tasty.Cannon as WS
+import Test.Tasty.HUnit
+import TestHelpers
+import TestSetup
+import qualified Wire.API.Message as Msg
+import qualified Wire.API.Team.Feature as Public
+import Wire.API.User (UserProfile (..))
+import Wire.API.User.Client (UserClients (..), UserClientsFull (userClientsFull))
+import qualified Wire.API.User.Client as Client
+
+onlyIfLhEnabled :: TestM () -> TestM ()
+onlyIfLhEnabled action = do
+ featureLegalHold <- view (tsGConf . optSettings . setFeatureFlags . flagLegalHold)
+ case featureLegalHold of
+ FeatureLegalHoldDisabledPermanently ->
+ liftIO $ hPutStrLn stderr "*** legalhold feature flag disabled, not running this test case"
+ FeatureLegalHoldDisabledByDefault ->
+ action
+ FeatureLegalHoldWhitelistTeamsAndImplicitConsent ->
+ liftIO $ hPutStrLn stderr "*** legalhold feature flag set to whitelisted only, not running this test case"
+
+tests :: IO TestSetup -> TestTree
+tests s =
+ -- See also Client Tests in Brig; where behaviour around deleting/adding LH clients is tested
+ testGroup
+ "Teams LegalHold API (with flag disabled-by-default)"
+ [ -- device handling (CRUD)
+ test s "POST /teams/{tid}/legalhold/{uid}" (onlyIfLhEnabled testRequestLegalHoldDevice),
+ test s "PUT /teams/{tid}/legalhold/approve" (onlyIfLhEnabled testApproveLegalHoldDevice),
+ test s "(user denies approval: nothing needs to be done in backend)" (pure ()),
+ test s "GET /teams/{tid}/legalhold/{uid}" (onlyIfLhEnabled testGetLegalHoldDeviceStatus),
+ test s "DELETE /teams/{tid}/legalhold/{uid}" (onlyIfLhEnabled testDisableLegalHoldForUser),
+ -- legal hold settings
+ test s "POST /teams/{tid}/legalhold/settings" (onlyIfLhEnabled testCreateLegalHoldTeamSettings),
+ test s "GET /teams/{tid}/legalhold/settings" (onlyIfLhEnabled testGetLegalHoldTeamSettings),
+ test s "DELETE /teams/{tid}/legalhold/settings" (onlyIfLhEnabled testRemoveLegalHoldFromTeam),
+ test s "GET, PUT [/i]?/teams/{tid}/legalhold" (onlyIfLhEnabled testEnablePerTeam),
+ test s "GET, PUT [/i]?/teams/{tid}/legalhold - too large" (onlyIfLhEnabled testEnablePerTeamTooLarge),
+ -- behavior of existing end-points
+ test s "POST /clients" (onlyIfLhEnabled testCannotCreateLegalHoldDeviceOldAPI),
+ test s "GET /teams/{tid}/members" (onlyIfLhEnabled testGetTeamMembersIncludesLHStatus),
+ test s "POST /register - cannot add team members above fanout limit" (onlyIfLhEnabled testAddTeamUserTooLargeWithLegalhold),
+ test s "GET legalhold status in user profile" (onlyIfLhEnabled testGetLegalholdStatus),
+ {- TODO:
+ conversations/{cnv}/otr/messages - possibly show the legal hold device (if missing) as a different device type (or show that on device level, depending on how client teams prefer)
+ GET /team/{tid}/members - show legal hold status of all members
+
+ -}
+ test s "handshake between LH device and user with old clients is blocked" (onlyIfLhEnabled testOldClientsBlockDeviceHandshake),
+ test s "User cannot fetch prekeys of LH users if consent is missing" (onlyIfLhEnabled (testClaimKeys TCKConsentMissing)),
+ test s "User cannot fetch prekeys of LH users: if user has old client" (onlyIfLhEnabled (testClaimKeys TCKOldClient)),
+ test s "User can fetch prekeys of LH users if consent is given and user has only new clients" (onlyIfLhEnabled (testClaimKeys TCKConsentAndNewClients))
+ ]
+
+testRequestLegalHoldDevice :: TestM ()
+testRequestLegalHoldDevice = do
+ (owner, tid) <- createBindingTeam
+ member <- randomUser
+ addTeamMemberInternal tid member (rolePermissions RoleMember) Nothing
+ ensureQueueEmpty
+ -- Can't request a device if team feature flag is disabled
+ requestLegalHoldDevice owner member tid !!! testResponse 403 (Just "legalhold-not-enabled")
+ cannon <- view tsCannon
+ -- Assert that the appropriate LegalHold Request notification is sent to the user's
+ -- clients
+ WS.bracketR2 cannon member member $ \(ws, ws') -> withDummyTestServiceForTeam owner tid $ \_chan -> do
+ do
+ -- test device creation without consent
+ requestLegalHoldDevice member member tid !!! testResponse 403 (Just "operation-denied")
+ UserLegalHoldStatusResponse userStatus _ _ <- getUserStatusTyped member tid
+ liftIO $
+ assertEqual
+ "User with insufficient permissions should be unable to start flow"
+ UserLegalHoldNoConsent
+ userStatus
+
+ do
+ requestLegalHoldDevice owner member tid !!! testResponse 409 (Just "legalhold-no-consent")
+ UserLegalHoldStatusResponse userStatus _ _ <- getUserStatusTyped member tid
+ liftIO $
+ assertEqual
+ "User with insufficient permissions should be unable to start flow"
+ UserLegalHoldNoConsent
+ userStatus
+
+ do
+ -- test granting consent
+ lhs <- view legalHoldStatus <$> getTeamMember member tid member
+ liftIO $ assertEqual "" lhs UserLegalHoldNoConsent
+
+ grantConsent tid member
+ lhs' <- view legalHoldStatus <$> getTeamMember member tid member
+ liftIO $ assertEqual "" lhs' UserLegalHoldDisabled
+
+ grantConsent tid member
+ lhs'' <- view legalHoldStatus <$> getTeamMember member tid member
+ liftIO $ assertEqual "" lhs'' UserLegalHoldDisabled
+
+ do
+ requestLegalHoldDevice member member tid !!! testResponse 403 (Just "operation-denied")
+ UserLegalHoldStatusResponse userStatus _ _ <- getUserStatusTyped member tid
+ liftIO $
+ assertEqual
+ "User with insufficient permissions should be unable to start flow"
+ UserLegalHoldDisabled
+ userStatus
+
+ do
+ requestLegalHoldDevice owner member tid !!! testResponse 201 Nothing
+ UserLegalHoldStatusResponse userStatus _ _ <- getUserStatusTyped member tid
+ liftIO $
+ assertEqual
+ "requestLegalHoldDevice should set user status to Pending"
+ UserLegalHoldPending
+ userStatus
+ do
+ requestLegalHoldDevice owner member tid !!! testResponse 204 Nothing
+ UserLegalHoldStatusResponse userStatus _ _ <- getUserStatusTyped member tid
+ liftIO $
+ assertEqual
+ "requestLegalHoldDevice when already pending should leave status as Pending"
+ UserLegalHoldPending
+ userStatus
+
+ cassState <- view tsCass
+ liftIO $ do
+ storedPrekeys <- Cql.runClient cassState (LegalHoldData.selectPendingPrekeys member)
+ assertBool "user should have pending prekeys stored" (not . null $ storedPrekeys)
+ let pluck = \case
+ (Ev.LegalHoldClientRequested rdata) -> do
+ Ev.lhcTargetUser rdata @?= member
+ Ev.lhcLastPrekey rdata @?= head someLastPrekeys
+ Ev.lhcClientId rdata @?= someClientId
+ _ -> assertBool "Unexpected event" False
+ assertNotification ws pluck
+ -- all devices get notified.
+ assertNotification ws' pluck
+
+testApproveLegalHoldDevice :: TestM ()
+testApproveLegalHoldDevice = do
+ (owner, tid) <- createBindingTeam
+ member <- do
+ usr <- randomUser
+ addTeamMemberInternal tid usr (rolePermissions RoleMember) Nothing
+ pure usr
+ member2 <- do
+ usr <- randomUser
+ addTeamMemberInternal tid usr (rolePermissions RoleMember) Nothing
+ pure usr
+ outsideContact <- do
+ usr <- randomUser
+ connectUsers member (List1.singleton usr)
+ pure usr
+ stranger <- randomUser
+ grantConsent tid owner
+ grantConsent tid member
+ grantConsent tid member2
+ ensureQueueEmpty
+ -- not allowed to approve if team setting is disabled
+ approveLegalHoldDevice (Just defPassword) owner member tid
+ !!! testResponse 403 (Just "legalhold-not-enabled")
+ cannon <- view tsCannon
+ WS.bracketRN cannon [owner, member, member, member2, outsideContact, stranger] $
+ \[ows, mws, mws', member2Ws, outsideContactWs, strangerWs] -> withDummyTestServiceForTeam owner tid $ \chan -> do
+ requestLegalHoldDevice owner member tid !!! testResponse 201 Nothing
+ liftIO . assertMatchJSON chan $ \(RequestNewLegalHoldClient userId' teamId') -> do
+ assertEqual "userId == member" userId' member
+ assertEqual "teamId == tid" teamId' tid
+ -- Only the user themself can approve adding a LH device
+ approveLegalHoldDevice (Just defPassword) owner member tid !!! testResponse 403 (Just "access-denied")
+ -- Requires password
+ approveLegalHoldDevice Nothing member member tid !!! const 403 === statusCode
+ approveLegalHoldDevice (Just defPassword) member member tid !!! testResponse 200 Nothing
+ -- checks if the cookie we give to the legalhold service is actually valid
+ assertMatchJSON chan $ \(LegalHoldServiceConfirm _clientId _uid _tid authToken) ->
+ renewToken authToken
+ cassState <- view tsCass
+ liftIO $ do
+ clients' <- Cql.runClient cassState $ Data.lookupClients [member]
+ assertBool "Expect clientId to be saved on the user" $
+ Clients.contains member someClientId clients'
+ UserLegalHoldStatusResponse userStatus _ _ <- getUserStatusTyped member tid
+ liftIO $
+ assertEqual
+ "After approval user legalhold status should be Enabled"
+ UserLegalHoldEnabled
+ userStatus
+ let pluck = \case
+ Ev.ClientAdded _ eClient -> do
+ clientId eClient @?= someClientId
+ clientType eClient @?= LegalHoldClientType
+ clientClass eClient @?= Just LegalHoldClient
+ _ -> assertBool "Unexpected event" False
+ assertNotification mws pluck
+ assertNotification mws' pluck
+ -- Other team users should get a user.legalhold-enable event
+ let pluck' = \case
+ Ev.UserLegalHoldEnabled eUser -> eUser @?= member
+ _ -> assertBool "Unexpected event" False
+ assertNotification ows pluck'
+ -- We send to all members of a team. which includes the team-settings
+ assertNotification member2Ws pluck'
+ when False $ do
+ -- this doesn't work any more since consent (personal users cannot grant consent).
+ assertNotification outsideContactWs pluck'
+ assertNoNotification strangerWs
+
+testGetLegalHoldDeviceStatus :: TestM ()
+testGetLegalHoldDeviceStatus = do
+ (owner, tid) <- createBindingTeam
+ member <- randomUser
+ addTeamMemberInternal tid member (rolePermissions RoleMember) Nothing
+ ensureQueueEmpty
+ forM_ [owner, member] $ \uid -> do
+ status <- getUserStatusTyped uid tid
+ liftIO $
+ assertEqual
+ "unexpected status"
+ (UserLegalHoldStatusResponse UserLegalHoldNoConsent Nothing Nothing)
+ status
+ withDummyTestServiceForTeam owner tid $ \_chan -> do
+ grantConsent tid member
+ do
+ UserLegalHoldStatusResponse userStatus lastPrekey' clientId' <- getUserStatusTyped member tid
+ liftIO $
+ do
+ assertEqual "User legal hold status should start as disabled" UserLegalHoldDisabled userStatus
+ assertEqual "last_prekey should be Nothing when LH is disabled" Nothing lastPrekey'
+ assertEqual "client.id should be Nothing when LH is disabled" Nothing clientId'
+ do
+ requestLegalHoldDevice owner member tid !!! testResponse 201 Nothing
+ assertZeroLegalHoldDevices member
+ UserLegalHoldStatusResponse userStatus lastPrekey' clientId' <- getUserStatusTyped member tid
+ liftIO $
+ do
+ assertEqual "requestLegalHoldDevice should set user status to Pending" UserLegalHoldPending userStatus
+ assertEqual "last_prekey should be set when LH is pending" (Just (head someLastPrekeys)) lastPrekey'
+ assertEqual "client.id should be set when LH is pending" (Just someClientId) clientId'
+ do
+ requestLegalHoldDevice owner member tid !!! testResponse 204 Nothing
+ UserLegalHoldStatusResponse userStatus _ _ <- getUserStatusTyped member tid
+ liftIO $
+ assertEqual
+ "requestLegalHoldDevice when already pending should leave status as Pending"
+ UserLegalHoldPending
+ userStatus
+ do
+ approveLegalHoldDevice (Just defPassword) member member tid !!! testResponse 200 Nothing
+ UserLegalHoldStatusResponse userStatus lastPrekey' clientId' <- getUserStatusTyped member tid
+ liftIO $
+ do
+ assertEqual "approving should change status to Enabled" UserLegalHoldEnabled userStatus
+ assertEqual "last_prekey should be set when LH is pending" (Just (head someLastPrekeys)) lastPrekey'
+ assertEqual "client.id should be set when LH is pending" (Just someClientId) clientId'
+ assertExactlyOneLegalHoldDevice member
+ requestLegalHoldDevice owner member tid !!! testResponse 409 (Just "legalhold-already-enabled")
+
+testDisableLegalHoldForUser :: TestM ()
+testDisableLegalHoldForUser = do
+ (owner, tid) <- createBindingTeam
+ member <- randomUser
+ addTeamMemberInternal tid member (rolePermissions RoleMember) Nothing
+ ensureQueueEmpty
+ cannon <- view tsCannon
+ WS.bracketR2 cannon owner member $ \(ows, mws) -> withDummyTestServiceForTeam owner tid $ \chan -> do
+ grantConsent tid member
+ requestLegalHoldDevice owner member tid !!! testResponse 201 Nothing
+ approveLegalHoldDevice (Just defPassword) member member tid !!! testResponse 200 Nothing
+ assertNotification mws $ \case
+ Ev.ClientAdded _ client -> do
+ clientId client @?= someClientId
+ clientType client @?= LegalHoldClientType
+ clientClass client @?= (Just LegalHoldClient)
+ _ -> assertBool "Unexpected event" False
+ -- Only the admin can disable legal hold
+ disableLegalHoldForUser (Just defPassword) tid member member !!! testResponse 403 (Just "operation-denied")
+ assertExactlyOneLegalHoldDevice member
+ -- Require password to disable for usern
+ disableLegalHoldForUser Nothing tid owner member !!! const 403 === statusCode
+ assertExactlyOneLegalHoldDevice member
+ disableLegalHoldForUser (Just defPassword) tid owner member !!! testResponse 200 Nothing
+ liftIO . assertMatchChan chan $ \(req, _) -> do
+ assertEqual "method" "POST" (requestMethod req)
+ assertEqual "path" (pathInfo req) ["legalhold", "remove"]
+ assertNotification mws $ \case
+ Ev.ClientEvent (Ev.ClientRemoved _ clientId') -> clientId clientId' @?= someClientId
+ _ -> assertBool "Unexpected event" False
+ assertNotification mws $ \case
+ Ev.UserEvent (Ev.UserLegalHoldDisabled uid) -> uid @?= member
+ _ -> assertBool "Unexpected event" False
+ -- Other users should also get the event
+ assertNotification ows $ \case
+ Ev.UserLegalHoldDisabled uid -> uid @?= member
+ _ -> assertBool "Unexpected event" False
+ assertZeroLegalHoldDevices member
+
+data IsWorking = Working | NotWorking
+ deriving (Eq, Show)
+
+testCreateLegalHoldTeamSettings :: TestM ()
+testCreateLegalHoldTeamSettings = do
+ (owner, tid) <- createBindingTeam
+ member <- randomUser
+ addTeamMemberInternal tid member (rolePermissions RoleMember) Nothing
+ ensureQueueEmpty
+ newService <- newLegalHoldService
+ -- not allowed to create if team setting is disabled
+ postSettings owner tid newService !!! testResponse 403 (Just "legalhold-not-enabled")
+ putEnabled tid Public.TeamFeatureEnabled -- enable it for this team
+
+ -- not allowed for users with corresp. permission bit missing
+ postSettings member tid newService !!! testResponse 403 (Just "operation-denied")
+ -- rejected if service is not available
+ postSettings owner tid newService !!! testResponse 412 (Just "legalhold-unavailable")
+ -- checks /status of legal hold service (boolean argument says whether the service is
+ -- behaving or not)
+ let lhapp :: HasCallStack => IsWorking -> Chan Void -> Application
+ lhapp NotWorking _ _ cont = cont respondBad
+ lhapp Working _ req cont = do
+ if
+ | pathInfo req /= ["legalhold", "status"] -> cont respondBad
+ | requestMethod req /= "GET" -> cont respondBad
+ | otherwise -> cont respondOk
+ respondOk :: Wai.Response
+ respondOk = responseLBS status200 mempty mempty
+ respondBad :: Wai.Response
+ respondBad = responseLBS status404 mempty mempty
+ lhtest :: HasCallStack => IsWorking -> Chan Void -> TestM ()
+ lhtest NotWorking _ = do
+ postSettings owner tid newService !!! testResponse 412 (Just "legalhold-unavailable")
+ lhtest Working _ = do
+ let Right [k] = pemParseBS "-----BEGIN PUBLIC KEY-----\n\n-----END PUBLIC KEY-----"
+ let badServiceBadKey = newService {newLegalHoldServiceKey = ServiceKeyPEM k}
+ postSettings owner tid badServiceBadKey !!! testResponse 400 (Just "legalhold-invalid-key")
+ postSettings owner tid newService !!! testResponse 201 Nothing
+ postSettings owner tid newService !!! testResponse 201 Nothing -- it's idempotent
+ ViewLegalHoldService service <- getSettingsTyped owner tid
+ liftIO $ do
+ Just (_, fpr) <- validateServiceKey (newLegalHoldServiceKey newService)
+ assertEqual "viewLegalHoldTeam" tid (viewLegalHoldServiceTeam service)
+ assertEqual "viewLegalHoldServiceUrl" (newLegalHoldServiceUrl newService) (viewLegalHoldServiceUrl service)
+ assertEqual "viewLegalHoldServiceFingerprint" fpr (viewLegalHoldServiceFingerprint service)
+ -- The pubkey is different... if a connection would be reused
+ -- this request would actually return a 201
+ let badServiceValidKey = newService {newLegalHoldServiceKey = ServiceKeyPEM publicKeyNotMatchingService}
+ postSettings owner tid badServiceValidKey !!! testResponse 412 (Just "legalhold-unavailable")
+ -- We do not use the higher level withDummyTestServiceForTeam here because we want to make
+ -- legalhold service misbehave on purpose in certain cases
+ -- if no valid service response can be obtained, responds with 400
+ withTestService (lhapp NotWorking) (lhtest NotWorking)
+ -- if valid service response can be obtained, writes a pending entry to cassandra
+ -- synchronously and respond with 201
+ withTestService (lhapp Working) (lhtest Working)
+
+-- NOTE: we do not expect event TeamEvent'TEAM_UPDATE as a reaction to this POST.
+
+testGetLegalHoldTeamSettings :: TestM ()
+testGetLegalHoldTeamSettings = do
+ (owner, tid) <- createBindingTeam
+ stranger <- randomUser
+ member <- randomUser
+ addTeamMemberInternal tid member (rolePermissions RoleMember) Nothing
+ ensureQueueEmpty
+ newService <- newLegalHoldService
+ let lhapp :: Chan () -> Application
+ lhapp _ch _req res = res $ responseLBS status200 mempty mempty
+ withTestService lhapp $ \_ -> do
+ -- returns 403 if user is not in team.
+ getSettings stranger tid !!! testResponse 403 (Just "no-team-member")
+ -- returns 200 with corresp. status if legalhold for team is disabled
+ do
+ let respOk :: ResponseLBS -> TestM ()
+ respOk resp = liftIO $ do
+ assertEqual "bad status code" 200 (statusCode resp)
+ assertEqual "bad body" ViewLegalHoldServiceDisabled (responseJsonUnsafe resp)
+ getSettings owner tid >>= respOk
+ getSettings member tid >>= respOk
+ putEnabled tid Public.TeamFeatureEnabled -- enable it for this team
+
+ -- returns 200 with corresp. status if legalhold for team is enabled, but not configured
+ do
+ let respOk :: ResponseLBS -> TestM ()
+ respOk resp = liftIO $ do
+ assertEqual "bad status code" 200 (statusCode resp)
+ assertEqual "bad body" ViewLegalHoldServiceNotConfigured (responseJsonUnsafe resp)
+ getSettings owner tid >>= respOk
+ getSettings member tid >>= respOk
+ postSettings owner tid newService !!! testResponse 201 Nothing
+ -- returns legal hold service info if team is under legal hold and user is in team (even
+ -- no permissions).
+ ViewLegalHoldService service <- getSettingsTyped member tid
+ liftIO $ do
+ let sKey = newLegalHoldServiceKey newService
+ Just (_, fpr) <- validateServiceKey sKey
+ assertEqual "viewLegalHoldServiceTeam" tid (viewLegalHoldServiceTeam service)
+ assertEqual "viewLegalHoldServiceUrl" (newLegalHoldServiceUrl newService) (viewLegalHoldServiceUrl service)
+ assertEqual "viewLegalHoldServiceFingerprint" fpr (viewLegalHoldServiceFingerprint service)
+ assertEqual "viewLegalHoldServiceKey" sKey (viewLegalHoldServiceKey service)
+ assertEqual "viewLegalHoldServiceAuthToken" (newLegalHoldServiceToken newService) (viewLegalHoldServiceAuthToken service)
+
+testRemoveLegalHoldFromTeam :: TestM ()
+testRemoveLegalHoldFromTeam = do
+ (owner, tid) <- createBindingTeam
+ stranger <- randomUser
+ member <- randomUser
+ addTeamMemberInternal tid member noPermissions Nothing
+ ensureQueueEmpty
+ -- fails if LH for team is disabled
+ deleteSettings (Just defPassword) owner tid !!! testResponse 403 (Just "legalhold-not-enabled")
+ withDummyTestServiceForTeam owner tid $ \chan -> do
+ newService <- newLegalHoldService
+ postSettings owner tid newService !!! testResponse 201 Nothing
+ -- enable legalhold for member
+ do
+ grantConsent tid member
+ requestLegalHoldDevice owner member tid !!! testResponse 201 Nothing
+ approveLegalHoldDevice (Just defPassword) member member tid !!! testResponse 200 Nothing
+ UserLegalHoldStatusResponse userStatus _ _ <- getUserStatusTyped member tid
+ liftIO $
+ assertEqual
+ "After approval user legalhold status should be Enabled"
+ UserLegalHoldEnabled
+ userStatus
+ -- returns 403 if user is not in team or has unsufficient permissions.
+ deleteSettings (Just defPassword) stranger tid !!! testResponse 403 (Just "no-team-member")
+ deleteSettings (Just defPassword) member tid !!! testResponse 403 (Just "operation-denied")
+ -- Fails without password
+ deleteSettings Nothing owner tid !!! testResponse 403 (Just "access-denied")
+ let delete'' expectRemoteLHCall = do
+ deleteSettings (Just defPassword) owner tid !!! testResponse 204 Nothing
+ when expectRemoteLHCall . liftIO . assertMatchChan chan $ \(req, _) -> do
+ putStrLn (show (pathInfo req, pathInfo req == ["legalhold", "remove"]))
+ putStrLn (show (requestMethod req, requestMethod req == "POST"))
+ assertEqual "path" ["legalhold", "remove"] (pathInfo req)
+ assertEqual "method" "POST" (requestMethod req)
+ resp <- getSettings owner tid
+ liftIO $ assertEqual "bad body" ViewLegalHoldServiceNotConfigured (responseJsonUnsafe resp)
+ -- returns 204 if legal hold is successfully removed from team
+ -- is idempotent (deleting twice in a row works) from BE's PoV
+ -- NOTE: Only if LH is active will there be a remote call to the LH service
+ delete'' True
+ delete'' False
+ -- deletion of settings should disable for all team members and remove their clients
+ do
+ UserLegalHoldStatusResponse userStatus _ _ <- getUserStatusTyped member tid
+ liftIO $
+ assertEqual
+ "After approval user legalhold status should be Disabled"
+ UserLegalHoldDisabled
+ userStatus
+ assertZeroLegalHoldDevices member
+
+testEnablePerTeam :: TestM ()
+testEnablePerTeam = do
+ (owner, tid) <- createBindingTeam
+ member <- randomUser
+ addTeamMemberInternal tid member (rolePermissions RoleMember) Nothing
+ ensureQueueEmpty
+ do
+ status :: Public.TeamFeatureStatus 'Public.TeamFeatureLegalHold <- responseJsonUnsafe <$> (getEnabled tid (getEnabled tid do
+ grantConsent tid member
+ requestLegalHoldDevice owner member tid !!! const 201 === statusCode
+ approveLegalHoldDevice (Just defPassword) member member tid !!! testResponse 200 Nothing
+ do
+ UserLegalHoldStatusResponse status _ _ <- getUserStatusTyped member tid
+ liftIO $ assertEqual "User legal hold status should be enabled" UserLegalHoldEnabled status
+ do
+ putEnabled tid Public.TeamFeatureDisabled -- disable again
+ status :: Public.TeamFeatureStatus 'Public.TeamFeatureLegalHold <- responseJsonUnsafe <$> (getEnabled tid (getEnabled tid (getEnabled tid TestM ()
+ tryout uid = do
+ brg <- view tsBrig
+ let newClientBody =
+ (newClient LegalHoldClientType (someLastPrekeys !! 0))
+ { newClientPassword = Just defPassword
+ }
+ req =
+ brg
+ . path "clients"
+ . json newClientBody
+ . zUser uid
+ . zConn "conn"
+ post req !!! const 400 === statusCode
+ assertZeroLegalHoldDevices uid
+
+testGetTeamMembersIncludesLHStatus :: TestM ()
+testGetTeamMembersIncludesLHStatus = do
+ (owner, tid) <- createBindingTeam
+ member <- randomUser
+ addTeamMemberInternal tid member (rolePermissions RoleMember) Nothing
+ ensureQueueEmpty
+
+ let findMemberStatus :: [TeamMember] -> Maybe UserLegalHoldStatus
+ findMemberStatus ms =
+ ms ^? traversed . filtered (has $ userId . only member) . legalHoldStatus
+
+ let check :: HasCallStack => UserLegalHoldStatus -> String -> TestM ()
+ check status msg = do
+ members' <- view teamMembers <$> getTeamMembers owner tid
+ liftIO $
+ assertEqual
+ ("legal hold status should be " <> msg)
+ (Just status)
+ (findMemberStatus members')
+
+ check UserLegalHoldNoConsent "disabled when it is disabled for the team"
+ withDummyTestServiceForTeam owner tid $ \_chan -> do
+ check UserLegalHoldNoConsent "no_consent on new team members"
+ grantConsent tid member
+ check UserLegalHoldDisabled "disabled on team members that have granted consent"
+ requestLegalHoldDevice owner member tid !!! testResponse 201 Nothing
+ check UserLegalHoldPending "pending after requesting device"
+ approveLegalHoldDevice (Just defPassword) member member tid !!! testResponse 200 Nothing
+ check UserLegalHoldEnabled "enabled after confirming device"
+
+testOldClientsBlockDeviceHandshake :: TestM ()
+testOldClientsBlockDeviceHandshake = do
+ -- "handshake between LH device and user with old devices is blocked"
+ --
+ -- this specifically checks the place that handles otr messages and responds with status
+ -- 412 and a list of missing clients.
+ --
+ -- if any of those clients are LH, this test provodes a "missing-legalhold-consent" error
+ -- instead, without any information about the LH clients. the condition is actually "has
+ -- old device or has not granted consent", but the latter part is blocked earlier in 1:1 and
+ -- group conversations, and hard to test at the device level.)
+ --
+ -- tracked here: https://wearezeta.atlassian.net/browse/SQSERVICES-454
+
+ (legalholder, tid) <- createBindingTeam
+ legalholder2 <- view userId <$> addUserToTeam legalholder tid
+ ensureQueueEmpty
+ (peer, tid2) <-
+ -- has to be a team member, granting LH consent for personal users is not supported.
+ createBindingTeam
+ ensureQueueEmpty
+
+ let doEnableLH :: HasCallStack => UserId -> UserId -> TestM ClientId
+ doEnableLH owner uid = do
+ requestLegalHoldDevice owner uid tid !!! testResponse 201 Nothing
+ approveLegalHoldDevice (Just defPassword) uid uid tid !!! testResponse 200 Nothing
+ UserLegalHoldStatusResponse userStatus _ _ <- getUserStatusTyped uid tid
+ liftIO $ assertEqual "approving should change status" UserLegalHoldEnabled userStatus
+ getInternalClientsFull (UserSet $ Set.fromList [uid])
+ <&> userClientsFull
+ <&> Map.elems
+ <&> Set.unions
+ <&> Set.toList
+ <&> (\[x] -> x)
+ <&> clientId
+
+ withDummyTestServiceForTeam legalholder tid $ \_chan -> do
+ grantConsent tid legalholder
+ grantConsent tid legalholder2
+
+ legalholderLHDevice <- doEnableLH legalholder legalholder
+ _legalholder2LHDevice <- doEnableLH legalholder legalholder2
+
+ let caps = Set.singleton Client.ClientSupportsLegalholdImplicitConsent
+ legalholderClient <- do
+ clnt <- randomClientWithCaps legalholder (someLastPrekeys !! 1) (Just caps)
+ ensureClientCaps legalholder clnt (Client.ClientCapabilityList caps)
+ pure clnt
+ legalholder2Client <- do
+ clnt <- randomClient legalholder2 (someLastPrekeys !! 3)
+ -- this another way to do it (instead of providing caps during client creation).
+ ensureClientCaps legalholder2 clnt (Client.ClientCapabilityList mempty)
+ upgradeClientToLH legalholder2 clnt
+ ensureClientCaps legalholder2 clnt (Client.ClientCapabilityList caps)
+ pure clnt
+ grantConsent tid2 peer
+ connectUsers peer (List1.list1 legalholder [legalholder2])
+
+ convId <-
+ decodeConvId
+ <$> ( postConv peer [legalholder, legalholder2] (Just "gossip") [] Nothing Nothing
+ UserId -> ClientId -> TestM ResponseLBS
+ runit sender senderClient = do
+ postOtrMessage id sender senderClient convId rcps
+ where
+ rcps =
+ [ (legalholder, legalholderClient, "ciphered"),
+ (legalholder, legalholderLHDevice, "ciphered"),
+ (legalholder2, legalholder2Client, "ciphered")
+ -- legalholder2 LH device missing
+ ]
+
+ errWith :: (HasCallStack, Typeable a, FromJSON a) => Int -> (a -> Bool) -> ResponseLBS -> TestM ()
+ errWith wantStatus wantBody rsp = liftIO $ do
+ assertEqual "" wantStatus (statusCode rsp)
+ assertBool
+ (show $ responseBody rsp)
+ ( case responseJsonMaybe rsp of
+ Nothing -> False
+ Just bdy -> wantBody bdy
+ )
+
+ -- LH devices are treated as clients that have the ClientSupportsLegalholdImplicitConsent
+ -- capability (so LH doesn't break for users who have LH devices; it sounds silly, but
+ -- it's good to test this, since it did require adding a few lines of production code in
+ -- 'addClient' about client capabilities).
+ runit legalholder legalholderClient >>= errWith 412 (\(_ :: Msg.ClientMismatch) -> True)
+
+ -- If user has a client without the ClientSupportsLegalholdImplicitConsent
+ -- capability then message sending is prevented to legalhold devices.
+ peerClient <- randomClient peer (someLastPrekeys !! 2)
+ runit peer peerClient >>= errWith 412 (\err -> Error.label err == "missing-legalhold-consent")
+ upgradeClientToLH peer peerClient
+ runit peer peerClient >>= errWith 412 (\(_ :: Msg.ClientMismatch) -> True)
+
+data TestClaimKeys
+ = TCKConsentMissing
+ | TCKOldClient
+ | TCKConsentAndNewClients
+
+testClaimKeys :: TestClaimKeys -> TestM ()
+testClaimKeys testcase = do
+ -- "cannot fetch prekeys of LH users if requester did not give consent or has old clients"
+ (legalholder, tid) <- createBindingTeam
+ ensureQueueEmpty
+ (peer, teamPeer) <- createBindingTeam
+ ensureQueueEmpty
+
+ let doEnableLH :: HasCallStack => TeamId -> UserId -> UserId -> TestM ClientId
+ doEnableLH team owner uid = do
+ requestLegalHoldDevice owner uid team !!! testResponse 201 Nothing
+ approveLegalHoldDevice (Just defPassword) uid uid team !!! testResponse 200 Nothing
+ UserLegalHoldStatusResponse userStatus _ _ <- getUserStatusTyped uid team
+ liftIO $ assertEqual "approving should change status" UserLegalHoldEnabled userStatus
+ getInternalClientsFull (UserSet $ Set.fromList [uid])
+ <&> userClientsFull
+ <&> Map.elems
+ <&> Set.unions
+ <&> Set.toList
+ <&> (\[x] -> x)
+ <&> clientId
+
+ let makePeerClient :: TestM ()
+ makePeerClient = case testcase of
+ TCKConsentMissing -> do
+ peerClient <- randomClient peer (someLastPrekeys !! 2)
+ upgradeClientToLH peer peerClient
+ TCKOldClient -> do
+ void $ randomClient peer (someLastPrekeys !! 2)
+ grantConsent teamPeer peer
+ TCKConsentAndNewClients -> do
+ peerClient <- randomClient peer (someLastPrekeys !! 2)
+ upgradeClientToLH peer peerClient
+ grantConsent teamPeer peer
+
+ let assertResponse :: Assertions ()
+ assertResponse = case testcase of
+ TCKConsentMissing -> bad
+ TCKOldClient -> bad
+ TCKConsentAndNewClients -> good
+ where
+ good = testResponse 200 Nothing
+ bad = testResponse 412 (Just "missing-legalhold-consent")
+
+ let fetchKeys :: ClientId -> TestM ()
+ fetchKeys legalholderLHDevice = do
+ getUsersPrekeysClientUnqualified peer legalholder legalholderLHDevice !!! assertResponse
+ getUsersPrekeyBundleUnqualified peer legalholder !!! assertResponse
+ let userClients = UserClients (Map.fromList [(legalholder, Set.fromList [legalholderLHDevice])])
+ getMultiUserPrekeyBundleUnqualified peer userClients !!! assertResponse
+
+ withDummyTestServiceForTeam legalholder tid $ \_chan -> do
+ grantConsent tid legalholder
+ legalholderLHDevice <- doEnableLH tid legalholder legalholder
+
+ makePeerClient
+ fetchKeys legalholderLHDevice
+
+----------------------------------------------------------------------
+-- API helpers
+
+getEnabled :: HasCallStack => TeamId -> TestM ResponseLBS
+getEnabled tid = do
+ g <- view tsGalley
+ get $
+ g
+ . paths ["i", "teams", toByteString' tid, "features", "legalhold"]
+
+renewToken :: HasCallStack => Text -> TestM ()
+renewToken tok = do
+ b <- view tsBrig
+ void . post $
+ b
+ . paths ["access"]
+ . cookieRaw "zuid" (toByteString' tok)
+ . expect2xx
+
+putEnabled :: HasCallStack => TeamId -> Public.TeamFeatureStatusValue -> TestM ()
+putEnabled tid enabled = do
+ g <- view tsGalley
+ putEnabledM g tid enabled
+
+putEnabledM :: (HasCallStack, MonadHttp m, MonadIO m) => GalleyR -> TeamId -> Public.TeamFeatureStatusValue -> m ()
+putEnabledM g tid enabled = void $ putEnabledM' g expect2xx tid enabled
+
+putEnabled' :: HasCallStack => (Bilge.Request -> Bilge.Request) -> TeamId -> Public.TeamFeatureStatusValue -> TestM ResponseLBS
+putEnabled' extra tid enabled = do
+ g <- view tsGalley
+ putEnabledM' g extra tid enabled
+
+putEnabledM' :: (HasCallStack, MonadHttp m, MonadIO m) => GalleyR -> (Bilge.Request -> Bilge.Request) -> TeamId -> Public.TeamFeatureStatusValue -> m ResponseLBS
+putEnabledM' g extra tid enabled = do
+ put $
+ g
+ . paths ["i", "teams", toByteString' tid, "features", "legalhold"]
+ . json (Public.TeamFeatureStatusNoConfig enabled)
+ . extra
+
+postSettings :: HasCallStack => UserId -> TeamId -> NewLegalHoldService -> TestM ResponseLBS
+postSettings uid tid new =
+ -- Retry calls to this endpoint, on k8s it sometimes takes a while to establish a working
+ -- connection.
+ retrying policy only412 $ \_ -> do
+ g <- view tsGalley
+ post $
+ g
+ . paths ["teams", toByteString' tid, "legalhold", "settings"]
+ . zUser uid
+ . zConn "conn"
+ . zType "access"
+ . json new
+ where
+ policy :: RetryPolicy
+ policy = exponentialBackoff 50 <> limitRetries 5
+ only412 :: RetryStatus -> ResponseLBS -> TestM Bool
+ only412 _ resp = pure $ statusCode resp == 412
+
+getSettingsTyped :: HasCallStack => UserId -> TeamId -> TestM ViewLegalHoldService
+getSettingsTyped uid tid = responseJsonUnsafe <$> (getSettings uid tid UserId -> TeamId -> TestM ResponseLBS
+getSettings uid tid = do
+ g <- view tsGalley
+ get $
+ g
+ . paths ["teams", toByteString' tid, "legalhold", "settings"]
+ . zUser uid
+ . zConn "conn"
+ . zType "access"
+
+deleteSettings :: HasCallStack => Maybe PlainTextPassword -> UserId -> TeamId -> TestM ResponseLBS
+deleteSettings mPassword uid tid = do
+ g <- view tsGalley
+ delete $
+ g
+ . paths ["teams", toByteString' tid, "legalhold", "settings"]
+ . zUser uid
+ . zConn "conn"
+ . zType "access"
+ . json (RemoveLegalHoldSettingsRequest mPassword)
+
+getUserStatusTyped :: HasCallStack => UserId -> TeamId -> TestM UserLegalHoldStatusResponse
+getUserStatusTyped uid tid = do
+ g <- view tsGalley
+ getUserStatusTyped' g uid tid
+
+getUserStatusTyped' :: (HasCallStack, MonadHttp m, MonadIO m, MonadCatch m) => GalleyR -> UserId -> TeamId -> m UserLegalHoldStatusResponse
+getUserStatusTyped' g uid tid = do
+ resp <- getUserStatus' g uid tid GalleyR -> UserId -> TeamId -> m ResponseLBS
+getUserStatus' g uid tid = do
+ get $
+ g
+ . paths ["teams", toByteString' tid, "legalhold", toByteString' uid]
+ . zUser uid
+ . zConn "conn"
+ . zType "access"
+
+approveLegalHoldDevice :: HasCallStack => Maybe PlainTextPassword -> UserId -> UserId -> TeamId -> TestM ResponseLBS
+approveLegalHoldDevice mPassword zusr uid tid = do
+ g <- view tsGalley
+ approveLegalHoldDevice' g mPassword zusr uid tid
+
+approveLegalHoldDevice' ::
+ (HasCallStack, MonadHttp m, MonadIO m) =>
+ GalleyR ->
+ Maybe PlainTextPassword ->
+ UserId ->
+ UserId ->
+ TeamId ->
+ m ResponseLBS
+approveLegalHoldDevice' g mPassword zusr uid tid = do
+ put $
+ g
+ . paths ["teams", toByteString' tid, "legalhold", toByteString' uid, "approve"]
+ . zUser zusr
+ . zConn "conn"
+ . zType "access"
+ . json (ApproveLegalHoldForUserRequest mPassword)
+
+disableLegalHoldForUser ::
+ HasCallStack =>
+ Maybe PlainTextPassword ->
+ TeamId ->
+ UserId ->
+ UserId ->
+ TestM ResponseLBS
+disableLegalHoldForUser mPassword tid zusr uid = do
+ g <- view tsGalley
+ disableLegalHoldForUser' g mPassword tid zusr uid
+
+disableLegalHoldForUser' ::
+ (HasCallStack, MonadHttp m, MonadIO m) =>
+ GalleyR ->
+ Maybe PlainTextPassword ->
+ TeamId ->
+ UserId ->
+ UserId ->
+ m ResponseLBS
+disableLegalHoldForUser' g mPassword tid zusr uid = do
+ delete $
+ g
+ . paths ["teams", toByteString' tid, "legalhold", toByteString' uid]
+ . zUser zusr
+ . zType "access"
+ . json (DisableLegalHoldForUserRequest mPassword)
+
+assertExactlyOneLegalHoldDevice :: HasCallStack => UserId -> TestM ()
+assertExactlyOneLegalHoldDevice uid = do
+ clients :: [Client] <-
+ getClients uid >>= responseJsonError
+ liftIO $ do
+ let numdevs = length $ clientType <$> clients
+ assertEqual ("expected exactly one legal hold device for user: " <> show uid) numdevs 1
+
+assertZeroLegalHoldDevices :: HasCallStack => UserId -> TestM ()
+assertZeroLegalHoldDevices uid = do
+ clients :: [Client] <-
+ getClients uid >>= responseJsonError
+ liftIO $ do
+ let numdevs = length $ clientType <$> clients
+ assertBool
+ ( "a legal hold device was found when none was expected for user"
+ <> show uid
+ )
+ (numdevs == 0)
+
+---------------------------------------------------------------------
+--- Device helpers
+
+grantConsent :: HasCallStack => TeamId -> UserId -> TestM ()
+grantConsent tid zusr = do
+ g <- view tsGalley
+ grantConsent' g tid zusr
+
+grantConsent' :: (HasCallStack, MonadHttp m, MonadIO m) => GalleyR -> TeamId -> UserId -> m ()
+grantConsent' = grantConsent'' expect2xx
+
+grantConsent'' :: (HasCallStack, MonadHttp m, MonadIO m) => (Bilge.Request -> Bilge.Request) -> GalleyR -> TeamId -> UserId -> m ()
+grantConsent'' expectation g tid zusr = do
+ void . post $
+ g
+ . paths ["teams", toByteString' tid, "legalhold", "consent"]
+ . zUser zusr
+ . zConn "conn"
+ . zType "access"
+ . expectation
+
+requestLegalHoldDevice :: HasCallStack => UserId -> UserId -> TeamId -> TestM ResponseLBS
+requestLegalHoldDevice zusr uid tid = do
+ g <- view tsGalley
+ requestLegalHoldDevice' g zusr uid tid
+
+requestLegalHoldDevice' :: (HasCallStack, MonadHttp m, MonadIO m) => GalleyR -> UserId -> UserId -> TeamId -> m ResponseLBS
+requestLegalHoldDevice' g zusr uid tid = do
+ post $
+ g
+ . paths ["teams", toByteString' tid, "legalhold", toByteString' uid]
+ . zUser zusr
+ . zConn "conn"
+ . zType "access"
+
+--------------------------------------------------------------------
+-- setup helpers
+
+-- | Create a new legal hold service creation request with the URL from the integration test
+-- config.
+newLegalHoldService :: HasCallStack => TestM NewLegalHoldService
+newLegalHoldService = do
+ config <- view (tsIConf . to provider)
+ key' <- liftIO $ readServiceKey (publicKey config)
+ let Just url =
+ fromByteString $
+ encodeUtf8 (botHost config) <> ":" <> cs (show (botPort config)) <> "/legalhold"
+ return
+ NewLegalHoldService
+ { newLegalHoldServiceUrl = url,
+ newLegalHoldServiceKey = key',
+ newLegalHoldServiceToken = ServiceToken "tok"
+ }
+
+-- | FUTUREWORK: reduce duplication (copied from brig/Provider.hs)
+readServiceKey :: (HasCallStack, MonadIO m) => FilePath -> m ServiceKeyPEM
+readServiceKey fp = liftIO $ do
+ bs <- BS.readFile fp
+ let Right [k] = pemParseBS bs
+ return (ServiceKeyPEM k)
+
+-- FUTUREWORK: run this test suite against an actual LH service (by changing URL and key in
+-- the config file), and see if it works as well as with our mock service.
+withDummyTestServiceForTeam ::
+ forall a.
+ HasCallStack =>
+ UserId ->
+ TeamId ->
+ -- | the test
+ (Chan (Wai.Request, LBS) -> TestM a) ->
+ TestM a
+withDummyTestServiceForTeam owner tid go = do
+ withTestService dummyService runTest
+ where
+ runTest :: Chan (Wai.Request, LBS) -> TestM a
+ runTest chan = do
+ newService <- newLegalHoldService
+ putEnabled tid Public.TeamFeatureEnabled -- enable it for this team
+ postSettings owner tid newService !!! testResponse 201 Nothing
+ go chan
+
+ dummyService :: Chan (Wai.Request, LBS) -> Wai.Application
+ dummyService ch req cont = do
+ reqBody <- Wai.strictRequestBody req
+ writeChan ch (req, reqBody)
+ case (pathInfo req, requestMethod req, getRequestHeader "Authorization" req) of
+ (["legalhold", "status"], "GET", _) -> cont respondOk
+ (_, _, Nothing) -> cont missingAuth
+ (["legalhold", "initiate"], "POST", Just _) -> cont initiateResp
+ (["legalhold", "confirm"], "POST", Just _) ->
+ cont respondOk
+ (["legalhold", "remove"], "POST", Just _) -> cont respondOk
+ _ -> cont respondBad
+
+ initiateResp :: Wai.Response
+ initiateResp =
+ Wai.json $
+ -- FUTUREWORK: use another key to prevent collisions with keys used by tests
+ NewLegalHoldClient somePrekeys (head $ someLastPrekeys)
+
+ respondOk :: Wai.Response
+ respondOk = responseLBS status200 mempty mempty
+
+ respondBad :: Wai.Response
+ respondBad = responseLBS status404 mempty mempty
+
+ missingAuth :: Wai.Response
+ missingAuth = responseLBS status400 mempty "no authorization header"
+
+ getRequestHeader :: String -> Wai.Request -> Maybe ByteString
+ getRequestHeader name req = lookup (fromString name) $ requestHeaders req
+
+-- | Run a test with an mock legal hold service application. The mock service is also binding
+-- to a TCP socket for the backend to connect to. The mock service can expose internal
+-- details to the test (for both read and write) via a 'Chan'.
+--
+-- WARNINGS: (1) This is not concurrency-proof! (2) tests need to be written in a way that
+-- they can be run several times if they fail the first time. this is the allow for the ssl
+-- service to have some time to propagate through the test system (needed on k8s).
+withTestService ::
+ HasCallStack =>
+ -- | the mock service
+ (Chan e -> Application) ->
+ -- | the test
+ (Chan e -> TestM a) ->
+ TestM a
+withTestService mkApp go = do
+ config <- view (tsIConf . to provider)
+ let tlss = Warp.tlsSettings (cert config) (privateKey config)
+ let defs = Warp.defaultSettings {Warp.settingsPort = botPort config}
+ buf <- liftIO newChan
+ srv <-
+ liftIO . Async.async $
+ Warp.runTLS tlss defs $
+ mkApp buf
+ go buf `finally` liftIO (Async.cancel srv)
+
+publicKeyNotMatchingService :: PEM
+publicKeyNotMatchingService =
+ let Right [k] =
+ pemParseBS . BS.unlines $
+ [ "-----BEGIN PUBLIC KEY-----",
+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu+Kg/PHHU3atXrUbKnw0",
+ "G06FliXcNt3lMwl2os5twEDcPPFw/feGiAKymxp+7JqZDrseS5D9THGrW+OQRIPH",
+ "WvUBdiLfGrZqJO223DB6D8K2Su/odmnjZJ2z23rhXoEArTplu+Dg9K+c2LVeXTKV",
+ "VPOaOzgtAB21XKRiQ4ermqgi3/njr03rXyq/qNkuNd6tNcg+HAfGxfGvvCSYBfiS",
+ "bUKr/BeArYRcjzr/h5m1In6fG/if9GEI6m8dxHT9JbY53wiksowy6ajCuqskIFg8",
+ "7X883H+LA/d6X5CTiPv1VMxXdBUiGPuC9IT/6CNQ1/LFt0P37ax58+LGYlaFo7la",
+ "nQIDAQAZ",
+ "-----END PUBLIC KEY-----"
+ ]
+ in k
+
+testGetLegalholdStatus :: TestM ()
+testGetLegalholdStatus = do
+ (owner1, tid1) <- createBindingTeam
+ member1 <- view userId <$> addUserToTeam owner1 tid1
+ ensureQueueEmpty
+
+ (owner2, tid2) <- createBindingTeam
+ member2 <- view userId <$> addUserToTeam owner2 tid2
+ ensureQueueEmpty
+
+ personal <- randomUser
+
+ let check :: HasCallStack => UserId -> UserId -> Maybe TeamId -> UserLegalHoldStatus -> TestM ()
+ check getter targetUser targetTeam stat = do
+ profile <- getUserProfile getter targetUser
+ when (profileLegalholdStatus profile /= stat) $ do
+ meminfo <- getUserStatusTyped targetUser `mapM` targetTeam
+
+ liftIO . forM_ meminfo $ \mem -> do
+ assertEqual "member LH status" stat (ulhsrStatus mem)
+ assertEqual "team id in brig user record" targetTeam (profileTeam profile)
+
+ liftIO $ assertEqual "user profile status info" stat (profileLegalholdStatus profile)
+
+ requestDev :: HasCallStack => UserId -> UserId -> TeamId -> TestM ()
+ requestDev requestor target tid = do
+ requestLegalHoldDevice requestor target tid !!! testResponse 201 Nothing
+
+ approveDev :: HasCallStack => UserId -> TeamId -> TestM ()
+ approveDev target tid = do
+ approveLegalHoldDevice (Just defPassword) target target tid !!! testResponse 200 Nothing
+
+ check owner1 member1 (Just tid1) UserLegalHoldNoConsent
+ check member1 member1 (Just tid1) UserLegalHoldNoConsent
+ check owner2 member1 (Just tid1) UserLegalHoldNoConsent
+ check member2 member1 (Just tid1) UserLegalHoldNoConsent
+ check personal member1 (Just tid1) UserLegalHoldNoConsent
+ check owner1 personal Nothing UserLegalHoldNoConsent
+ check member1 personal Nothing UserLegalHoldNoConsent
+ check owner2 personal Nothing UserLegalHoldNoConsent
+ check member2 personal Nothing UserLegalHoldNoConsent
+ check personal personal Nothing UserLegalHoldNoConsent
+
+ onlyIfLhEnabled $
+ withDummyTestServiceForTeam owner1 tid1 $ \_chan -> do
+ grantConsent tid1 member1
+ check owner1 member1 (Just tid1) UserLegalHoldDisabled
+ check member2 member1 (Just tid1) UserLegalHoldDisabled
+ check personal member1 (Just tid1) UserLegalHoldDisabled
+
+ requestDev owner1 member1 tid1
+ check personal member1 (Just tid1) UserLegalHoldPending
+
+ approveDev member1 tid1
+ check personal member1 (Just tid1) UserLegalHoldEnabled
+
+----------------------------------------------------------------------
+-- test helpers
+
+deriving instance Show Ev.Event
+
+deriving instance Show Ev.UserEvent
+
+deriving instance Show Ev.ClientEvent
+
+deriving instance Show Ev.PropertyEvent
+
+deriving instance Show Ev.ConnectionEvent
+
+-- (partial implementation, just good enough to make the tests work)
+instance FromJSON Ev.Event where
+ parseJSON ev = flip (withObject "Ev.Event") ev $ \o -> do
+ typ :: Text <- o .: "type"
+ if
+ | typ `elem` ["user.legalhold-request", "user.legalhold-enable", "user.legalhold-disable"] -> Ev.UserEvent <$> Aeson.parseJSON ev
+ | typ `elem` ["user.client-add", "user.client-remove"] -> Ev.ClientEvent <$> Aeson.parseJSON ev
+ | typ `elem` ["user.connection"] -> Ev.ConnectionEvent <$> Aeson.parseJSON ev
+ | otherwise -> fail $ "Ev.Event: unsupported event type: " <> show typ
+
+-- (partial implementation, just good enough to make the tests work)
+instance FromJSON Ev.UserEvent where
+ parseJSON = withObject "Ev.UserEvent" $ \o -> do
+ tag :: Text <- o .: "type"
+ case tag of
+ "user.legalhold-enable" -> Ev.UserLegalHoldEnabled <$> o .: "id"
+ "user.legalhold-disable" -> Ev.UserLegalHoldDisabled <$> o .: "id"
+ "user.legalhold-request" ->
+ Ev.LegalHoldClientRequested
+ <$> ( Ev.LegalHoldClientRequestedData
+ <$> o .: "id" -- this is the target user
+ <*> o .: "last_prekey"
+ <*> (o .: "client" >>= withObject "id" (.: "id"))
+ )
+ x -> fail $ "Ev.UserEvent: unsupported event type: " ++ show x
+
+-- (partial implementation, just good enough to make the tests work)
+instance FromJSON Ev.ClientEvent where
+ parseJSON = withObject "Ev.ClientEvent" $ \o -> do
+ tag :: Text <- o .: "type"
+ case tag of
+ "user.client-add" -> Ev.ClientAdded fakeuid <$> o .: "client"
+ "user.client-remove" -> Ev.ClientRemoved fakeuid <$> (makeFakeClient <$> (o .: "client" >>= withObject "id" (.: "id")))
+ x -> fail $ "Ev.ClientEvent: unsupported event type: " ++ show x
+ where
+ fakeuid = read @UserId "6980fb5e-ba64-11eb-a339-0b3625bf01be"
+ makeFakeClient cid =
+ Client
+ cid
+ PermanentClientType
+ (toUTCTimeMillis $ read "2021-05-23 09:39:15.937523809 UTC")
+ Nothing
+ Nothing
+ Nothing
+ Nothing
+ Nothing
+ (Client.ClientCapabilityList mempty)
+
+instance FromJSON Ev.ConnectionEvent where
+ parseJSON = Aeson.withObject "ConnectionEvent" $ \o -> do
+ tag :: Text <- o .: "type"
+ case tag of
+ "user.connection" ->
+ Ev.ConnectionUpdated
+ <$> o .: "connection"
+ <*> pure Nothing
+ <*> pure Nothing
+ x -> fail $ "unspported event type: " ++ show x
+
+assertNotification :: (HasCallStack, FromJSON a, MonadIO m) => WS.WebSocket -> (a -> Assertion) -> m ()
+assertNotification ws predicate =
+ void . liftIO . WS.assertMatch (5 WS.# WS.Second) ws $ \notif -> do
+ unless ((NonEmpty.length . List1.toNonEmpty $ ntfPayload $ notif) == 1) $
+ error $ "not suppored by test helper: event with more than one object in the payload: " <> cs (Aeson.encode notif)
+ let j = Aeson.Object $ List1.head (ntfPayload notif)
+ case Aeson.fromJSON j of
+ Aeson.Success x -> predicate x
+ Aeson.Error s -> error $ s ++ " in " ++ cs (Aeson.encode j)
+
+assertNoNotification :: (HasCallStack, MonadIO m) => WS.WebSocket -> m ()
+assertNoNotification ws = void . liftIO $ WS.assertNoEvent (5 WS.# WS.Second) [ws]
+
+assertMatchJSON :: (HasCallStack, FromJSON a, MonadThrow m, MonadCatch m, MonadIO m) => Chan (Wai.Request, LBS) -> (a -> m ()) -> m ()
+assertMatchJSON c match = do
+ assertMatchChan c $ \(_, reqBody) -> do
+ case Aeson.eitherDecode reqBody of
+ Right x -> match x
+ Left s -> error $ s ++ " in " ++ cs reqBody
+
+assertMatchChan :: (HasCallStack, MonadThrow m, MonadCatch m, MonadIO m) => Chan a -> (a -> m ()) -> m ()
+assertMatchChan c match = go []
+ where
+ refill = mapM_ (liftIO <$> writeChan c)
+ go buf = do
+ m <- liftIO . timeout (5 WS.# WS.Second) . readChan $ c
+ case m of
+ Just n ->
+ do
+ match n
+ refill buf
+ `catchAll` \e -> case asyncExceptionFromException e of
+ Just x -> error $ show (x :: SomeAsyncException)
+ Nothing -> go (n : buf)
+ Nothing -> do
+ refill buf
+ error "Timeout"
diff --git a/services/galley/test/integration/API/Util.hs b/services/galley/test/integration/API/Util.hs
index b99b4e1fd23..61d780a905e 100644
--- a/services/galley/test/integration/API/Util.hs
+++ b/services/galley/test/integration/API/Util.hs
@@ -22,14 +22,15 @@ module API.Util where
import qualified API.SQS as SQS
import Bilge hiding (timeout)
import Bilge.Assert
+import Bilge.TestSession
import Brig.Types
-import Brig.Types.Intra (ConnectionStatus (ConnectionStatus), UserAccount (..))
+import Brig.Types.Intra (ConnectionStatus (ConnectionStatus), UserAccount (..), UserSet (..))
import Brig.Types.Team.Invitation
import Brig.Types.User.Auth (CookieLabel (..))
-import Control.Exception (finally)
import Control.Lens hiding (from, to, (#), (.=))
-import Control.Monad.Catch (MonadCatch)
-import Control.Retry (constantDelay, limitRetries, retrying)
+import Control.Monad.Catch (MonadCatch, MonadMask, finally)
+import Control.Monad.Except (ExceptT, runExceptT)
+import Control.Retry (constantDelay, exponentialBackoff, limitRetries, retrying)
import Data.Aeson hiding (json)
import Data.Aeson.Lens (key, _String)
import qualified Data.ByteString as BS
@@ -38,12 +39,15 @@ import qualified Data.ByteString.Char8 as C
import Data.ByteString.Conversion
import qualified Data.ByteString.Lazy as Lazy
import qualified Data.Currency as Currency
+import Data.Domain
import qualified Data.Handle as Handle
import qualified Data.HashMap.Strict as HashMap
import Data.Id
import Data.Json.Util (UTCTimeMillis)
+import Data.LegalHold (defUserLegalHoldStatus)
import Data.List.NonEmpty (NonEmpty)
import Data.List1 as List1
+import qualified Data.Map as LMap
import qualified Data.Map.Strict as Map
import Data.Misc
import Data.ProtocolBuffers (encodeMessage)
@@ -76,23 +80,38 @@ import Gundeck.Types.Notification
queuedTime,
)
import Imports
-import qualified Network.Wai.Test as WaiTest
import System.Random
import qualified Test.QuickCheck as Q
import Test.Tasty.Cannon (TimeoutUnit (..), (#))
import qualified Test.Tasty.Cannon as WS
import Test.Tasty.HUnit
+import TestHelpers (viewFederationDomain)
import TestSetup
import UnliftIO.Timeout
+import Util.Options
import Web.Cookie
import qualified Wire.API.Conversation as Public
import Wire.API.Conversation.Member (Member (..))
import qualified Wire.API.Event.Team as TE
+import Wire.API.Federation.GRPC.Types (FederatedRequest, OutwardResponse (..))
+import qualified Wire.API.Federation.Mock as Mock
import qualified Wire.API.Message.Proto as Proto
+import Wire.API.User.Client (ClientCapability (..), UserClientsFull (UserClientsFull))
+import qualified Wire.API.User.Client as Client
-------------------------------------------------------------------------------
-- API Operations
+-- | A class for monads with access to a Galley instance
+class HasGalley m where
+ viewGalley :: m GalleyR
+
+instance HasGalley TestM where
+ viewGalley = view tsGalley
+
+instance (HasGalley m, Monad m) => HasGalley (SessionT m) where
+ viewGalley = lift viewGalley
+
symmPermissions :: [Perm] -> Permissions
symmPermissions p = let s = Set.fromList p in fromJust (newPermissions s s)
@@ -100,7 +119,7 @@ createBindingTeam :: HasCallStack => TestM (UserId, TeamId)
createBindingTeam = do
ownerid <- randomTeamCreator
teams <- getTeams ownerid
- let [team] = view (teamListTeams) teams
+ let [team] = view teamListTeams teams
let tid = view teamId team
SQS.assertQueue "create team" SQS.tActivate
refreshIndex
@@ -113,7 +132,7 @@ createBindingTeamWithMembers numUsers = do
mem <- addUserToTeam owner tid
SQS.assertQueue "add member" $ SQS.tUpdate (fromIntegral n) [owner]
refreshIndex
- return $ view Galley.Types.Teams.userId $ mem
+ return $ view Galley.Types.Teams.userId mem
return (tid, owner, members)
@@ -304,9 +323,11 @@ addTeamMember usr tid muid mperms mmbinv = do
post (g . paths ["teams", toByteString' tid, "members"] . zUser usr . zConn "conn" . payload)
!!! const 200 === statusCode
+-- | FUTUREWORK: do not use this, it's broken!! use 'addUserToTeam' instead! https://wearezeta.atlassian.net/browse/SQSERVICES-471
addTeamMemberInternal :: HasCallStack => TeamId -> UserId -> Permissions -> Maybe (UserId, UTCTimeMillis) -> TestM ()
addTeamMemberInternal tid muid mperms mmbinv = addTeamMemberInternal' tid muid mperms mmbinv !!! const 200 === statusCode
+-- | FUTUREWORK: do not use this, it's broken!! use 'addUserToTeam' instead! https://wearezeta.atlassian.net/browse/SQSERVICES-471
addTeamMemberInternal' :: HasCallStack => TeamId -> UserId -> Permissions -> Maybe (UserId, UTCTimeMillis) -> TestM ResponseLBS
addTeamMemberInternal' tid muid mperms mmbinv = do
g <- view tsGalley
@@ -408,13 +429,13 @@ getInvitationCode t ref = do
. queryItem "invitation_id" (toByteString' ref)
)
let lbs = fromMaybe "" $ responseBody r
- return $ fromByteString . Text.encodeUtf8 =<< (lbs ^? key "code" . _String)
+ return $ fromByteString . Text.encodeUtf8 =<< lbs ^? key "code" . _String
fromMaybe (error "No code?")
<$> retrying
(constantDelay 800000 <> limitRetries 3)
(\_ -> pure . isNothing)
- (\_ -> getm)
+ (const getm)
-- Note that here we don't make use of the datatype because NewConv has a default
-- and therefore cannot be unset. However, given that this is to test the legacy
@@ -457,7 +478,7 @@ createTeamConvAccessRaw u tid us name acc role mtimer convRole = do
let tinfo = ConvTeamInfo tid False
let conv =
NewConvUnmanaged $
- NewConv us name (fromMaybe (Set.fromList []) acc) role (Just tinfo) mtimer Nothing (fromMaybe roleNameWireAdmin convRole)
+ NewConv us [] name (fromMaybe (Set.fromList []) acc) role (Just tinfo) mtimer Nothing (fromMaybe roleNameWireAdmin convRole)
post
( g
. path "/conversations"
@@ -486,7 +507,7 @@ createManagedConv u tid us name acc mtimer = do
let tinfo = ConvTeamInfo tid True
let conv =
NewConvManaged $
- NewConv us name (fromMaybe (Set.fromList []) acc) Nothing (Just tinfo) mtimer Nothing roleNameWireAdmin
+ NewConv us [] name (fromMaybe (Set.fromList []) acc) Nothing (Just tinfo) mtimer Nothing roleNameWireAdmin
r <-
post
( g
@@ -504,22 +525,34 @@ createOne2OneTeamConv u1 u2 n tid = do
g <- view tsGalley
let conv =
NewConvUnmanaged $
- NewConv [u2] n mempty Nothing (Just $ ConvTeamInfo tid False) Nothing Nothing roleNameWireAdmin
+ NewConv [u2] [] n mempty Nothing (Just $ ConvTeamInfo tid False) Nothing Nothing roleNameWireAdmin
post $ g . path "/conversations/one2one" . zUser u1 . zConn "conn" . zType "access" . json conv
postConv :: UserId -> [UserId] -> Maybe Text -> [Access] -> Maybe AccessRole -> Maybe Milliseconds -> TestM ResponseLBS
postConv u us name a r mtimer = postConvWithRole u us name a r mtimer roleNameWireAdmin
-postConvWithRole :: UserId -> [UserId] -> Maybe Text -> [Access] -> Maybe AccessRole -> Maybe Milliseconds -> RoleName -> TestM ResponseLBS
-postConvWithRole u us name a r mtimer role = do
+postConvQualified :: (HasGalley m, MonadIO m, MonadMask m, MonadHttp m) => UserId -> [Qualified UserId] -> Maybe Text -> [Access] -> Maybe AccessRole -> Maybe Milliseconds -> m ResponseLBS
+postConvQualified u us name a r mtimer = postConvWithRoleQualified us u [] name a r mtimer roleNameWireAdmin
+
+postTeamConv :: TeamId -> UserId -> [UserId] -> Maybe Text -> [Access] -> Maybe AccessRole -> Maybe Milliseconds -> TestM ResponseLBS
+postTeamConv tid u us name a r mtimer = do
g <- view tsGalley
- let conv = NewConvUnmanaged $ NewConv us name (Set.fromList a) r Nothing mtimer Nothing role
+ let conv = NewConvUnmanaged $ NewConv us [] name (Set.fromList a) r (Just (ConvTeamInfo tid False)) mtimer Nothing roleNameWireAdmin
+ post $ g . path "/conversations" . zUser u . zConn "conn" . zType "access" . json conv
+
+postConvWithRole :: UserId -> [UserId] -> Maybe Text -> [Access] -> Maybe AccessRole -> Maybe Milliseconds -> RoleName -> TestM ResponseLBS
+postConvWithRole = postConvWithRoleQualified []
+
+postConvWithRoleQualified :: (HasGalley m, MonadIO m, MonadMask m, MonadHttp m) => [Qualified UserId] -> UserId -> [UserId] -> Maybe Text -> [Access] -> Maybe AccessRole -> Maybe Milliseconds -> RoleName -> m ResponseLBS
+postConvWithRoleQualified qualifiedUsers u unqualifiedUsers name a r mtimer role = do
+ g <- viewGalley
+ let conv = NewConvUnmanaged $ NewConv unqualifiedUsers qualifiedUsers name (Set.fromList a) r Nothing mtimer Nothing role
post $ g . path "/conversations" . zUser u . zConn "conn" . zType "access" . json conv
postConvWithReceipt :: UserId -> [UserId] -> Maybe Text -> [Access] -> Maybe AccessRole -> Maybe Milliseconds -> ReceiptMode -> TestM ResponseLBS
postConvWithReceipt u us name a r mtimer rcpt = do
g <- view tsGalley
- let conv = NewConvUnmanaged $ NewConv us name (Set.fromList a) r Nothing mtimer (Just rcpt) roleNameWireAdmin
+ let conv = NewConvUnmanaged $ NewConv us [] name (Set.fromList a) r Nothing mtimer (Just rcpt) roleNameWireAdmin
post $ g . path "/conversations" . zUser u . zConn "conn" . zType "access" . json conv
postSelfConv :: UserId -> TestM ResponseLBS
@@ -530,7 +563,7 @@ postSelfConv u = do
postO2OConv :: UserId -> UserId -> Maybe Text -> TestM ResponseLBS
postO2OConv u1 u2 n = do
g <- view tsGalley
- let conv = NewConvUnmanaged $ NewConv [u2] n mempty Nothing Nothing Nothing Nothing roleNameWireAdmin
+ let conv = NewConvUnmanaged $ NewConv [u2] [] n mempty Nothing Nothing Nothing Nothing roleNameWireAdmin
post $ g . path "/conversations/one2one" . zUser u1 . zConn "conn" . zType "access" . json conv
postConnectConv :: UserId -> UserId -> Text -> Text -> Maybe Text -> TestM ResponseLBS
@@ -590,7 +623,7 @@ postOtrBroadcastMessage req usrs clt rcps = do
-- | 'postOtrBroadcastMessage' with @"report_missing"@ in body.
postOtrBroadcastMessage' :: (Monad m, MonadCatch m, MonadIO m, MonadHttp m, MonadFail m, HasCallStack) => (Request -> Request) -> Maybe [UserId] -> (Request -> Request) -> UserId -> ClientId -> [(UserId, ClientId, Text)] -> m ResponseLBS
-postOtrBroadcastMessage' g reportMissingBody f u d rec = do
+postOtrBroadcastMessage' g reportMissingBody f u d rec =
post $
g
. f
@@ -679,6 +712,20 @@ getConv u c = do
. zConn "conn"
. zType "access"
+getConvQualified :: UserId -> Qualified ConvId -> TestM ResponseLBS
+getConvQualified u convId = do
+ g <- view tsGalley
+ getConvQualified' g u convId
+
+getConvQualified' :: (MonadIO m, MonadHttp m) => GalleyR -> UserId -> Qualified ConvId -> m ResponseLBS
+getConvQualified' g u (Qualified conv domain) = do
+ get $
+ g
+ . paths ["conversations", toByteString' domain, toByteString' conv]
+ . zUser u
+ . zConn "conn"
+ . zType "access"
+
getConvIds :: UserId -> Maybe (Either [ConvId] ConvId) -> Maybe Int32 -> TestM ResponseLBS
getConvIds u r s = do
g <- view tsGalley
@@ -693,11 +740,14 @@ getConvIds u r s = do
postQualifiedMembers :: UserId -> NonEmpty (Qualified UserId) -> ConvId -> TestM ResponseLBS
postQualifiedMembers zusr invitees conv = do
g <- view tsGalley
+ postQualifiedMembers' g zusr invitees conv
+
+postQualifiedMembers' :: (MonadIO m, MonadHttp m) => (Request -> Request) -> UserId -> NonEmpty (Qualified UserId) -> ConvId -> m ResponseLBS
+postQualifiedMembers' g zusr invitees conv = do
let invite = Public.InviteQualified invitees roleNameWireAdmin
post $
g
- -- FUTUREWORK: use an endpoint without /i/ once it's ready.
- . paths ["i", "conversations", toByteString' conv, "members", "v2"]
+ . paths ["conversations", toByteString' conv, "members", "v2"]
. zUser zusr
. zConn "conn"
. zType "access"
@@ -892,7 +942,7 @@ deleteUser u = do
delete (g . path "/i/user" . zUser u) !!! const 200 === statusCode
getTeamQueue :: HasCallStack => UserId -> Maybe NotificationId -> Maybe (Int, Bool) -> Bool -> TestM [(NotificationId, UserId)]
-getTeamQueue zusr msince msize onlyLast = do
+getTeamQueue zusr msince msize onlyLast =
parseEventList . responseJsonUnsafe
<$> ( getTeamQueue' zusr msince (fst <$> msize) onlyLast
[(NotificationId, UserId)]
parseEventList qnl
- | isJust msize && qnl ^. queuedHasMore /= (snd $ fromJust msize) =
+ | isJust msize && qnl ^. queuedHasMore /= snd (fromJust msize) =
error $ "expected has_more: " <> show (snd $ fromJust msize) <> "; but found: " <> show (qnl ^. queuedHasMore)
- | qnl ^. queuedTime /= Nothing =
+ | isJust (qnl ^. queuedTime) =
error $ "expected time: Nothing; but found: " <> show (qnl ^. queuedTime)
| otherwise =
fmap (_2 %~ parseEvt) . mconcat . fmap parseEvts . view queuedNotifications $ qnl
@@ -1006,7 +1056,7 @@ assertConvWithRole r t c s us n mt role = do
assertEqual "self" (Just s) (memId <$> _self)
assertEqual "others" (Just . Set.fromList $ us) (Set.fromList . map (qUnqualified . omQualifiedId) . toList <$> others)
assertEqual "creator is always and admin" (Just roleNameWireAdmin) (memConvRoleName <$> _self)
- assertBool "others role" (all (\x -> x == role) $ fromMaybe (error "Cannot be null") ((map omConvRoleName . toList <$> others)))
+ assertBool "others role" (all (== role) $ maybe (error "Cannot be null") (map omConvRoleName . toList) others)
assertBool "otr muted not false" (Just False == (memOtrMuted <$> _self))
assertBool "otr muted ref not empty" (isNothing (memOtrMutedRef =<< _self))
assertBool "otr archived not false" (Just False == (memOtrArchived <$> _self))
@@ -1018,10 +1068,10 @@ assertConvWithRole r t c s us n mt role = do
_ -> return ()
return cId
-wsAssertOtr :: ConvId -> UserId -> ClientId -> ClientId -> Text -> Notification -> IO ()
+wsAssertOtr :: Qualified ConvId -> Qualified UserId -> ClientId -> ClientId -> Text -> Notification -> IO ()
wsAssertOtr = wsAssertOtr' "data"
-wsAssertOtr' :: Text -> ConvId -> UserId -> ClientId -> ClientId -> Text -> Notification -> IO ()
+wsAssertOtr' :: Text -> Qualified ConvId -> Qualified UserId -> ClientId -> ClientId -> Text -> Notification -> IO ()
wsAssertOtr' evData conv usr from to txt n = do
let e = List1.head (WS.unpackPayload n)
ntfTransient n @?= False
@@ -1031,19 +1081,19 @@ wsAssertOtr' evData conv usr from to txt n = do
evtData e @?= EdOtrMessage (OtrMessage from to txt (Just evData))
-- | This assumes the default role name
-wsAssertMemberJoin :: ConvId -> UserId -> [UserId] -> Notification -> IO ()
-wsAssertMemberJoin conv usr new n = wsAssertMemberJoinWithRole conv usr new roleNameWireAdmin n
+wsAssertMemberJoin :: Qualified ConvId -> Qualified UserId -> [Qualified UserId] -> Notification -> IO ()
+wsAssertMemberJoin conv usr new = wsAssertMemberJoinWithRole conv usr new roleNameWireAdmin
-wsAssertMemberJoinWithRole :: ConvId -> UserId -> [UserId] -> RoleName -> Notification -> IO ()
+wsAssertMemberJoinWithRole :: Qualified ConvId -> Qualified UserId -> [Qualified UserId] -> RoleName -> Notification -> IO ()
wsAssertMemberJoinWithRole conv usr new role n = do
let e = List1.head (WS.unpackPayload n)
ntfTransient n @?= False
evtConv e @?= conv
evtType e @?= MemberJoin
evtFrom e @?= usr
- evtData e @?= EdMembersJoin (SimpleMembers (fmap (\x -> SimpleMember x role) new))
+ evtData e @?= EdMembersJoin (SimpleMembers (fmap (`SimpleMember` role) new))
-wsAssertMemberUpdateWithRole :: ConvId -> UserId -> UserId -> RoleName -> Notification -> IO ()
+wsAssertMemberUpdateWithRole :: Qualified ConvId -> Qualified UserId -> UserId -> RoleName -> Notification -> IO ()
wsAssertMemberUpdateWithRole conv usr target role n = do
let e = List1.head (WS.unpackPayload n)
ntfTransient n @?= False
@@ -1056,7 +1106,7 @@ wsAssertMemberUpdateWithRole conv usr target role n = do
assertEqual "conversation_role" (Just role) (misConvRoleName mis)
x -> assertFailure $ "Unexpected event data: " ++ show x
-wsAssertConvAccessUpdate :: ConvId -> UserId -> ConversationAccessUpdate -> Notification -> IO ()
+wsAssertConvAccessUpdate :: Qualified ConvId -> Qualified UserId -> ConversationAccessUpdate -> Notification -> IO ()
wsAssertConvAccessUpdate conv usr new n = do
let e = List1.head (WS.unpackPayload n)
ntfTransient n @?= False
@@ -1065,7 +1115,7 @@ wsAssertConvAccessUpdate conv usr new n = do
evtFrom e @?= usr
evtData e @?= EdConvAccessUpdate new
-wsAssertConvMessageTimerUpdate :: ConvId -> UserId -> ConversationMessageTimerUpdate -> Notification -> IO ()
+wsAssertConvMessageTimerUpdate :: Qualified ConvId -> Qualified UserId -> ConversationMessageTimerUpdate -> Notification -> IO ()
wsAssertConvMessageTimerUpdate conv usr new n = do
let e = List1.head (WS.unpackPayload n)
ntfTransient n @?= False
@@ -1074,7 +1124,7 @@ wsAssertConvMessageTimerUpdate conv usr new n = do
evtFrom e @?= usr
evtData e @?= EdConvMessageTimerUpdate new
-wsAssertMemberLeave :: ConvId -> UserId -> [UserId] -> Notification -> IO ()
+wsAssertMemberLeave :: Qualified ConvId -> Qualified UserId -> [UserId] -> Notification -> IO ()
wsAssertMemberLeave conv usr old n = do
let e = List1.head (WS.unpackPayload n)
ntfTransient n @?= False
@@ -1152,6 +1202,20 @@ zType = header "Z-Type"
connectUsers :: UserId -> List1 UserId -> TestM ()
connectUsers u us = void $ connectUsersWith expect2xx u us
+-- TODO: it'd be nicer to just take a list here and handle the cases with 0
+-- users differently
+connectLocalQualifiedUsers :: UserId -> List1 (Qualified UserId) -> TestM ()
+connectLocalQualifiedUsers u us = do
+ localDomain <- viewFederationDomain
+ let partitionMap = partitionQualified . toList . toNonEmpty $ us
+ -- FUTUREWORK: connect all users, not just those on the same domain as 'u'
+ case LMap.lookup localDomain partitionMap of
+ Nothing -> err
+ Just [] -> err
+ Just (x : xs) -> void $ connectUsersWith expect2xx u (list1 x xs)
+ where
+ err = liftIO . assertFailure $ "No user on the domain with " ++ show u
+
connectUsersUnchecked ::
UserId ->
List1 UserId ->
@@ -1163,7 +1227,7 @@ connectUsersWith ::
UserId ->
List1 UserId ->
TestM (List1 (Response (Maybe Lazy.ByteString), Response (Maybe Lazy.ByteString)))
-connectUsersWith fn u us = mapM connectTo us
+connectUsersWith fn u = mapM connectTo
where
connectTo v = do
b <- view tsBrig
@@ -1217,6 +1281,27 @@ putConnection from to r = do
where
payload = RequestBodyLBS . encode $ object ["status" .= r]
+-- | A slightly modified copy of 'putConnection' from above.
+putConnectionQualified :: Qualified UserId -> UserId -> Relation -> TestM ResponseLBS
+putConnectionQualified fromQualified to r = do
+ localDomain <- viewFederationDomain
+ let (Qualified from qualifiedDomain) = fromQualified
+ liftIO $
+ assertEqual
+ "The qualified user's domain is not local"
+ localDomain
+ qualifiedDomain
+ brig <- view tsBrig
+ put $
+ brig
+ . paths ["/connections", toByteString' to]
+ . contentJson
+ . body payload
+ . zUser from
+ . zConn "conn"
+ where
+ payload = RequestBodyLBS . encode $ object ["status" .= r]
+
-- | A copy of `assertConnections from Brig integration tests.
assertConnections :: HasCallStack => UserId -> [ConnectionStatus] -> TestM ()
assertConnections u cstat = do
@@ -1224,7 +1309,7 @@ assertConnections u cstat = do
resp <- listConnections brig u show cstat <> " is not a subset of " <> show cstat'
where
status c = ConnectionStatus (ucFrom c) (ucTo c) (ucStatus c)
@@ -1251,7 +1336,7 @@ randomUser' isCreator hasPassword hasEmail = do
["name" .= fromEmail e]
<> ["password" .= defPassword | hasPassword]
<> ["email" .= fromEmail e | hasEmail]
- <> ["team" .= (Team.BindingNewTeam $ Team.newNewTeam (unsafeRange "teamName") (unsafeRange "defaultIcon")) | isCreator]
+ <> ["team" .= Team.BindingNewTeam (Team.newNewTeam (unsafeRange "teamName") (unsafeRange "defaultIcon")) | isCreator]
selfProfile <- responseJsonUnsafe <$> (post (b . path "/i/users" . json p) UserId -> LastPrekey -> TestM ClientId
-randomClient uid lk = do
+randomClient uid lk = randomClientWithCaps uid lk Nothing
+
+randomClientWithCaps :: HasCallStack => UserId -> LastPrekey -> Maybe (Set Client.ClientCapability) -> TestM ClientId
+randomClientWithCaps uid lk caps = do
b <- view tsBrig
resp <-
post (b . paths ["i", "clients", toByteString' uid] . json newClientBody)
@@ -1277,7 +1365,8 @@ randomClient uid lk = do
rStatus = 201
newClientBody =
(newClient cType lk)
- { newClientPassword = Just defPassword
+ { newClientPassword = Just defPassword,
+ newClientCapabilities = caps
}
ensureDeletedState :: HasCallStack => Bool -> UserId -> UserId -> TestM ()
@@ -1305,6 +1394,23 @@ getClients u = do
. zUser u
. zConn "conn"
+getInternalClientsFull :: UserSet -> TestM UserClientsFull
+getInternalClientsFull userSet = do
+ b <- view tsBrig
+ res <-
+ post $
+ b
+ . paths ["i", "clients", "full"]
+ . zConn "conn"
+ . json userSet
+ responseJsonError res
+
+ensureClientCaps :: HasCallStack => UserId -> ClientId -> Client.ClientCapabilityList -> TestM ()
+ensureClientCaps uid cid caps = do
+ UserClientsFull (Map.lookup uid -> (Just clnts)) <- getInternalClientsFull (UserSet $ Set.singleton uid)
+ let [clnt] = filter ((== cid) . clientId) $ Set.toList clnts
+ liftIO $ assertEqual ("ensureClientCaps: " <> show (uid, cid, caps)) (clientCapabilities clnt) caps
+
-- TODO: Refactor, as used also in brig
deleteClient :: UserId -> ClientId -> Maybe PlainTextPassword -> TestM ResponseLBS
deleteClient u c pw = do
@@ -1333,7 +1439,7 @@ isUserDeleted u = do
case responseBody r of
Nothing -> error $ "getStatus: failed to parse response: " ++ show r
Just j -> do
- let st = maybeFromJSON =<< (j ^? key "status")
+ let st = maybeFromJSON =<< j ^? key "status"
let decoded = fromMaybe (error $ "getStatus: failed to decode status" ++ show j) st
return $ decoded == Deleted
where
@@ -1503,10 +1609,10 @@ defCookieLabel = CookieLabel "auth"
-- | This allows you to run requests against a galley instantiated using the given options.
-- Note that ONLY 'galley' calls should occur within the provided action, calls to other
-- services will fail.
-withSettingsOverrides :: MonadIO m => Opts.Opts -> WaiTest.Session a -> m a
-withSettingsOverrides opts action = liftIO $ do
- (galleyApp, _, finalizer) <- Run.mkApp opts
- WaiTest.runSession action galleyApp
+withSettingsOverrides :: (HasGalley m, MonadIO m, MonadMask m) => Opts.Opts -> SessionT m a -> m a
+withSettingsOverrides opts action = do
+ (galleyApp, _, finalizer) <- liftIO $ Run.mkApp opts
+ runSessionT action galleyApp
`finally` liftIO finalizer
waitForMemberDeletion :: UserId -> TeamId -> UserId -> TestM ()
@@ -1535,6 +1641,19 @@ deleteTeamMember g tid owner deletee =
!!! do
const 202 === statusCode
+deleteTeam :: UserId -> TeamId -> TestM ()
+deleteTeam owner tid = do
+ g <- view tsGalley
+ delete
+ ( g
+ . paths ["teams", toByteString' tid]
+ . zUser owner
+ . zConn "conn"
+ . json (newTeamMemberDeleteData (Just defPassword))
+ )
+ !!! do
+ const 202 === statusCode
+
-- (Duplicate of 'Galley.Intra.User.getUsers'.)
getUsersByUid :: [UserId] -> TestM [User]
getUsersByUid = getUsersBy "ids"
@@ -1561,3 +1680,117 @@ getUserProfile zusr uid = do
brig <- view tsBrig
res <- get (brig . zUser zusr . paths ["users", toByteString' uid])
responseJsonError res
+
+upgradeClientToLH :: HasCallStack => UserId -> ClientId -> TestM ()
+upgradeClientToLH zusr cid =
+ putCapabilities zusr cid [ClientSupportsLegalholdImplicitConsent]
+
+putCapabilities :: HasCallStack => UserId -> ClientId -> [ClientCapability] -> TestM ()
+putCapabilities zusr cid caps = do
+ brig <- view tsBrig
+ void $
+ put
+ ( brig
+ . zUser zusr
+ . paths ["clients", toByteString' cid]
+ . json (UpdateClient mempty Nothing Nothing (Just . Set.fromList $ caps))
+ . expect2xx
+ )
+
+getUsersPrekeysClientUnqualified :: HasCallStack => UserId -> UserId -> ClientId -> TestM ResponseLBS
+getUsersPrekeysClientUnqualified zusr uid cid = do
+ brig <- view tsBrig
+ get
+ ( brig
+ . zUser zusr
+ . paths ["users", toByteString' uid, "prekeys", toByteString' cid]
+ )
+
+getUsersPrekeyBundleUnqualified :: HasCallStack => UserId -> UserId -> TestM ResponseLBS
+getUsersPrekeyBundleUnqualified zusr uid = do
+ brig <- view tsBrig
+ get
+ ( brig
+ . zUser zusr
+ . paths ["users", toByteString' uid, "prekeys"]
+ )
+
+getMultiUserPrekeyBundleUnqualified :: HasCallStack => UserId -> UserClients -> TestM ResponseLBS
+getMultiUserPrekeyBundleUnqualified zusr userClients = do
+ brig <- view tsBrig
+ post
+ ( brig
+ . zUser zusr
+ . paths ["users", "prekeys"]
+ . json userClients
+ )
+
+mkProfile :: Qualified UserId -> Name -> UserProfile
+mkProfile quid name =
+ UserProfile
+ { profileQualifiedId = quid,
+ profileName = name,
+ profilePict = noPict,
+ profileAssets = mempty,
+ profileAccentId = defaultAccentId,
+ profileDeleted = False,
+ profileService = Nothing,
+ profileHandle = Nothing,
+ profileLocale = Nothing,
+ profileExpire = Nothing,
+ profileTeam = Nothing,
+ profileEmail = Nothing,
+ profileLegalholdStatus = defUserLegalHoldStatus
+ }
+
+-- mock federator
+
+-- | Run the given action on a temporary galley instance with access to a mock
+-- federator.
+--
+-- The `resp :: FederatedRequest -> a` argument can be used to provide a fake
+-- federator response (of an arbitrary JSON-serialisable type a) for every
+-- expected request.
+withTempMockFederator ::
+ (MonadIO m, ToJSON a, HasGalley m, MonadMask m) =>
+ Opts.Opts ->
+ Domain ->
+ (FederatedRequest -> a) ->
+ SessionT m b ->
+ m (b, Mock.ReceivedRequests)
+withTempMockFederator opts targetDomain resp action = assertRightT
+ . Mock.withTempMockFederator st0 (pure . oresp)
+ $ \st -> lift $ do
+ let opts' =
+ opts & Opts.optFederator
+ ?~ Endpoint "127.0.0.1" (fromIntegral (Mock.serverPort st))
+ withSettingsOverrides opts' action
+ where
+ st0 = Mock.initState targetDomain (Domain "example.com")
+ oresp = OutwardResponseBody . Lazy.toStrict . encode . resp
+
+assertRight :: (MonadIO m, Show a, HasCallStack) => Either a b -> m b
+assertRight = \case
+ Left e -> liftIO $ assertFailure $ "Expected Right, got Left: " <> show e
+ Right x -> pure x
+
+assertRightT :: (MonadIO m, Show a, HasCallStack) => ExceptT a m b -> m b
+assertRightT = assertRight <=< runExceptT
+
+-- | Run a probe several times, until a "good" value materializes or until patience runs out
+-- (after ~2secs).
+-- If all retries were unsuccessful, 'aFewTimes' will return the last obtained value, even
+-- if it does not satisfy the predicate.
+aFewTimes :: TestM a -> (a -> Bool) -> TestM a
+aFewTimes action good = do
+ env <- ask
+ liftIO $
+ retrying
+ (exponentialBackoff 1000 <> limitRetries 11)
+ (\_ -> pure . not . good)
+ (\_ -> runReaderT (runTestM action) env)
+
+aFewTimesAssertBool :: HasCallStack => String -> (a -> Bool) -> TestM a -> TestM ()
+aFewTimesAssertBool msg good action = do
+ result <- aFewTimes action good
+ liftIO $ assertBool msg (good result)
diff --git a/services/galley/test/integration/API/Util/TeamFeature.hs b/services/galley/test/integration/API/Util/TeamFeature.hs
index a16be31d0d4..78edbf72342 100644
--- a/services/galley/test/integration/API/Util/TeamFeature.hs
+++ b/services/galley/test/integration/API/Util/TeamFeature.hs
@@ -20,6 +20,7 @@ module API.Util.TeamFeature where
import API.Util (zUser)
import qualified API.Util as Util
import Bilge
+import qualified Bilge.TestSession as BilgeTest
import Control.Lens (view, (.~))
import Data.Aeson (ToJSON)
import Data.ByteString.Conversion (toByteString')
@@ -27,11 +28,10 @@ import Data.Id (TeamId, UserId)
import Galley.Options (optSettings, setFeatureFlags)
import Galley.Types.Teams
import Imports
-import qualified Network.Wai.Test as WaiTest
import TestSetup
import qualified Wire.API.Team.Feature as Public
-withCustomSearchFeature :: FeatureTeamSearchVisibility -> WaiTest.Session () -> TestM ()
+withCustomSearchFeature :: FeatureTeamSearchVisibility -> BilgeTest.SessionT TestM () -> TestM ()
withCustomSearchFeature flag action = do
opts <- view tsGConf
let opts' = opts & optSettings . setFeatureFlags . flagTeamSearchVisibility .~ flag
diff --git a/services/galley/test/integration/TestHelpers.hs b/services/galley/test/integration/TestHelpers.hs
index dae7fe0ec25..726b548367c 100644
--- a/services/galley/test/integration/TestHelpers.hs
+++ b/services/galley/test/integration/TestHelpers.hs
@@ -29,6 +29,7 @@ import Imports
import Test.Tasty (TestName, TestTree)
import Test.Tasty.HUnit (Assertion, assertBool, testCase)
import TestSetup
+import UnliftIO.Exception (finally)
test :: IO TestSetup -> TestName -> TestM a -> TestTree
test s n h = testCase n runTest
@@ -48,7 +49,9 @@ test s n h = testCase n runTest
runTest :: Assertion
runTest = do
setup <- s
- void . flip runReaderT setup . runTestM $ h >> assertClean
+ -- this `finally` doesnt seem to help. if there is an exception in a test
+ -- the other tests still see remaining messages on the queue
+ void . flip runReaderT setup . runTestM $ (ensureQueueEmpty >> h) `finally` assertClean
viewFederationDomain :: TestM Domain
viewFederationDomain = view (tsGConf . optSettings . setFederationDomain)
diff --git a/services/galley/test/unit/Main.hs b/services/galley/test/unit/Main.hs
index 70ebebaeb99..fe750244bfa 100644
--- a/services/galley/test/unit/Main.hs
+++ b/services/galley/test/unit/Main.hs
@@ -23,13 +23,14 @@ where
import Imports
import qualified Test.Galley.API
import qualified Test.Galley.Intra.User
+import qualified Test.Galley.Roundtrip
import Test.Tasty
main :: IO ()
main =
- defaultMain $
- testGroup
- "Tests"
- [ Test.Galley.API.tests,
- Test.Galley.Intra.User.tests
+ defaultMain . testGroup "Tests"
+ =<< sequence
+ [ pure Test.Galley.API.tests,
+ pure Test.Galley.Intra.User.tests,
+ Test.Galley.Roundtrip.tests
]
diff --git a/services/galley/test/unit/Test/Galley/Roundtrip.hs b/services/galley/test/unit/Test/Galley/Roundtrip.hs
new file mode 100644
index 00000000000..66ccac36f75
--- /dev/null
+++ b/services/galley/test/unit/Test/Galley/Roundtrip.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE DeriveAnyClass #-}
+
+-- This file is part of the Wire Server implementation.
+--
+-- Copyright (C) 2020 Wire Swiss GmbH
+--
+-- This program is free software: you can redistribute it and/or modify it under
+-- the terms of the GNU Affero General Public License as published by the Free
+-- Software Foundation, either version 3 of the License, or (at your option) any
+-- later version.
+--
+-- This program is distributed in the hope that it will be useful, but WITHOUT
+-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+-- details.
+--
+-- You should have received a copy of the GNU Affero General Public License along
+-- with this program. If not, see .
+
+module Test.Galley.Roundtrip
+ ( tests,
+ )
+where
+
+import Data.Proxy (Proxy (Proxy))
+import Imports
+import Servant.Swagger (validateEveryToJSON)
+import Test.Tasty (TestTree)
+import Test.Tasty.Hspec (testSpec)
+import qualified Wire.API.Routes.Public.LegalHold as LegalHoldAPI
+
+tests :: IO TestTree
+tests = testSpec "Roundtrip" $ validateEveryToJSON (Proxy @LegalHoldAPI.ServantAPI)
diff --git a/services/gundeck/src/Gundeck/API/Error.hs b/services/gundeck/src/Gundeck/API/Error.hs
index 936e8c62ae3..e6c5d9f657f 100644
--- a/services/gundeck/src/Gundeck/API/Error.hs
+++ b/services/gundeck/src/Gundeck/API/Error.hs
@@ -19,13 +19,13 @@ module Gundeck.API.Error where
import Data.Text.Lazy (Text)
import Network.HTTP.Types.Status
-import Network.Wai.Utilities.Error (Error (..))
+import Network.Wai.Utilities.Error (Error (..), mkError)
notificationNotFound :: Error
-notificationNotFound = Error status404 "not-found" "Some notifications not found."
+notificationNotFound = mkError status404 "not-found" "Some notifications not found."
clientError :: Text -> Error
-clientError = Error status400 "client-error"
+clientError = mkError status400 "client-error"
invalidNotificationId :: Error
invalidNotificationId = clientError "Notification ID must be a version 1 UUID"
diff --git a/services/gundeck/src/Gundeck/API/Public.hs b/services/gundeck/src/Gundeck/API/Public.hs
index 273dc640e0f..49741400e27 100644
--- a/services/gundeck/src/Gundeck/API/Public.hs
+++ b/services/gundeck/src/Gundeck/API/Public.hs
@@ -167,22 +167,22 @@ success t =
invalidToken :: Response
invalidToken =
- json (Error status400 "invalid-token" "Invalid push token")
+ json (mkError status400 "invalid-token" "Invalid push token")
& setStatus status404
snsThreadBudgetReached :: Response
snsThreadBudgetReached =
- json (Error status400 "sns-thread-budget-reached" "Too many concurrent calls to SNS; is SNS down?")
+ json (mkError status400 "sns-thread-budget-reached" "Too many concurrent calls to SNS; is SNS down?")
& setStatus status413
tokenTooLong :: Response
tokenTooLong =
- json (Error status400 "token-too-long" "Push token length must be < 8192 for GCM or 400 for APNS")
+ json (mkError status400 "token-too-long" "Push token length must be < 8192 for GCM or 400 for APNS")
& setStatus status413
metadataTooLong :: Response
metadataTooLong =
- json (Error status400 "metadata-too-long" "Tried to add token to endpoint resulting in metadata length > 2048")
+ json (mkError status400 "metadata-too-long" "Tried to add token to endpoint resulting in metadata length > 2048")
& setStatus status413
notFound :: Response
diff --git a/services/gundeck/src/Gundeck/Monad.hs b/services/gundeck/src/Gundeck/Monad.hs
index 510d7e3e414..22851a12d81 100644
--- a/services/gundeck/src/Gundeck/Monad.hs
+++ b/services/gundeck/src/Gundeck/Monad.hs
@@ -106,7 +106,7 @@ lookupReqId = maybe def RequestId . lookup requestIdName . requestHeaders
{-# INLINE lookupReqId #-}
fromJsonBody :: FromJSON a => JsonRequest a -> Gundeck a
-fromJsonBody r = exceptT (throwM . Error status400 "bad-request") return (parseBody r)
+fromJsonBody r = exceptT (throwM . mkError status400 "bad-request") return (parseBody r)
{-# INLINE fromJsonBody #-}
ifNothing :: Error -> Maybe a -> Gundeck a
diff --git a/services/gundeck/src/Gundeck/Push.hs b/services/gundeck/src/Gundeck/Push.hs
index d44f667ab41..b45922d3e2d 100644
--- a/services/gundeck/src/Gundeck/Push.hs
+++ b/services/gundeck/src/Gundeck/Push.hs
@@ -80,7 +80,7 @@ push ps = do
Right () -> return ()
Left exs -> do
forM_ exs $ Log.err . msg . (val "Push failed: " +++) . show
- throwM (Error status500 "server-error" "Server Error")
+ throwM (mkError status500 "server-error" "Server Error")
-- | Abstract over all effects in 'pushAll' (for unit testing).
class MonadThrow m => MonadPushAll m where
@@ -452,7 +452,7 @@ addToken uid cid newtok = mpaRunWithBudget 1 AddTokenNoBudget $ do
update n t arn = do
when (n >= 3) $ do
Log.err $ msg (val "AWS SNS inconsistency w.r.t. " +++ toText arn)
- throwM (Error status500 "server-error" "Server Error")
+ throwM (mkError status500 "server-error" "Server Error")
aws <- view awsEnv
ept <- Aws.execute aws (Aws.lookupEndpoint arn)
case ept of
@@ -497,7 +497,7 @@ updateEndpoint uid t arn e = do
env <- view awsEnv
unless (equalTransport && equalApp) $ do
Log.err $ logMessage uid arn (t ^. token) "Transport or app mismatch"
- throwM $ Error status500 "server-error" "Server Error"
+ throwM $ mkError status500 "server-error" "Server Error"
Log.info $ logMessage uid arn (t ^. token) "Upserting push token."
let users = Set.insert uid (e ^. endpointUsers)
Aws.execute env $ Aws.updateEndpoint users (t ^. token) arn
@@ -514,7 +514,7 @@ deleteToken :: UserId -> Token -> Gundeck ()
deleteToken uid tok = do
as <- filter (\x -> x ^. addrToken == tok) <$> Data.lookup uid Data.Quorum
when (null as) $
- throwM (Error status404 "not-found" "Push token not found")
+ throwM (mkError status404 "not-found" "Push token not found")
Native.deleteTokens as Nothing
listTokens :: UserId -> Gundeck PushTokenList
diff --git a/services/gundeck/src/Gundeck/Util.hs b/services/gundeck/src/Gundeck/Util.hs
index aaf53b61769..7ebb157a6bf 100644
--- a/services/gundeck/src/Gundeck/Util.hs
+++ b/services/gundeck/src/Gundeck/Util.hs
@@ -38,7 +38,7 @@ mkNotificationId = do
where
x10 = limitRetries 10 <> exponentialBackoff 10
fun = const (return . isNothing)
- err = Error status500 "internal-error" "unable to generate notification ID"
+ err = mkError status500 "internal-error" "unable to generate notification ID"
mapAsync ::
(MonadUnliftIO m, Traversable t) =>
diff --git a/services/nginz/Dockerfile b/services/nginz/Dockerfile
index 7835e09554f..d2abe28321c 100644
--- a/services/nginz/Dockerfile
+++ b/services/nginz/Dockerfile
@@ -41,11 +41,6 @@ ENV CONFIG --prefix=/etc/nginx \
--add-module=/src/third_party/headers-more-nginx-module \
--add-module=/src/third_party/nginx-module-vts
-# extra build dependencies needed for libzauth/nginx-xauth-module
-RUN apk add --no-cache --virtual .build-deps \
- libsodium-dev \
- llvm-libunwind-dev
-
################# similar block as upstream ########################################
# see https://github.com/nginxinc/docker-nginx/blob/master/stable/alpine/Dockerfile
# This uses dockerfile logic from before 1.16
@@ -53,7 +48,9 @@ RUN apk add --no-cache --virtual .build-deps \
ENV NGINX_VERSION 1.16.1
-RUN apk update && apk add --virtual .build-deps \
+RUN apk update
+
+RUN apk add -vv --virtual .build-deps \
libsodium-dev \
llvm-libunwind-dev \
gcc \
@@ -69,6 +66,7 @@ RUN apk update && apk add --virtual .build-deps \
gd-dev \
geoip-dev
+# This line checks whether the 'apk add' succeeded, sometimes it doesn't work.
RUN curl -h
RUN set -x \
diff --git a/services/proxy/src/Proxy/API/Public.hs b/services/proxy/src/Proxy/API/Public.hs
index 3b7c0a5b058..1af67b77152 100644
--- a/services/proxy/src/Proxy/API/Public.hs
+++ b/services/proxy/src/Proxy/API/Public.hs
@@ -219,10 +219,10 @@ isError :: Status -> Bool
isError (statusCode -> c) = c < 200 || c > 299
error500 :: Error
-error500 = Error status500 "internal-error" "Internal server error"
+error500 = mkError status500 "internal-error" "Internal server error"
error502 :: Error
-error502 = Error status502 "bad-gateway" "Bad gateway"
+error502 = mkError status502 "bad-gateway" "Bad gateway"
newtype S = S Status
diff --git a/services/spar/src/Spar/App.hs b/services/spar/src/Spar/App.hs
index a5fa91ec65c..340c8d5cf3a 100644
--- a/services/spar/src/Spar/App.hs
+++ b/services/spar/src/Spar/App.hs
@@ -572,7 +572,7 @@ errorPage err inputs mcky =
}
where
werr = either forceWai id $ renderSparError err
- forceWai ServerError {..} = Wai.Error (Http.Status errHTTPCode "") (cs errReasonPhrase) (cs errBody)
+ forceWai ServerError {..} = Wai.mkError (Http.Status errHTTPCode "") (cs errReasonPhrase) (cs errBody)
errbody :: [LT]
errbody =
[ "",
diff --git a/services/spar/src/Spar/Error.hs b/services/spar/src/Spar/Error.hs
index 480a040c256..e9920b9be23 100644
--- a/services/spar/src/Spar/Error.hs
+++ b/services/spar/src/Spar/Error.hs
@@ -110,13 +110,13 @@ sparToServerErrorWithLogging logger err = do
servantToWaiError :: ServerError -> Wai.Error
servantToWaiError (ServerError code phrase body _headers) =
- Wai.Error (Status code (cs phrase)) (cs phrase) (cs body)
+ Wai.mkError (Status code (cs phrase)) (cs phrase) (cs body)
sparToServerError :: SparError -> ServerError
sparToServerError = either id waiToServant . renderSparError
waiToServant :: Wai.Error -> ServerError
-waiToServant waierr@(Wai.Error status label _) =
+waiToServant waierr@(Wai.Error status label _ _) =
ServerError
{ errHTTPCode = statusCode status,
errReasonPhrase = cs label,
@@ -131,52 +131,52 @@ renderSparErrorWithLogging logger err = do
pure errPossiblyWai
renderSparError :: SparError -> Either ServerError Wai.Error
-renderSparError (SAML.CustomError SparNoSuchRequest) = Right $ Wai.Error status500 "server-error" "AuthRequest seems to have disappeared (could not find verdict format)."
-renderSparError (SAML.CustomError (SparNoRequestRefInResponse msg)) = Right $ Wai.Error status400 "server-error-unsupported-saml" ("The IdP needs to provide an InResponseTo attribute in the assertion: " <> msg)
-renderSparError (SAML.CustomError (SparCouldNotSubstituteSuccessURI msg)) = Right $ Wai.Error status400 "bad-success-redirect" ("re-parsing the substituted URI failed: " <> msg)
-renderSparError (SAML.CustomError (SparCouldNotSubstituteFailureURI msg)) = Right $ Wai.Error status400 "bad-failure-redirect" ("re-parsing the substituted URI failed: " <> msg)
-renderSparError (SAML.CustomError (SparBadInitiateLoginQueryParams label)) = Right $ Wai.Error status400 label label
-renderSparError (SAML.CustomError (SparBindFromWrongOrNoTeam msg)) = Right $ Wai.Error status403 "bad-team" ("Forbidden: wrong user team " <> msg)
-renderSparError (SAML.CustomError (SparBindFromBadAccountStatus msg)) = Right $ Wai.Error status403 "bad-account-status" ("Forbidden: user has account status " <> msg <> "; only Active, PendingInvitation are supported")
-renderSparError (SAML.CustomError SparBindUserRefTaken) = Right $ Wai.Error status403 "subject-id-taken" "Forbidden: SubjectID is used by another wire user. If you have an old user bound to this IdP, unbind or delete that user."
-renderSparError (SAML.CustomError (SparBadUserName msg)) = Right $ Wai.Error status400 "bad-username" ("Bad UserName in SAML response, except len [1, 128]: " <> msg)
-renderSparError (SAML.CustomError (SparCannotCreateUsersOnReplacedIdP replacingIdPId)) = Right $ Wai.Error status400 "cannont-provision-on-replaced-idp" ("This IdP has been replaced, users can only be auto-provisioned on the replacing IdP " <> replacingIdPId)
+renderSparError (SAML.CustomError SparNoSuchRequest) = Right $ Wai.mkError status500 "server-error" "AuthRequest seems to have disappeared (could not find verdict format)."
+renderSparError (SAML.CustomError (SparNoRequestRefInResponse msg)) = Right $ Wai.mkError status400 "server-error-unsupported-saml" ("The IdP needs to provide an InResponseTo attribute in the assertion: " <> msg)
+renderSparError (SAML.CustomError (SparCouldNotSubstituteSuccessURI msg)) = Right $ Wai.mkError status400 "bad-success-redirect" ("re-parsing the substituted URI failed: " <> msg)
+renderSparError (SAML.CustomError (SparCouldNotSubstituteFailureURI msg)) = Right $ Wai.mkError status400 "bad-failure-redirect" ("re-parsing the substituted URI failed: " <> msg)
+renderSparError (SAML.CustomError (SparBadInitiateLoginQueryParams label)) = Right $ Wai.mkError status400 label label
+renderSparError (SAML.CustomError (SparBindFromWrongOrNoTeam msg)) = Right $ Wai.mkError status403 "bad-team" ("Forbidden: wrong user team " <> msg)
+renderSparError (SAML.CustomError (SparBindFromBadAccountStatus msg)) = Right $ Wai.mkError status403 "bad-account-status" ("Forbidden: user has account status " <> msg <> "; only Active, PendingInvitation are supported")
+renderSparError (SAML.CustomError SparBindUserRefTaken) = Right $ Wai.mkError status403 "subject-id-taken" "Forbidden: SubjectID is used by another wire user. If you have an old user bound to this IdP, unbind or delete that user."
+renderSparError (SAML.CustomError (SparBadUserName msg)) = Right $ Wai.mkError status400 "bad-username" ("Bad UserName in SAML response, except len [1, 128]: " <> msg)
+renderSparError (SAML.CustomError (SparCannotCreateUsersOnReplacedIdP replacingIdPId)) = Right $ Wai.mkError status400 "cannont-provision-on-replaced-idp" ("This IdP has been replaced, users can only be auto-provisioned on the replacing IdP " <> replacingIdPId)
-- RFC-specific errors
-renderSparError (SAML.CustomError (SparCouldNotParseRfcResponse service msg)) = Right $ Wai.Error status502 "bad-upstream" ("Could not parse " <> service <> " response body: " <> msg)
-renderSparError (SAML.CustomError SparReAuthRequired) = Right $ Wai.Error status403 "access-denied" "This operation requires reauthentication."
-renderSparError (SAML.CustomError SparCouldNotRetrieveCookie) = Right $ Wai.Error status502 "bad-upstream" "Unable to get a cookie from an upstream server."
-renderSparError (SAML.CustomError (SparCassandraError msg)) = Right $ Wai.Error status500 "server-error" msg -- TODO: should we be more specific here and make it 'db-error'?
-renderSparError (SAML.CustomError (SparCassandraTTLError ttlerr)) = Right $ Wai.Error status400 "ttl-error" (cs $ show ttlerr)
-renderSparError (SAML.UnknownIdP msg) = Right $ Wai.Error status404 "not-found" ("IdP not found: " <> msg)
-renderSparError (SAML.Forbidden msg) = Right $ Wai.Error status403 "forbidden" ("Forbidden: " <> msg)
-renderSparError (SAML.BadSamlResponseBase64Error msg) = Right $ Wai.Error status400 "bad-response-encoding" ("Bad response: base64 error: " <> cs msg)
-renderSparError (SAML.BadSamlResponseXmlError msg) = Right $ Wai.Error status400 "bad-response-xml" ("Bad response: XML parse error: " <> cs msg)
-renderSparError (SAML.BadSamlResponseSamlError msg) = Right $ Wai.Error status400 "bad-response-saml" ("Bad response: SAML parse error: " <> cs msg)
-renderSparError SAML.BadSamlResponseFormFieldMissing = Right $ Wai.Error status400 "bad-response-saml" ("Bad response: SAMLResponse form field missing from HTTP body")
-renderSparError SAML.BadSamlResponseIssuerMissing = Right $ Wai.Error status400 "bad-response-saml" ("Bad response: no Issuer in AuthnResponse")
-renderSparError SAML.BadSamlResponseNoAssertions = Right $ Wai.Error status400 "bad-response-saml" ("Bad response: no assertions in AuthnResponse")
-renderSparError SAML.BadSamlResponseAssertionWithoutID = Right $ Wai.Error status400 "bad-response-saml" ("Bad response: assertion without ID")
-renderSparError (SAML.BadSamlResponseInvalidSignature msg) = Right $ Wai.Error status400 "bad-response-signature" (cs msg)
-renderSparError (SAML.CustomError SparIdPNotFound) = Right $ Wai.Error status404 "not-found" "Could not find IdP."
-renderSparError (SAML.CustomError SparMissingZUsr) = Right $ Wai.Error status400 "client-error" "[header] 'Z-User' required"
-renderSparError (SAML.CustomError SparNotInTeam) = Right $ Wai.Error status403 "no-team-member" "Requesting user is not a team member or not a member of this team."
-renderSparError (SAML.CustomError (SparNoPermission perm)) = Right $ Wai.Error status403 "insufficient-permissions" ("You need permission " <> cs perm <> ".")
-renderSparError (SAML.CustomError SparSSODisabled) = Right $ Wai.Error status403 "sso-disabled" "Please ask customer support to enable this feature for your team."
-renderSparError (SAML.CustomError SparInitLoginWithAuth) = Right $ Wai.Error status403 "login-with-auth" "This end-point is only for login, not binding."
-renderSparError (SAML.CustomError SparInitBindWithoutAuth) = Right $ Wai.Error status403 "bind-without-auth" "This end-point is only for binding, not login."
-renderSparError SAML.UnknownError = Right $ Wai.Error status500 "server-error" "Unknown server error."
-renderSparError (SAML.BadServerConfig msg) = Right $ Wai.Error status500 "server-error" ("Error in server config: " <> msg)
-renderSparError (SAML.InvalidCert msg) = Right $ Wai.Error status500 "invalid-certificate" ("Error in idp certificate: " <> msg)
+renderSparError (SAML.CustomError (SparCouldNotParseRfcResponse service msg)) = Right $ Wai.mkError status502 "bad-upstream" ("Could not parse " <> service <> " response body: " <> msg)
+renderSparError (SAML.CustomError SparReAuthRequired) = Right $ Wai.mkError status403 "access-denied" "This operation requires reauthentication."
+renderSparError (SAML.CustomError SparCouldNotRetrieveCookie) = Right $ Wai.mkError status502 "bad-upstream" "Unable to get a cookie from an upstream server."
+renderSparError (SAML.CustomError (SparCassandraError msg)) = Right $ Wai.mkError status500 "server-error" msg -- TODO: should we be more specific here and make it 'db-error'?
+renderSparError (SAML.CustomError (SparCassandraTTLError ttlerr)) = Right $ Wai.mkError status400 "ttl-error" (cs $ show ttlerr)
+renderSparError (SAML.UnknownIdP msg) = Right $ Wai.mkError status404 "not-found" ("IdP not found: " <> msg)
+renderSparError (SAML.Forbidden msg) = Right $ Wai.mkError status403 "forbidden" ("Forbidden: " <> msg)
+renderSparError (SAML.BadSamlResponseBase64Error msg) = Right $ Wai.mkError status400 "bad-response-encoding" ("Bad response: base64 error: " <> cs msg)
+renderSparError (SAML.BadSamlResponseXmlError msg) = Right $ Wai.mkError status400 "bad-response-xml" ("Bad response: XML parse error: " <> cs msg)
+renderSparError (SAML.BadSamlResponseSamlError msg) = Right $ Wai.mkError status400 "bad-response-saml" ("Bad response: SAML parse error: " <> cs msg)
+renderSparError SAML.BadSamlResponseFormFieldMissing = Right $ Wai.mkError status400 "bad-response-saml" ("Bad response: SAMLResponse form field missing from HTTP body")
+renderSparError SAML.BadSamlResponseIssuerMissing = Right $ Wai.mkError status400 "bad-response-saml" ("Bad response: no Issuer in AuthnResponse")
+renderSparError SAML.BadSamlResponseNoAssertions = Right $ Wai.mkError status400 "bad-response-saml" ("Bad response: no assertions in AuthnResponse")
+renderSparError SAML.BadSamlResponseAssertionWithoutID = Right $ Wai.mkError status400 "bad-response-saml" ("Bad response: assertion without ID")
+renderSparError (SAML.BadSamlResponseInvalidSignature msg) = Right $ Wai.mkError status400 "bad-response-signature" (cs msg)
+renderSparError (SAML.CustomError SparIdPNotFound) = Right $ Wai.mkError status404 "not-found" "Could not find IdP."
+renderSparError (SAML.CustomError SparMissingZUsr) = Right $ Wai.mkError status400 "client-error" "[header] 'Z-User' required"
+renderSparError (SAML.CustomError SparNotInTeam) = Right $ Wai.mkError status403 "no-team-member" "Requesting user is not a team member or not a member of this team."
+renderSparError (SAML.CustomError (SparNoPermission perm)) = Right $ Wai.mkError status403 "insufficient-permissions" ("You need permission " <> cs perm <> ".")
+renderSparError (SAML.CustomError SparSSODisabled) = Right $ Wai.mkError status403 "sso-disabled" "Please ask customer support to enable this feature for your team."
+renderSparError (SAML.CustomError SparInitLoginWithAuth) = Right $ Wai.mkError status403 "login-with-auth" "This end-point is only for login, not binding."
+renderSparError (SAML.CustomError SparInitBindWithoutAuth) = Right $ Wai.mkError status403 "bind-without-auth" "This end-point is only for binding, not login."
+renderSparError SAML.UnknownError = Right $ Wai.mkError status500 "server-error" "Unknown server error."
+renderSparError (SAML.BadServerConfig msg) = Right $ Wai.mkError status500 "server-error" ("Error in server config: " <> msg)
+renderSparError (SAML.InvalidCert msg) = Right $ Wai.mkError status500 "invalid-certificate" ("Error in idp certificate: " <> msg)
-- Errors related to IdP creation
-renderSparError (SAML.CustomError (SparNewIdPBadMetadata msg)) = Right $ Wai.Error status400 "invalid-metadata" msg
-renderSparError (SAML.CustomError SparNewIdPPubkeyMismatch) = Right $ Wai.Error status400 "key-mismatch" "public keys in body, metadata do not match"
-renderSparError (SAML.CustomError SparNewIdPAlreadyInUse) = Right $ Wai.Error status400 "idp-already-in-use" "an idp issuer can only be used within one team"
-renderSparError (SAML.CustomError (SparNewIdPWantHttps msg)) = Right $ Wai.Error status400 "idp-must-be-https" ("an idp request uri must be https, not http or other: " <> msg)
-renderSparError (SAML.CustomError SparIdPHasBoundUsers) = Right $ Wai.Error status412 "idp-has-bound-users" "an idp can only be deleted if it is empty"
-renderSparError (SAML.CustomError SparIdPIssuerInUse) = Right $ Wai.Error status400 "idp-issuer-in-use" "The issuer of your IdP is already in use. Remove the entry in the team that uses it, or construct a new IdP issuer."
+renderSparError (SAML.CustomError (SparNewIdPBadMetadata msg)) = Right $ Wai.mkError status400 "invalid-metadata" msg
+renderSparError (SAML.CustomError SparNewIdPPubkeyMismatch) = Right $ Wai.mkError status400 "key-mismatch" "public keys in body, metadata do not match"
+renderSparError (SAML.CustomError SparNewIdPAlreadyInUse) = Right $ Wai.mkError status400 "idp-already-in-use" "an idp issuer can only be used within one team"
+renderSparError (SAML.CustomError (SparNewIdPWantHttps msg)) = Right $ Wai.mkError status400 "idp-must-be-https" ("an idp request uri must be https, not http or other: " <> msg)
+renderSparError (SAML.CustomError SparIdPHasBoundUsers) = Right $ Wai.mkError status412 "idp-has-bound-users" "an idp can only be deleted if it is empty"
+renderSparError (SAML.CustomError SparIdPIssuerInUse) = Right $ Wai.mkError status400 "idp-issuer-in-use" "The issuer of your IdP is already in use. Remove the entry in the team that uses it, or construct a new IdP issuer."
-- Errors related to provisioning
-renderSparError (SAML.CustomError (SparProvisioningMoreThanOneIdP msg)) = Right $ Wai.Error status400 "more-than-one-idp" ("Team can have at most one IdP configured: " <> msg)
-renderSparError (SAML.CustomError SparProvisioningTokenLimitReached) = Right $ Wai.Error status403 "token-limit-reached" "The limit of provisioning tokens per team has been reached"
+renderSparError (SAML.CustomError (SparProvisioningMoreThanOneIdP msg)) = Right $ Wai.mkError status400 "more-than-one-idp" ("Team can have at most one IdP configured: " <> msg)
+renderSparError (SAML.CustomError SparProvisioningTokenLimitReached) = Right $ Wai.mkError status403 "token-limit-reached" "The limit of provisioning tokens per team has been reached"
-- SCIM errors
renderSparError (SAML.CustomError (SparScimError err)) = Left $ Scim.scimToServerError err
-- Other
diff --git a/tools/stern/src/Stern/API.hs b/tools/stern/src/Stern/API.hs
index aa6accca451..da37f3dc6f9 100644
--- a/tools/stern/src/Stern/API.hs
+++ b/tools/stern/src/Stern/API.hs
@@ -505,13 +505,13 @@ revokeIdentity emailOrPhone = Intra.revokeIdentity emailOrPhone >> return empty
changeEmail :: JSON ::: UserId ::: JsonRequest EmailUpdate -> Handler Response
changeEmail (_ ::: uid ::: req) = do
- upd <- parseBody req !>> Error status400 "client-error"
+ upd <- parseBody req !>> mkError status400 "client-error"
Intra.changeEmail uid upd
return empty
changePhone :: JSON ::: UserId ::: JsonRequest PhoneUpdate -> Handler Response
changePhone (_ ::: uid ::: req) = do
- upd <- parseBody req !>> Error status400 "client-error"
+ upd <- parseBody req !>> mkError status400 "client-error"
Intra.changePhone uid upd
return empty
@@ -525,7 +525,7 @@ deleteUser (uid ::: emailOrPhone) = do
info $ userMsg uid . msg (val "Deleting account")
void $ Intra.deleteAccount uid
return empty
- else throwE $ Error status400 "match-error" "email or phone did not match UserId"
+ else throwE $ mkError status400 "match-error" "email or phone did not match UserId"
_ -> return $ setStatus status404 empty
where
checkUUID u = userId u == uid
@@ -542,10 +542,10 @@ deleteTeam (givenTid ::: email) = do
void $ Intra.deleteBindingTeam givenTid
return $ setStatus status202 empty
where
- handleNoUser = ifNothing (Error status404 "no-user" "No such user with that email")
- handleNoTeam = ifNothing (Error status404 "no-binding-team" "No such binding team")
- wrongMemberCount = Error status403 "wrong-member-count" "Only teams with 1 user can be deleted"
- bindingTeamMismatch = Error status404 "binding-team-mismatch" "Binding team mismatch"
+ handleNoUser = ifNothing (mkError status404 "no-user" "No such user with that email")
+ handleNoTeam = ifNothing (mkError status404 "no-binding-team" "No such binding team")
+ wrongMemberCount = mkError status403 "wrong-member-count" "Only teams with 1 user can be deleted"
+ bindingTeamMismatch = mkError status404 "binding-team-mismatch" "Binding team mismatch"
isUserKeyBlacklisted :: Either Email Phone -> Handler Response
isUserKeyBlacklisted emailOrPhone = do
@@ -597,7 +597,7 @@ setTeamFeatureFlagH ::
TeamId ::: JsonRequest (Public.TeamFeatureStatus a) ::: JSON ->
Handler Response
setTeamFeatureFlagH (tid ::: req ::: _) = do
- status :: Public.TeamFeatureStatus a <- parseBody req !>> Error status400 "client-error"
+ status :: Public.TeamFeatureStatus a <- parseBody req !>> mkError status400 "client-error"
empty <$ Intra.setTeamFeatureFlag @a tid status
getTeamFeatureFlagNoConfigH ::
@@ -614,7 +614,7 @@ setTeamFeatureNoConfigFlagH (tid ::: featureName ::: statusValue) =
setSearchVisibility :: JSON ::: TeamId ::: JsonRequest Team.TeamSearchVisibility -> Handler Response
setSearchVisibility (_ ::: tid ::: req) = do
- status :: Team.TeamSearchVisibility <- parseBody req !>> Error status400 "client-error"
+ status :: Team.TeamSearchVisibility <- parseBody req !>> mkError status400 "client-error"
liftM json $ Intra.setSearchVisibility tid status
getTeamBillingInfo :: TeamId -> Handler Response
@@ -622,17 +622,17 @@ getTeamBillingInfo tid = do
ti <- Intra.getTeamBillingInfo tid
case ti of
Just t -> return $ json t
- Nothing -> throwE (Error status404 "no-team" "No team or no billing info for team")
+ Nothing -> throwE (mkError status404 "no-team" "No team or no billing info for team")
updateTeamBillingInfo :: JSON ::: TeamId ::: JsonRequest TeamBillingInfoUpdate -> Handler Response
updateTeamBillingInfo (_ ::: tid ::: req) = do
- update <- parseBody req !>> Error status400 "client-error"
+ update <- parseBody req !>> mkError status400 "client-error"
current <- Intra.getTeamBillingInfo tid >>= handleNoTeam
let changes = parse update current
Intra.setTeamBillingInfo tid changes
liftM json $ Intra.getTeamBillingInfo tid
where
- handleNoTeam = ifNothing (Error status404 "no-team" "No team or no billing info for team")
+ handleNoTeam = ifNothing (mkError status404 "no-team" "No team or no billing info for team")
parse :: TeamBillingInfoUpdate -> TeamBillingInfo -> TeamBillingInfo
parse TeamBillingInfoUpdate {..} tbi =
tbi
@@ -648,10 +648,10 @@ updateTeamBillingInfo (_ ::: tid ::: req) = do
setTeamBillingInfo :: JSON ::: TeamId ::: JsonRequest TeamBillingInfo -> Handler Response
setTeamBillingInfo (_ ::: tid ::: req) = do
- billingInfo <- parseBody req !>> Error status400 "client-error"
+ billingInfo <- parseBody req !>> mkError status400 "client-error"
current <- Intra.getTeamBillingInfo tid
when (isJust current) $
- throwE (Error status403 "existing-team" "Cannot set info on existing team, use update instead")
+ throwE (mkError status403 "existing-team" "Cannot set info on existing team, use update instead")
Intra.setTeamBillingInfo tid billingInfo
getTeamBillingInfo tid
@@ -661,8 +661,8 @@ getTeamInfoByMemberEmail e = do
tid <- (Intra.getUserBindingTeam . userId . accountUser $ acc) >>= handleTeam
liftM json $ Intra.getTeamInfo tid
where
- handleUser = ifNothing (Error status404 "no-user" "No such user with that email")
- handleTeam = ifNothing (Error status404 "no-binding-team" "No such binding team")
+ handleUser = ifNothing (mkError status404 "no-user" "No such user with that email")
+ handleTeam = ifNothing (mkError status404 "no-binding-team" "No such binding team")
getTeamInvoice :: TeamId ::: InvoiceId ::: JSON -> Handler Response
getTeamInvoice (tid ::: iid ::: _) = do
@@ -674,7 +674,7 @@ getConsentLog e = do
acc <- (listToMaybe <$> Intra.getUserProfilesByIdentity (Left e))
when (isJust acc) $
throwE $
- Error status403 "user-exists" "Trying to access consent log of existing user!"
+ mkError status403 "user-exists" "Trying to access consent log of existing user!"
consentLog <- Intra.getEmailConsentLog e
marketo <- Intra.getMarketoResult e
return . json $
@@ -735,7 +735,7 @@ ifNothing :: Error -> Maybe a -> Handler a
ifNothing e = maybe (throwE e) return
noSuchUser :: Maybe a -> Handler a
-noSuchUser = ifNothing (Error status404 "no-user" "No such user")
+noSuchUser = ifNothing (mkError status404 "no-user" "No such user")
mkFeaturePutGetRoute ::
forall (a :: Public.TeamFeatureName).
diff --git a/tools/stern/src/Stern/Intra.hs b/tools/stern/src/Stern/Intra.hs
index c95b9695ddb..85bfd53d3ee 100644
--- a/tools/stern/src/Stern/Intra.hs
+++ b/tools/stern/src/Stern/Intra.hs
@@ -87,7 +87,7 @@ import Gundeck.Types
import Imports
import Network.HTTP.Types.Method
import Network.HTTP.Types.Status hiding (statusCode)
-import Network.Wai.Utilities (Error (..))
+import Network.Wai.Utilities (Error (..), mkError)
import Stern.App
import Stern.Types
import System.Logger.Class hiding (Error, name, (.=))
@@ -159,7 +159,7 @@ getUserConnections uid = do
. maybe id (queryItem "start" . toByteString') start
. expect2xx
)
- parseResponse (Error status502 "bad-upstream") r
+ parseResponse (mkError status502 "bad-upstream") r
batchSize = 100 :: Int
getUsersConnections :: List UserId -> Handler [ConnectionStatus]
@@ -177,7 +177,7 @@ getUsersConnections uids = do
. expect2xx
)
info $ msg ("Response" ++ show r)
- parseResponse (Error status502 "bad-upstream") r
+ parseResponse (mkError status502 "bad-upstream") r
where
users = BS.intercalate "," $ map toByteString' (fromList uids)
@@ -199,7 +199,7 @@ getUserProfiles uidsOrHandles = do
. qry
. expect2xx
)
- parseResponse (Error status502 "bad-upstream") r
+ parseResponse (mkError status502 "bad-upstream") r
prepareQS :: Either [UserId] [Handle] -> [(Request -> Request)]
prepareQS (Left uids) = fmap (queryItem "ids") (toQS uids)
prepareQS (Right handles) = fmap (queryItem "handles") (toQS handles)
@@ -222,7 +222,7 @@ getUserProfilesByIdentity emailOrPhone = do
. userKeyToParam emailOrPhone
. expect2xx
)
- parseResponse (Error status502 "bad-upstream") r
+ parseResponse (mkError status502 "bad-upstream") r
getContacts :: UserId -> Text -> Int32 -> Handler (SearchResult Contact)
getContacts u q s = do
@@ -240,7 +240,7 @@ getContacts u q s = do
. queryItem "size" (toByteString' s)
. expect2xx
)
- parseResponse (Error status502 "bad-upstream") r
+ parseResponse (mkError status502 "bad-upstream") r
revokeIdentity :: Either Email Phone -> Handler ()
revokeIdentity emailOrPhone = do
@@ -337,7 +337,7 @@ getUserBindingTeam u = do
. header "Z-Connection" (toByteString' "")
. expect2xx
)
- teams <- parseResponse (Error status502 "bad-upstream") r
+ teams <- parseResponse (mkError status502 "bad-upstream") r
return $
listToMaybe $
fmap (view teamId) $
@@ -373,9 +373,9 @@ getTeamBillingInfo tid = do
. paths ["i", "team", toByteString' tid, "billing"]
)
case Bilge.statusCode r of
- 200 -> Just <$> parseResponse (Error status502 "bad-upstream") r
+ 200 -> Just <$> parseResponse (mkError status502 "bad-upstream") r
404 -> return Nothing
- _ -> throwE (Error status502 "bad-upstream" "bad response")
+ _ -> throwE (mkError status502 "bad-upstream" "bad response")
setTeamBillingInfo :: TeamId -> TeamBillingInfo -> Handler ()
setTeamBillingInfo tid tbu = do
@@ -408,7 +408,7 @@ isBlacklisted emailOrPhone = do
case Bilge.statusCode r of
200 -> return True
404 -> return False
- _ -> throwE (Error status502 "bad-upstream" "bad response")
+ _ -> throwE (mkError status502 "bad-upstream" "bad response")
setBlacklistStatus :: Bool -> Either Email Phone -> Handler ()
setBlacklistStatus status emailOrPhone = do
@@ -444,8 +444,8 @@ getTeamFeatureFlag tid = do
resp <- catchRpcErrors $ rpc' "galley" gly req
case Bilge.statusCode resp of
200 -> pure $ responseJsonUnsafe @(Public.TeamFeatureStatus a) resp
- 404 -> throwE (Error status404 "bad-upstream" "team doesnt exist")
- _ -> throwE (Error status502 "bad-upstream" "bad response")
+ 404 -> throwE (mkError status404 "bad-upstream" "team doesnt exist")
+ _ -> throwE (mkError status502 "bad-upstream" "bad response")
setTeamFeatureFlag ::
forall (a :: Public.TeamFeatureName).
@@ -466,8 +466,8 @@ setTeamFeatureFlag tid status = do
resp <- catchRpcErrors $ rpc' "galley" gly req
case statusCode resp of
200 -> pure ()
- 404 -> throwE (Error status404 "bad-upstream" "team doesnt exist")
- _ -> throwE (Error status502 "bad-upstream" "bad response")
+ 404 -> throwE (mkError status404 "bad-upstream" "team doesnt exist")
+ _ -> throwE (mkError status502 "bad-upstream" "bad response")
getTeamFeatureFlagNoConfig ::
TeamId ->
@@ -484,8 +484,8 @@ getTeamFeatureFlagNoConfig tid featureName = do
resp <- catchRpcErrors (rpc' "galley" gly req)
case statusCode resp of
200 -> pure $ responseJsonUnsafe @Public.TeamFeatureStatusNoConfig resp
- 404 -> throwE (Error status404 "bad-upstream" "team doesnt exist")
- _ -> throwE (Error status502 "bad-upstream" "bad response")
+ 404 -> throwE (mkError status404 "bad-upstream" "team doesnt exist")
+ _ -> throwE (mkError status502 "bad-upstream" "bad response")
setTeamFeatureFlagNoConfig ::
TeamId ->
@@ -503,7 +503,7 @@ setTeamFeatureFlagNoConfig tid featureName statusValue = do
resp <- catchRpcErrors $ rpc' "galley" gly req
case statusCode resp of
200 -> pure ()
- 404 -> throwE (Error status404 "bad-upstream" "team doesnt exist")
+ 404 -> throwE (mkError status404 "bad-upstream" "team doesnt exist")
_ -> throwE $ responseJsonUnsafe resp
getSearchVisibility :: TeamId -> Handler TeamSearchVisibilityView
@@ -520,7 +520,7 @@ getSearchVisibility tid = do
)
where
fromResponseBody :: Response (Maybe LByteString) -> Handler TeamSearchVisibilityView
- fromResponseBody resp = parseResponse (Error status502 "bad-upstream") resp
+ fromResponseBody resp = parseResponse (mkError status502 "bad-upstream") resp
setSearchVisibility :: TeamId -> TeamSearchVisibility -> Handler ()
setSearchVisibility tid typ = do
@@ -540,7 +540,7 @@ setSearchVisibility tid typ = do
200 -> pure ()
403 ->
throwE $
- Error
+ mkError
status403
"team-search-visibility-unset"
"This team does not have TeamSearchVisibility enabled. Ensure this is the correct TeamID or first enable the feature"
@@ -564,7 +564,7 @@ catchRpcErrors action = ExceptT $ catch (Right <$> action) catchRPCException
catchRPCException :: RPCException -> App (Either Error a)
catchRPCException rpcE = do
Log.err $ rpcExceptionMsg rpcE
- pure . Left $ Error status500 "io-error" (pack $ show rpcE)
+ pure . Left $ mkError status500 "io-error" (pack $ show rpcE)
getTeamData :: TeamId -> Handler TeamData
getTeamData tid = do
@@ -580,8 +580,8 @@ getTeamData tid = do
. expectStatus (`elem` [200, 404])
)
case Bilge.statusCode r of
- 200 -> parseResponse (Error status502 "bad-upstream") r
- _ -> throwE (Error status404 "no-team" "no such team")
+ 200 -> parseResponse (mkError status502 "bad-upstream") r
+ _ -> throwE (mkError status404 "no-team" "no such team")
getTeamMembers :: TeamId -> Handler TeamMemberList
getTeamMembers tid = do
@@ -596,7 +596,7 @@ getTeamMembers tid = do
. paths ["i", "teams", toByteString' tid, "members"]
. expect2xx
)
- parseResponse (Error status502 "bad-upstream") r
+ parseResponse (mkError status502 "bad-upstream") r
getEmailConsentLog :: Email -> Handler ConsentLog
getEmailConsentLog email = do
@@ -611,7 +611,7 @@ getEmailConsentLog email = do
. paths ["/i/consent/logs/emails", toByteString' email]
. expect2xx
)
- parseResponse (Error status502 "bad-upstream") r
+ parseResponse (mkError status502 "bad-upstream") r
-- TODO: Temporary in stern -- All functions below this
-- will eventually be moved to a separate service
@@ -630,7 +630,7 @@ getUserConsentValue uid = do
. path "/self/consent"
. expect2xx
)
- parseResponse (Error status502 "bad-upstream") r
+ parseResponse (mkError status502 "bad-upstream") r
getMarketoResult :: Email -> Handler MarketoResult
getMarketoResult email = do
@@ -647,9 +647,9 @@ getMarketoResult email = do
)
-- 404 is acceptable when marketo doesn't know about this user, return an empty result
case statusCode r of
- 200 -> parseResponse (Error status502 "bad-upstream") r
+ 200 -> parseResponse (mkError status502 "bad-upstream") r
404 -> return noEmail
- _ -> throwE (Error status502 "bad-upstream" "")
+ _ -> throwE (mkError status502 "bad-upstream" "")
where
noEmail = MarketoResult $ M.singleton "results" emptyArray
@@ -666,7 +666,7 @@ getUserConsentLog uid = do
. paths ["/i/consent/logs/users", toByteString' uid]
. expect2xx
)
- parseResponse (Error status502 "bad-upstream") r
+ parseResponse (mkError status502 "bad-upstream") r
getUserCookies :: UserId -> Handler CookieList
getUserCookies uid = do
@@ -682,7 +682,7 @@ getUserCookies uid = do
. path "/cookies"
. expect2xx
)
- parseResponse (Error status502 "bad-upstream") r
+ parseResponse (mkError status502 "bad-upstream") r
getUserConversations :: UserId -> Handler [Conversation]
getUserConversations uid = do
@@ -710,7 +710,7 @@ getUserConversations uid = do
. maybe id (queryItem "start" . toByteString') start
. expect2xx
)
- parseResponse (Error status502 "bad-upstream") r
+ parseResponse (mkError status502 "bad-upstream") r
batchSize = 100 :: Int
getUserClients :: UserId -> Handler [Client]
@@ -728,7 +728,7 @@ getUserClients uid = do
. expect2xx
)
info $ msg ("Response" ++ show r)
- parseResponse (Error status502 "bad-upstream") r
+ parseResponse (mkError status502 "bad-upstream") r
getUserProperties :: UserId -> Handler UserProperties
getUserProperties uid = do
@@ -745,7 +745,7 @@ getUserProperties uid = do
. expect2xx
)
info $ msg ("Response" ++ show r)
- keys <- parseResponse (Error status502 "bad-upstream") r :: Handler [PropertyKey]
+ keys <- parseResponse (mkError status502 "bad-upstream") r :: Handler [PropertyKey]
UserProperties <$> fetchProperty b keys M.empty
where
fetchProperty _ [] acc = return acc
@@ -761,7 +761,7 @@ getUserProperties uid = do
. expect2xx
)
info $ msg ("Response" ++ show r)
- value <- parseResponse (Error status502 "bad-upstream") r
+ value <- parseResponse (mkError status502 "bad-upstream") r
fetchProperty b xs (M.insert x value acc)
getUserNotifications :: UserId -> Handler [QueuedNotification]
@@ -793,7 +793,7 @@ getUserNotifications uid = do
-- 404 is an acceptable response, in case, for some reason,
-- "start" is not found we still return a QueuedNotificationList
case statusCode r of
- 200 -> parseResponse (Error status502 "bad-upstream") r
- 404 -> parseResponse (Error status502 "bad-upstream") r
- _ -> throwE (Error status502 "bad-upstream" "")
+ 200 -> parseResponse (mkError status502 "bad-upstream") r
+ 404 -> parseResponse (mkError status502 "bad-upstream") r
+ _ -> throwE (mkError status502 "bad-upstream" "")
batchSize = 100 :: Int