Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
`GET /contacts/search` returns apps (and regular users) that collaborate with the searcher's team.

This means that `brig-index-migrate-data` now requires you to
configure the `elasticsearch-index` chart's postgres setup, and have a
postgres instance reachable with that setup, eg., like this:

```
# in charts/elasticsearch-index/values.yaml
postgresql:
host: postgresql # DNS name without protocol
port: "5432"
user: wire-server
dbname: wire-server
postgresqlPool:
size: 100
acquisitionTimeout: 10s
idlenessTimeout: 10m

postgresMigration:
user: cassandra
```

[More
info](https://docs.wire.com/latest/developer/reference/config-options.html?h=config#configure-postgresql)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Treat team collaborators like team members in contact search.
106 changes: 106 additions & 0 deletions integration/test/Test/TeamCollaborators.hs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{-# OPTIONS_GHC -Wno-ambiguous-fields #-}

-- This file is part of the Wire Server implementation.
--
-- Copyright (C) 2025 Wire Swiss GmbH <opensource@wire.com>
Expand All @@ -17,6 +19,9 @@

module Test.TeamCollaborators where

import qualified API.Brig as BrigP
import qualified API.BrigInternal as BrigI
import API.Common (randomName)
import API.Galley
import qualified API.GalleyInternal as Internal
import Data.Tuple.Extra
Expand Down Expand Up @@ -317,3 +322,104 @@ testUpdateCollaborator = do
[]
>>= assertSuccess
postOne2OneConversation bob alice team "chit-chat" >>= assertLabel 403 "operation-denied"

-- | Collaborators are part of the search space of the team they
-- collaborate with: `GET /search/contacts` returns them to members of
-- that team, just like it returns the team's own members. We test
-- collaborators from other teams, personal user accounts that
-- collaborate, and app.
testSearchFindsCollaborator :: (HasCallStack) => App ()
testSearchFindsCollaborator = do
(owner, team, [alice]) <- createTeam OwnDomain 2
(otherOwner, otherTeam, [bob, collab1]) <- createTeam OwnDomain 3
collab2 :: Value <- randomUser OwnDomain def
collab3 :: Value <-
BrigP.createApp otherOwner otherTeam def
`bindResponse` \resp -> resp.json %. "user"

collab1Name <- collab1 %. "name" & asString
collab2Name <- collab2 %. "name" & asString
collab3Name <- collab3 %. "name" & asString

collab1Name' <- randomName
collab2Name' <- randomName
collab3Name' <- randomName

-- Find before any collaborations have been established.
let assertFinds ::
(HasCallStack, MakesValue expectFound, MakesValue searcher) =>
String ->
expectFound ->
searcher ->
App ()
assertFinds searchTerm expectFound searcher = do
BrigI.refreshIndex OwnDomain
BrigP.searchContacts searcher searchTerm OwnDomain `bindResponse` \resp -> do
resp.status `shouldMatchInt` 200
foundIds :: [String] <- resp.json %. "documents" >>= asList >>= mapM objId
expectedIds :: [String] <- (make >=> asList >=> mapM objId) expectFound
assertBool
("found: " <> show foundIds <> "; expected: " <> show expectedIds)
(sort foundIds == sort expectedIds)

for_ [owner, alice] $ assertFinds collab1Name ([] @Value)
for_ [otherOwner, bob] $ assertFinds collab1Name [collab1]

for_ [owner, alice] $ assertFinds collab2Name [collab2]
for_ [otherOwner, bob] $ assertFinds collab2Name [collab2]

for_ [owner, alice] $ assertFinds collab3Name ([] @Value)
for_ [otherOwner, bob] $ assertFinds collab3Name [collab3]

-- Add collaborators to team
for_ [collab1, collab2, collab3]
$ \collab ->
addTeamCollaborator owner team collab ["implicit_connection"] >>= assertSuccess

for_ [owner, alice] $ assertFinds collab1Name [collab1]
for_ [otherOwner, bob] $ assertFinds collab1Name [collab1]

for_ [owner, alice] $ assertFinds collab2Name [collab2]
for_ [otherOwner, bob] $ assertFinds collab2Name [collab2]

for_ [owner, alice] $ assertFinds collab3Name [collab3]
for_ [otherOwner, bob] $ assertFinds collab3Name [collab3]

-- Check that updating name does not erase collaborating teams in index.
for_ [(collab1, collab1Name'), (collab2, collab2Name'), (collab3, collab3Name')]
$ \(collab, newName) -> do
let updateBody = (def :: BrigP.PutSelf) {BrigP.name = Just newName}
in BrigP.putSelf collab updateBody >>= assertSuccess

for_ [owner, alice] $ assertFinds collab1Name' [collab1]
for_ [otherOwner, bob] $ assertFinds collab1Name' [collab1]

for_ [owner, alice] $ assertFinds collab2Name' [collab2]
for_ [otherOwner, bob] $ assertFinds collab2Name' [collab2]

for_ [owner, alice] $ assertFinds collab3Name' [collab3]
for_ [otherOwner, bob] $ assertFinds collab3Name' [collab3]

-- Check that updating collaborating teams does not erase name in index.
for_ [collab1, collab2, collab3]
$ \collab -> do
removeTeamCollaborator owner team collab >>= assertSuccess

for_ [owner, alice] $ assertFinds collab1Name' ([] @Value)
for_ [otherOwner, bob] $ assertFinds collab1Name' [collab1]

for_ [owner, alice] $ assertFinds collab2Name' [collab2]
for_ [otherOwner, bob] $ assertFinds collab2Name' [collab2]

for_ [owner, alice] $ assertFinds collab3Name' ([] @Value)
for_ [otherOwner, bob] $ assertFinds collab3Name' [collab3]

-- Can one user collaborate in multiple teams without breaking search?
(_thirdOwner, _thirdTeam, [multiCollab]) <- createTeam OwnDomain 2
multiCollabName <- multiCollab %. "name" & asString

addTeamCollaborator owner team multiCollab ["implicit_connection"] >>= assertSuccess
addTeamCollaborator otherOwner otherTeam multiCollab ["implicit_connection"] >>= assertSuccess

for_ [owner, alice] $ assertFinds multiCollabName [multiCollab]
for_ [otherOwner, bob] $ assertFinds multiCollabName [multiCollab]
65 changes: 65 additions & 0 deletions libs/wire-subsystems/src/Wire/BrigAPIAccess/Local.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
-- This file is part of the Wire Server implementation.
--
-- Copyright (C) 2026 Wire Swiss GmbH <opensource@wire.com>
--
-- 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 <https://www.gnu.org/licenses/>.

-- | Interprets 'BrigAPIAccess' from within brig itself, by calling into the local
-- subsystems directly instead of round-tripping over HTTP to itself (as
-- 'Wire.BrigAPIAccess.Rpc.interpretBrigAccess' does for every other service).
--
-- Only the operations needed by code shared with other services (e.g.
-- 'Wire.TeamCollaboratorsSubsystem') are implemented locally. Everything else
-- falls back to the RPC handler, pointed at brig itself: correct, but a wasteful
-- round-trip through our own listen socket, so it logs a warning and should be
-- given a local implementation once something actually relies on it.
module Wire.BrigAPIAccess.Local where

import Imports
import Polysemy
import Polysemy.Error (Error)
import Polysemy.Input (runInputConst)
import Polysemy.TinyLog (TinyLog)
import Polysemy.TinyLog qualified as Log
import System.Logger.Message qualified as Log
import Util.Options (Endpoint)
import Wire.BrigAPIAccess
import Wire.BrigAPIAccess.Rpc (brigAccessRpcHandler)
import Wire.ParseException (ParseException)
import Wire.Rpc (Rpc)
import Wire.RpcException (RpcException)
import Wire.UserSubsystem (UserSubsystem)
import Wire.UserSubsystem qualified as UserSubsystem

-- | The 'Endpoint' is brig's own; it is only used for the operations that have
-- no local implementation yet.
interpretBrigAPIAccessLocally ::

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You still need to convince Akshay

forall r.
( Member TinyLog r,
Member Rpc r,
Member (Error ParseException) r,
Member (Error RpcException) r
) =>
Endpoint ->
InterpreterFor UserSubsystem r ->
InterpreterFor BrigAPIAccess r
interpretBrigAPIAccessLocally selfEndpoint runUser = interpret $ \case
UpdateSearchIndex uid -> runUser (UserSubsystem.internalUpdateSearchIndex uid)
other -> selfRpc other
where
selfRpc :: forall m x. BrigAPIAccess m x -> Sem r x
selfRpc action = do
Log.warn $
Log.msg (Log.val "BrigAPIAccess.Local: no local implementation, calling brig over HTTP")
runInputConst selfEndpoint (brigAccessRpcHandler action)
Loading