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
8 changes: 7 additions & 1 deletion charts/background-worker/templates/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ data:
host: brig
port: 8080

{{- if .enableFederation }}
federator:
host: {{ .federator.host }}
port: {{ .federator.port }}
{{- end }}

gundeck:
host: gundeck
port: 8080
Expand Down Expand Up @@ -103,4 +109,4 @@ data:
{{- if .postgresMigration }}
postgresMigration: {{- toYaml .postgresMigration | nindent 6 }}
{{- end }}
{{- end }}
{{- end }}
3 changes: 3 additions & 0 deletions charts/background-worker/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ config:
logLevel: Info
logFormat: StructuredJSON
enableFederation: false # keep in sync with brig, cargohold and galley charts' config.enableFederation as well as wire-server chart's tags.federation
federator:
host: federator
port: 8080
rabbitmq:
host: rabbitmq
port: 5672
Expand Down
4 changes: 4 additions & 0 deletions libs/galley-types/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
, data-default
, errors
, gitignoreSource
, http-types
, imports
, lens
, lib
Expand All @@ -21,6 +22,7 @@
, types-common
, utf8-string
, uuid
, wai-utilities
, wire-api
}:
mkDerivation {
Expand All @@ -36,6 +38,7 @@ mkDerivation {
crypton
data-default
errors
http-types
imports
lens
memory
Expand All @@ -44,6 +47,7 @@ mkDerivation {
types-common
utf8-string
uuid
wai-utilities
wire-api
];
license = lib.licenses.agpl3Only;
Expand Down
4 changes: 4 additions & 0 deletions libs/galley-types/galley-types.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ library
-- cabal-fmt: expand src
exposed-modules:
Galley.Types
Galley.Types.Clients
Galley.Types.Conversations.One2One
Galley.Types.Conversations.Roles
Galley.Types.Error
Galley.Types.Teams

other-modules: Paths_galley_types
Expand Down Expand Up @@ -76,6 +78,7 @@ library
, crypton
, data-default
, errors
, http-types
, imports
, lens >=4.12
, memory
Expand All @@ -84,6 +87,7 @@ library
, types-common >=0.16
, utf8-string
, uuid
, wai-utilities
, wire-api

default-language: GHC2021
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
-- | Most of the errors thrown by galley are defined as static errors in
-- 'Wire.API.Error.Galley' and declared as part of the API. Errors defined here
-- are dynamic, and mostly internal.
module Galley.API.Error
module Galley.Types.Error
( -- * Internal errors
InvalidInput (..),
InternalError (..),
Expand Down
4 changes: 4 additions & 0 deletions libs/galley-types/src/Galley/Types/Teams.hs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ module Galley.Types.Teams
( GetFeatureDefaults (..),
FeatureDefaults (..),
FeatureFlags,
FanoutLimit,
featureDefaults,
notTeamMember,
findTeamMember,
Expand All @@ -40,13 +41,16 @@ import Data.ByteString (toStrict)
import Data.ByteString.UTF8 qualified as UTF8
import Data.Default
import Data.Id (UserId)
import Data.Range (Range)
import Data.SOP
import Data.Set qualified as Set
import Imports
import Wire.API.Team.Feature
import Wire.API.Team.Member
import Wire.API.Team.Permission

type FanoutLimit = Range 1 HardTruncationLimit Int32

-- | Used to extract the feature config type out of 'FeatureDefaults' or
-- related types.
type family ConfigOf a
Expand Down
2 changes: 1 addition & 1 deletion libs/wire-api/src/Wire/API/Routes/Public/Brig.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1289,7 +1289,7 @@ type ClientAPI =
-- - MemberJoin event to self and other, if joining an existing connect conversation (via galley)
-- - ConvCreate event to self, if creating a connect conversation (via galley)
-- - ConvConnect event to self, in some cases (via galley),
-- for details see 'Galley.API.Create.createConnectConversation'
-- for details see 'Wire.ConversationSubsystem.Create.createConnectConversation'
type ConnectionAPI =
Named
"create-connection-unqualified"
Expand Down
20 changes: 19 additions & 1 deletion libs/wire-subsystems/src/Wire/ConversationSubsystem.hs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import Data.Qualified
import Data.Singletons (Sing)
import Imports
import Polysemy
import Wire.API.Conversation (ExtraConversationData)
import Wire.API.Conversation (ExtraConversationData, NewConv, NewOne2OneConv)
import Wire.API.Conversation.Action
import Wire.API.Event.Conversation
import Wire.NotificationSubsystem (LocalConversationUpdate)
Expand All @@ -43,5 +43,23 @@ data ConversationSubsystem m a where
ConversationAction (tag :: ConversationActionTag) ->
ExtraConversationData ->
ConversationSubsystem r LocalConversationUpdate
CreateGroupConversation ::
Local UserId ->
Maybe ConnId ->
NewConv ->
ConversationSubsystem m StoredConversation
CreateOne2OneConversation ::
Local UserId ->
ConnId ->
NewOne2OneConv ->
ConversationSubsystem m (StoredConversation, Bool)
CreateProteusSelfConversation ::
Local UserId ->
ConversationSubsystem m (StoredConversation, Bool)
CreateConnectConversation ::
Local UserId ->
Maybe ConnId ->
Connect ->
ConversationSubsystem m (StoredConversation, Bool)

makeSem ''ConversationSubsystem
112 changes: 112 additions & 0 deletions libs/wire-subsystems/src/Wire/ConversationSubsystem/Federation.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

-- This file is part of the Wire Server implementation.
--
-- Copyright (C) 2025 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/>.

module Wire.ConversationSubsystem.Federation where

import Control.Error (headMay)
import Data.Domain (Domain)
import Data.Qualified
import Data.Set qualified as Set
import Imports
import Polysemy
import Polysemy.Error
import Polysemy.Input
import Wire.API.Component (Component (..))
import Wire.API.Conversation.Protocol (ProtocolTag)
import Wire.API.Error.Galley (NonFederatingBackends (..), UnreachableBackends (..))
import Wire.API.Federation.API (fedClient)
import Wire.API.Federation.API.Brig (DomainSet (..), NonConnectedBackends (..))
import Wire.API.Federation.Client (FederatorClient)
import Wire.API.Federation.Error
import Wire.API.FederationStatus
import Wire.ConversationSubsystem.Types
import Wire.FederationAPIAccess (FederationAPIAccess)
import Wire.FederationAPIAccess qualified as E

enforceFederationProtocol ::
( Member (Error FederationError) r,
Member (Input ConversationSubsystemConfig) r
) =>
ProtocolTag ->
[Remote ()] ->
Sem r ()
enforceFederationProtocol proto domains = do
unless (null domains) $ do
mAllowedProtos <- federationProtocols <$> input
unless (maybe True (elem proto) mAllowedProtos) $
throw FederationDisabledForProtocol

checkFederationStatus ::
( Member (Error UnreachableBackends) r,
Member (Error NonFederatingBackends) r,
Member (FederationAPIAccess FederatorClient) r
) =>
RemoteDomains ->
Sem r ()
checkFederationStatus req = do
status <- getFederationStatus req
case status of
FullyConnected -> pure ()
NotConnectedDomains dom1 dom2 -> throw (NonFederatingBackends dom1 dom2)

getFederationStatus ::
( Member (Error UnreachableBackends) r,
Member (FederationAPIAccess FederatorClient) r
) =>
RemoteDomains ->
Sem r FederationStatus
getFederationStatus req = do
fmap firstConflictOrFullyConnected
. (ensureNoUnreachableBackends =<<)
$ E.runFederatedConcurrentlyEither
(Set.toList req.rdDomains)
( \qds ->
fedClient @'Brig @"get-not-fully-connected-backends"
(DomainSet . Set.map tDomain $ void qds `Set.delete` req.rdDomains)
)

-- | "conflict" here means two remote domains that we are connected to
-- but are not connected to each other.
firstConflictOrFullyConnected :: [Remote NonConnectedBackends] -> FederationStatus
firstConflictOrFullyConnected =
maybe
FullyConnected
(uncurry NotConnectedDomains)
. headMay
. mapMaybe toMaybeConflict
where
toMaybeConflict :: Remote NonConnectedBackends -> Maybe (Domain, Domain)
toMaybeConflict r =
headMay (Set.toList (nonConnectedBackends (tUnqualified r))) <&> (tDomain r,)

ensureNoUnreachableBackends ::
(Member (Error UnreachableBackends) r) =>
[Either (Remote e, b) a] ->
Sem r [a]
ensureNoUnreachableBackends results = do
let (errors, values) = partitionEithers results
unless (null errors) $
throw (UnreachableBackends (map (tDomain . fst) errors))
pure values
Loading