From 36a734b9fdec9e14f7532e730d753fecd1d8a5b7 Mon Sep 17 00:00:00 2001 From: Gautier DI FOLCO Date: Thu, 12 Mar 2026 18:04:19 +0100 Subject: [PATCH 01/19] WPB-23789: Move operation to `ConversationSubsystem` --- .../background-worker/configmap.yaml | 24 + .../wire-federation-v0/values.yaml.gotmpl | 28 + hack/helm_vars/wire-server/values.yaml.gotmpl | 28 + .../src/Wire/API/Team/FeatureFlags.hs | 13 +- libs/wire-subsystems/default.nix | 9 + .../Wire/BackgroundJobsRunner/Interpreter.hs | 2 +- .../src/Wire/ConversationStore/Cassandra.hs | 2 +- .../src/Wire/ConversationSubsystem.hs | 712 +++++++++++++++++- .../src/Wire/ConversationSubsystem}/Action.hs | 77 +- .../ConversationSubsystem}/Action/Kick.hs | 8 +- .../ConversationSubsystem}/Action/Leave.hs | 4 +- .../ConversationSubsystem}/Action/Notify.hs | 18 +- .../ConversationSubsystem}/Action/Reset.hs | 8 +- .../Wire/ConversationSubsystem}/Clients.hs | 13 +- .../src/Wire/ConversationSubsystem/Create.hs | 258 +++++++ .../Wire/ConversationSubsystem/Features.hs | 2 +- .../Wire/ConversationSubsystem/Federation.hs | 119 +-- .../Wire/ConversationSubsystem/Internal.hs | 6 +- .../Wire/ConversationSubsystem/Interpreter.hs | 392 +++++++++- .../Wire/ConversationSubsystem/Legalhold.hs | 2 +- .../LegalholdConflicts.hs | 11 +- .../src/Wire/ConversationSubsystem}/MLS.hs | 14 +- .../MLS/CheckClients.hs | 4 +- .../ConversationSubsystem}/MLS/Commit/Core.hs | 8 +- .../MLS/Commit/ExternalCommit.hs | 12 +- .../MLS/Commit/InternalCommit.hs | 22 +- .../MLS/Conversation.hs | 2 +- .../ConversationSubsystem}/MLS/Enabled.hs | 4 +- .../ConversationSubsystem}/MLS/GroupInfo.hs | 6 +- .../MLS/GroupInfoCheck.hs | 4 +- .../MLS/IncomingMessage.hs | 2 +- .../Wire/ConversationSubsystem}/MLS/Keys.hs | 2 +- .../ConversationSubsystem}/MLS/Message.hs | 114 ++- .../ConversationSubsystem}/MLS/Migration.hs | 2 +- .../ConversationSubsystem}/MLS/One2One.hs | 2 +- .../ConversationSubsystem}/MLS/OutOfSync.hs | 4 +- .../ConversationSubsystem}/MLS/Propagate.hs | 2 +- .../ConversationSubsystem}/MLS/Proposal.hs | 3 +- .../ConversationSubsystem}/MLS/Removal.hs | 8 +- .../Wire/ConversationSubsystem}/MLS/Reset.hs | 18 +- .../MLS/SubConversation.hs | 26 +- .../Wire/ConversationSubsystem}/MLS/Util.hs | 2 +- .../ConversationSubsystem}/MLS/Welcome.hs | 2 +- .../Wire/ConversationSubsystem}/Mapping.hs | 2 +- .../Wire/ConversationSubsystem}/Message.hs | 27 +- .../src/Wire/ConversationSubsystem/One2One.hs | 6 +- .../src/Wire/ConversationSubsystem}/Query.hs | 32 +- .../src/Wire/ConversationSubsystem}/Update.hs | 151 ++-- .../src/Wire/GalleyAPIAccess/Rpc.hs | 30 +- .../src/Wire/MeetingsSubsystem/Interpreter.hs | 2 +- .../Wire/NotificationSubsystem/Interpreter.hs | 5 +- .../TeamInvitationSubsystem/Interpreter.hs | 2 +- .../Wire/ConversationSubsystem/MappingSpec.hs | 108 ++- .../Wire/ConversationSubsystem/MessageSpec.hs | 130 ++-- .../Wire/ConversationSubsystem/One2OneSpec.hs | 23 +- .../MockInterpreters/ConversationSubsystem.hs | 8 +- libs/wire-subsystems/wire-subsystems.cabal | 43 ++ .../background-worker.integration.yaml | 31 + .../src/Wire/BackgroundWorker/Env.hs | 15 +- .../Wire/BackgroundWorker/Jobs/Registry.hs | 106 ++- .../src/Wire/BackgroundWorker/Options.hs | 27 +- .../Wire/BackendNotificationPusherSpec.hs | 36 +- .../background-worker/test/Test/Wire/Util.hs | 18 + services/galley/default.nix | 30 +- services/galley/galley.cabal | 60 -- services/galley/src/Galley/API/Create.hs | 140 ---- services/galley/src/Galley/API/Federation.hs | 48 +- services/galley/src/Galley/API/Internal.hs | 81 +- services/galley/src/Galley/API/LegalHold.hs | 226 ++---- services/galley/src/Galley/API/Public/Bot.hs | 31 +- .../src/Galley/API/Public/Conversation.hs | 8 +- .../galley/src/Galley/API/Public/Feature.hs | 7 +- .../galley/src/Galley/API/Public/LegalHold.hs | 1 + services/galley/src/Galley/API/Public/MLS.hs | 3 +- .../galley/src/Galley/API/Public/Messaging.hs | 2 +- services/galley/src/Galley/API/Public/Team.hs | 2 +- services/galley/src/Galley/API/Teams.hs | 97 +-- .../galley/src/Galley/API/Teams/Features.hs | 42 +- services/galley/src/Galley/App.hs | 116 ++- services/galley/src/Galley/Env.hs | 59 +- services/galley/src/Galley/Options.hs | 20 +- services/galley/test/integration/API.hs | 2 +- services/galley/test/integration/API/Teams.hs | 2 +- .../test/integration/API/Teams/LegalHold.hs | 2 +- .../API/Teams/LegalHold/DisabledByDefault.hs | 4 +- services/galley/test/unit/Run.hs | 8 +- 86 files changed, 2572 insertions(+), 1220 deletions(-) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/Action.hs (97%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/Action/Kick.hs (93%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/Action/Leave.hs (94%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/Action/Notify.hs (76%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/Action/Reset.hs (96%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/Clients.hs (93%) create mode 100644 libs/wire-subsystems/src/Wire/ConversationSubsystem/Create.hs rename services/galley/src/Galley/API/Teams/Features/Get.hs => libs/wire-subsystems/src/Wire/ConversationSubsystem/Features.hs (99%) rename services/galley/src/Galley/API/Federation/Handlers.hs => libs/wire-subsystems/src/Wire/ConversationSubsystem/Federation.hs (92%) rename services/galley/src/Galley/API/LegalHold/Get.hs => libs/wire-subsystems/src/Wire/ConversationSubsystem/Legalhold.hs (97%) rename services/galley/src/Galley/API/LegalHold/Conflicts.hs => libs/wire-subsystems/src/Wire/ConversationSubsystem/LegalholdConflicts.hs (96%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/MLS.hs (88%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/MLS/CheckClients.hs (98%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/MLS/Commit/Core.hs (98%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/MLS/Commit/ExternalCommit.hs (96%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/MLS/Commit/InternalCommit.hs (96%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/MLS/Conversation.hs (97%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/MLS/Enabled.hs (95%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/MLS/GroupInfo.hs (95%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/MLS/GroupInfoCheck.hs (97%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/MLS/IncomingMessage.hs (98%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/MLS/Keys.hs (95%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/MLS/Message.hs (90%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/MLS/Migration.hs (98%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/MLS/One2One.hs (99%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/MLS/OutOfSync.hs (97%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/MLS/Propagate.hs (98%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/MLS/Proposal.hs (99%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/MLS/Removal.hs (98%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/MLS/Reset.hs (93%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/MLS/SubConversation.hs (94%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/MLS/Util.hs (98%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/MLS/Welcome.hs (99%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/Mapping.hs (99%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/Message.hs (97%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/Query.hs (98%) rename {services/galley/src/Galley/API => libs/wire-subsystems/src/Wire/ConversationSubsystem}/Update.hs (94%) rename services/galley/test/unit/Test/Galley/Mapping.hs => libs/wire-subsystems/test/unit/Wire/ConversationSubsystem/MappingSpec.hs (59%) rename services/galley/test/unit/Test/Galley/API/Message.hs => libs/wire-subsystems/test/unit/Wire/ConversationSubsystem/MessageSpec.hs (56%) rename services/galley/test/unit/Test/Galley/API/One2One.hs => libs/wire-subsystems/test/unit/Wire/ConversationSubsystem/One2OneSpec.hs (80%) delete mode 100644 services/galley/src/Galley/API/Create.hs diff --git a/charts/wire-server/templates/background-worker/configmap.yaml b/charts/wire-server/templates/background-worker/configmap.yaml index 49c0c3d38d7..d8b790a2349 100644 --- a/charts/wire-server/templates/background-worker/configmap.yaml +++ b/charts/wire-server/templates/background-worker/configmap.yaml @@ -112,6 +112,30 @@ data: backgroundJobs: {{ toYaml . | indent 6 }} {{- end }} + + settings: + maxTeamSize: {{ .settings.maxTeamSize }} + maxFanoutSize: {{ .settings.maxFanoutSize }} + exposeInvitationURLsTeamAllowlist: {{ .settings.exposeInvitationURLsTeamAllowlist }} + maxConvSize: {{ .settings.maxConvSize }} + intraListing: {{ .settings.intraListing }} + {{- if .settings.conversationCodeURI }} + conversationCodeURI: {{ .settings.conversationCodeURI | quote }} + {{- else if .settings.multiIngress }} + multiIngress: {{- toYaml .settings.multiIngress | nindent 8 }} + {{- end }} + {{- if .settings.federationProtocols }} + federationProtocols: {{ .settings.federationProtocols }} + {{- end }} + {{- if .settings.guestLinkTTLSeconds }} + guestLinkTTLSeconds: {{ .settings.guestLinkTTLSeconds }} + {{- end }} + passwordHashingOptions: {{ toYaml .settings.passwordHashingOptions | nindent 8 }} + passwordHashingRateLimit: {{ toYaml .settings.passwordHashingRateLimit | nindent 8 }} + {{- if .settings.checkGroupInfo }} + checkGroupInfo: {{ .settings.checkGroupInfo }} + {{- end }} + {{- if .postgresMigration }} postgresMigration: {{- toYaml .postgresMigration | nindent 6 }} {{- end }} diff --git a/hack/helm_vars/wire-federation-v0/values.yaml.gotmpl b/hack/helm_vars/wire-federation-v0/values.yaml.gotmpl index 52abc30d9b6..95eee4d1a56 100644 --- a/hack/helm_vars/wire-federation-v0/values.yaml.gotmpl +++ b/hack/helm_vars/wire-federation-v0/values.yaml.gotmpl @@ -297,6 +297,34 @@ background-worker: backendNotificationPusher: pushBackoffMinWait: 1000 # 1ms pushBackoffMaxWait: 500000 # 0.5s + settings: + maxTeamSize: 32 + maxFanoutSize: 18 + exposeInvitationURLsTeamAllowlist: [] + maxConvSize: 16 + intraListing: true + conversationCodeURI: https://kube-staging-nginz-https.zinfra.io/conversation-join/ + guestLinkTTLSeconds: 31536000 + passwordHashingOptions: + algorithm: argon2id + iterations: 1 + parallelism: 4 + memory: 32 # This needs to be at least 8 * parallelism. + passwordHashingRateLimit: + ipAddrLimit: + burst: 5 + inverseRate: 300000000 # 5 mins, makes it 12 reqs/hour + userLimit: + burst: 5 + inverseRate: 60000000 # 1 min, makes it 60 req/hour + internalLimit: + burst: 10 + inverseRate: 0 # No rate limiting for internal use + ipv4CidrBlock: 32 # Only block individual IP addresses + ipv6CidrBlock: 64 # Block /64 range at a time. + ipAddressExceptions: + - 127.0.0.1/8 + checkGroupInfo: false secrets: rabbitmq: username: {{ .Values.rabbitmqUsername }} diff --git a/hack/helm_vars/wire-server/values.yaml.gotmpl b/hack/helm_vars/wire-server/values.yaml.gotmpl index 5255592075c..f6869d62cfb 100644 --- a/hack/helm_vars/wire-server/values.yaml.gotmpl +++ b/hack/helm_vars/wire-server/values.yaml.gotmpl @@ -682,6 +682,34 @@ background-worker: tlsCaSecretRef: name: "rabbitmq-certificate" key: "ca.crt" + settings: + maxTeamSize: 32 + maxFanoutSize: 18 + exposeInvitationURLsTeamAllowlist: [] + maxConvSize: 16 + intraListing: true + conversationCodeURI: https://kube-staging-nginz-https.zinfra.io/conversation-join/ + guestLinkTTLSeconds: 31536000 + passwordHashingOptions: + algorithm: argon2id + iterations: 1 + parallelism: 4 + memory: 32 # This needs to be at least 8 * parallelism. + passwordHashingRateLimit: + ipAddrLimit: + burst: 5 + inverseRate: 300000000 # 5 mins, makes it 12 reqs/hour + userLimit: + burst: 5 + inverseRate: 60000000 # 1 min, makes it 60 req/hour + internalLimit: + burst: 10 + inverseRate: 0 # No rate limiting for internal use + ipv4CidrBlock: 32 # Only block individual IP addresses + ipv6CidrBlock: 64 # Block /64 range at a time. + ipAddressExceptions: + - 127.0.0.1/8 + checkGroupInfo: false secrets: pgPassword: "posty-the-gres" rabbitmq: diff --git a/libs/wire-api/src/Wire/API/Team/FeatureFlags.hs b/libs/wire-api/src/Wire/API/Team/FeatureFlags.hs index 7915eb9a126..638da672409 100644 --- a/libs/wire-api/src/Wire/API/Team/FeatureFlags.hs +++ b/libs/wire-api/src/Wire/API/Team/FeatureFlags.hs @@ -25,6 +25,8 @@ module Wire.API.Team.FeatureFlags FeatureFlags, FanoutLimit, featureDefaults, + defaultFanoutLimit, + currentFanoutLimit, notTeamMember, findTeamMember, isTeamMember, @@ -42,7 +44,7 @@ import Data.ByteString.UTF8 qualified as UTF8 import Data.Default import Data.Id (UserId) import Data.OpenApi qualified as S -import Data.Range (Range) +import Data.Range (Range, fromRange, toRange, unsafeRange) import Data.SOP import Data.Schema import Data.Set qualified as Set @@ -53,6 +55,15 @@ import Wire.API.Team.Permission type FanoutLimit = Range 1 HardTruncationLimit Int32 +defaultFanoutLimit :: FanoutLimit +defaultFanoutLimit = toRange (Proxy @HardTruncationLimit) + +currentFanoutLimit :: Word32 -> Maybe FanoutLimit -> FanoutLimit +currentFanoutLimit maxTeamSize maxFanoutSize = + let optFanoutLimit = fromIntegral . fromRange $ fromMaybe defaultFanoutLimit maxFanoutSize + maxSize = fromIntegral maxTeamSize + in unsafeRange (min maxSize optFanoutLimit) + -- | Used to extract the feature config type out of 'FeatureDefaults' or -- related types. type family ConfigOf a diff --git a/libs/wire-subsystems/default.nix b/libs/wire-subsystems/default.nix index 924043b7f13..e4429af3fde 100644 --- a/libs/wire-subsystems/default.nix +++ b/libs/wire-subsystems/default.nix @@ -19,10 +19,12 @@ , bilge , bimap , bloodhound +, brig-types , bytestring , bytestring-conversion , case-insensitive , cassandra-util +, comonad , conduit , constraints , containers @@ -66,6 +68,7 @@ , imports , iproute , iso639 +, kan-extensions , lens , lens-aeson , lib @@ -155,10 +158,12 @@ mkDerivation { bilge bimap bloodhound + brig-types bytestring bytestring-conversion case-insensitive cassandra-util + comonad conduit constraints containers @@ -198,6 +203,7 @@ mkDerivation { imports iproute iso639 + kan-extensions lens lens-aeson lrucaching @@ -277,10 +283,12 @@ mkDerivation { base64-bytestring bilge bloodhound + brig-types bytestring bytestring-conversion case-insensitive cassandra-util + comonad conduit constraints containers @@ -321,6 +329,7 @@ mkDerivation { imports iproute iso639 + kan-extensions lens lens-aeson lrucaching diff --git a/libs/wire-subsystems/src/Wire/BackgroundJobsRunner/Interpreter.hs b/libs/wire-subsystems/src/Wire/BackgroundJobsRunner/Interpreter.hs index 7353ad6092f..fc2ba75eb69 100644 --- a/libs/wire-subsystems/src/Wire/BackgroundJobsRunner/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/BackgroundJobsRunner/Interpreter.hs @@ -44,7 +44,7 @@ import Wire.API.UserGroup import Wire.BackgroundJobsPublisher import Wire.BackgroundJobsRunner (BackgroundJobsRunner (..)) import Wire.ConversationStore (ConversationStore, getConversation, upsertMembers) -import Wire.ConversationSubsystem +import Wire.ConversationSubsystem hiding (getConversation) import Wire.Sem.Random import Wire.StoredConversation import Wire.UserGroupStore (UserGroupStore, getUserGroup, getUserGroupChannels) diff --git a/libs/wire-subsystems/src/Wire/ConversationStore/Cassandra.hs b/libs/wire-subsystems/src/Wire/ConversationStore/Cassandra.hs index 6fdd631757d..bc36f8de6bc 100644 --- a/libs/wire-subsystems/src/Wire/ConversationStore/Cassandra.hs +++ b/libs/wire-subsystems/src/Wire/ConversationStore/Cassandra.hs @@ -455,7 +455,7 @@ addMembers conv (UserList lusers rusers) = do -- User is remote, so we only add it to the member_remote_user -- table, but the reverse mapping has to be done on the remote -- backend; so we assume an additional call to their backend has - -- been (or will be) made separately. See Galley.API.Update.addMembers + -- been (or will be) made separately. See Wire.ConversationSubsystem.Update.addMembers addPrepQuery Cql.insertRemoteMember (conv, domain, uid, role) pure (map newMemberWithRole lusers, map newRemoteMemberWithRole rusers) diff --git a/libs/wire-subsystems/src/Wire/ConversationSubsystem.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem.hs index 3a4593cf22a..0a41e1fda3b 100644 --- a/libs/wire-subsystems/src/Wire/ConversationSubsystem.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem.hs @@ -17,20 +17,80 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Wire.ConversationSubsystem where +module Wire.ConversationSubsystem + ( module Wire.ConversationSubsystem, + Util.BotsAndMembers (..), + Util.ConsentGiven (..), + Util.canDeleteMember, + Util.consentGiven, + Util.isMember, + Util.userLHEnabled, + Features.toTeamStatus, + MLSRemoval.RemoveUserIncludeMain (..), + LegalholdConflicts.guardLegalholdPolicyConflicts, + ) +where +import Data.Code qualified as Code +import Data.CommaSeparatedList (CommaSeparatedList) +import Data.Domain import Data.Id +import Data.LegalHold (UserLegalHoldStatus) +import Data.Misc (IpAddr, PlainTextPassword6) +import Data.Proxy (Proxy (..)) import Data.Qualified -import Data.Range (Range) -import Data.Singletons (Sing) +import Data.Range +import Data.Singletons (Demote, Sing, SingKind) import Galley.Types.Clients (Clients) import Imports import Polysemy -import Wire.API.Conversation (ConvIdsPage, ConversationPagingState, ExtraConversationData, NewConv, NewOne2OneConv) +import Wire.API.Bot (AddBot, RemoveBot) +import Wire.API.Conversation hiding (Member) +import Wire.API.Conversation qualified as Public import Wire.API.Conversation.Action +import Wire.API.Conversation.CellsState (CellsState) +import Wire.API.Conversation.Code (ConversationCodeInfo, CreateConversationCodeRequest, JoinConversationByCode) +import Wire.API.Conversation.Pagination (ConversationPage) +import Wire.API.Conversation.Protocol +import Wire.API.Conversation.Role (ConversationRolesList) +import Wire.API.Conversation.Typing import Wire.API.Event.Conversation +import Wire.API.Federation.API.Common +import Wire.API.Federation.API.Galley +import Wire.API.MLS.CommitBundle +import Wire.API.MLS.GroupInfo (GroupInfoData) +import Wire.API.MLS.Keys (MLSKeys, MLSKeysByPurpose, MLSPublicKey, MLSPublicKeyFormat, SomeKey) +import Wire.API.MLS.Message +import Wire.API.MLS.OutOfSync (EnableOutOfSyncCheck) +import Wire.API.MLS.Serialisation +import Wire.API.MLS.SubConversation (ConvOrSubConvId, PublicSubConversation, SubConvId) +import Wire.API.Message (ClientMismatch, IgnoreMissing, MessageSendingStatus, NewOtrMessage, QualifiedNewOtrMessage, ReportMissing) +import Wire.API.Pagination (PageSize, SortOrder) +import Wire.API.Provider.Bot qualified as Public (BotConvView) +import Wire.API.Routes.Internal.Galley.ConversationsIntra (UpsertOne2OneConversationRequest) +import Wire.API.Routes.Public (ZHostValue) +import Wire.API.Routes.Public.Galley.Conversation +import Wire.API.Routes.Public.Galley.MLS (MLSReset) +import Wire.API.Routes.Public.Galley.Messaging (MessageNotSent, PostOtrResponse) +import Wire.API.Routes.Public.Util (UpdateResult) +import Wire.API.Routes.Version +import Wire.API.ServantProto (RawProto (..)) +import Wire.API.Team.Feature (AllTeamFeatures, GuestLinksConfig, LockableFeature) +import Wire.API.Team.LegalHold (UserLegalHoldStatusResponse) +import Wire.API.Team.Member (IsPerm, TeamMemberList) +import Wire.API.User (VerificationAction) +import Wire.ConversationStore.MLS.Types (ListGlobalSelfConvs) +import Wire.ConversationSubsystem.Features qualified as Features +import Wire.ConversationSubsystem.LegalholdConflicts qualified as LegalholdConflicts +import Wire.ConversationSubsystem.MLS.IncomingMessage (IncomingBundle, IncomingMessage) +import Wire.ConversationSubsystem.MLS.Removal qualified as MLSRemoval +import Wire.ConversationSubsystem.Util qualified as Util +import Wire.FeaturesConfigSubsystem.Types (GetFeatureConfig) import Wire.NotificationSubsystem (LocalConversationUpdate) -import Wire.StoredConversation +import Wire.StoredConversation (BotMember, LocalMember, StoredConversation) + +data PermissionCheckArgs teamAssociation where + PermissionCheckArgs :: forall k (p :: k) teamAssociation. (SingKind k, IsPerm teamAssociation (Demote k)) => Sing p -> Maybe teamAssociation -> PermissionCheckArgs teamAssociation data ConversationSubsystem m a where NotifyConversationAction :: @@ -45,24 +105,39 @@ data ConversationSubsystem m a where ConversationAction (tag :: ConversationActionTag) -> ExtraConversationData -> ConversationSubsystem r LocalConversationUpdate - CreateGroupConversation :: + InternalCreateGroupConversation :: Local UserId -> Maybe ConnId -> NewConv -> ConversationSubsystem m StoredConversation + CreateGroupConversationUpToV3 :: + Local UserId -> + Maybe ConnId -> + NewConv -> + ConversationSubsystem m (ConversationResponse Public.OwnConversation) + CreateGroupOwnConversation :: + Local UserId -> + Maybe ConnId -> + NewConv -> + ConversationSubsystem m CreateGroupConversationResponseV9 + CreateGroupConversation :: + Local UserId -> + Maybe ConnId -> + NewConv -> + ConversationSubsystem m CreateGroupConversation + CreateProteusSelfConversation :: + Local UserId -> + ConversationSubsystem m (ConversationResponse Public.OwnConversation) CreateOne2OneConversation :: Local UserId -> ConnId -> NewOne2OneConv -> - ConversationSubsystem m (StoredConversation, Bool) - CreateProteusSelfConversation :: - Local UserId -> - ConversationSubsystem m (StoredConversation, Bool) + ConversationSubsystem m (ConversationResponse Public.OwnConversation) CreateConnectConversation :: Local UserId -> Maybe ConnId -> Connect -> - ConversationSubsystem m (StoredConversation, Bool) + ConversationSubsystem m (ConversationResponse Public.OwnConversation) GetConversations :: [ConvId] -> ConversationSubsystem m [StoredConversation] @@ -76,5 +151,620 @@ data ConversationSubsystem m a where ConvId -> UserId -> ConversationSubsystem m (Maybe LocalMember) + InternalGetMember :: + Qualified ConvId -> + UserId -> + ConversationSubsystem m (Maybe Public.Member) + GetConversationMeta :: + ConvId -> + ConversationSubsystem m ConversationMetadata + GetMLSOne2OneConversationInternal :: + Local UserId -> + Qualified UserId -> + ConversationSubsystem m Public.OwnConversation + IsMLSOne2OneEstablished :: + Local UserId -> + Qualified UserId -> + ConversationSubsystem m Bool + GetLocalConversationInternal :: + ConvId -> + ConversationSubsystem m Conversation + RmClient :: + UserId -> + ClientId -> + ConversationSubsystem m () + GetClients :: + UserId -> + ConversationSubsystem m [ClientId] + AddBot :: + Local UserId -> + ConnId -> + AddBot -> + ConversationSubsystem m Event + RmBot :: + Local UserId -> + Maybe ConnId -> + RemoveBot -> + ConversationSubsystem m (UpdateResult Event) + GetFeatureInternal :: + (GetFeatureConfig cfg) => + TeamId -> + ConversationSubsystem m (LockableFeature cfg) + UpdateCellsState :: + ConvId -> + CellsState -> + ConversationSubsystem m () + RemoveUser :: + Local StoredConversation -> + MLSRemoval.RemoveUserIncludeMain -> + Qualified UserId -> + ConversationSubsystem m () + PostMLSCommitBundle :: + Local x -> + Qualified UserId -> + ClientId -> + ConvType -> + Qualified ConvOrSubConvId -> + Maybe ConnId -> + EnableOutOfSyncCheck -> + IncomingBundle -> + ConversationSubsystem m [LocalConversationUpdate] + PostMLSCommitBundleFromLocalUser :: + Version -> + Local UserId -> + ClientId -> + ConnId -> + RawMLS CommitBundle -> + ConversationSubsystem m MLSMessageSendingStatus + PostMLSMessage :: + Local x -> + Qualified UserId -> + ClientId -> + ConvType -> + Qualified ConvOrSubConvId -> + Maybe ConnId -> + EnableOutOfSyncCheck -> + IncomingMessage -> + ConversationSubsystem m [LocalConversationUpdate] + PostMLSMessageFromLocalUser :: + Version -> + Local UserId -> + ClientId -> + ConnId -> + RawMLS Message -> + ConversationSubsystem m MLSMessageSendingStatus + IsMLSEnabled :: ConversationSubsystem m Bool + IterateConversations :: + Local UserId -> + Range 1 500 Int32 -> + ([StoredConversation] -> m a) -> + ConversationSubsystem m () + RemoveMemberFromLocalConv :: + Local ConvId -> + Local UserId -> + Maybe ConnId -> + Qualified UserId -> + ConversationSubsystem m (Maybe Event) + FederationOnConversationCreated :: + Domain -> + ConversationCreated ConvId -> + ConversationSubsystem m EmptyResponse + FederationGetConversationsV1 :: + Domain -> + GetConversationsRequest -> + ConversationSubsystem m GetConversationsResponse + FederationGetConversations :: + Domain -> + GetConversationsRequest -> + ConversationSubsystem m GetConversationsResponseV2 + FederationLeaveConversation :: + Domain -> + LeaveConversationRequest -> + ConversationSubsystem m LeaveConversationResponse + FederationSendMessage :: + Domain -> + ProteusMessageSendRequest -> + ConversationSubsystem m MessageSendResponse + FederationUpdateConversation :: + Domain -> + ConversationUpdateRequest -> + ConversationSubsystem m ConversationUpdateResponse + FederationMlsSendWelcome :: + Domain -> + MLSWelcomeRequest -> + ConversationSubsystem m MLSWelcomeResponse + FederationSendMLSMessage :: + Domain -> + MLSMessageSendRequest -> + ConversationSubsystem m MLSMessageResponse + FederationSendMLSCommitBundle :: + Domain -> + MLSMessageSendRequest -> + ConversationSubsystem m MLSMessageResponse + FederationQueryGroupInfo :: + Domain -> + GetGroupInfoRequest -> + ConversationSubsystem m GetGroupInfoResponse + FederationUpdateTypingIndicator :: + Domain -> + TypingDataUpdateRequest -> + ConversationSubsystem m TypingDataUpdateResponse + FederationOnTypingIndicatorUpdated :: + Domain -> + TypingDataUpdated -> + ConversationSubsystem m EmptyResponse + FederationGetSubConversationForRemoteUser :: + Domain -> + GetSubConversationsRequest -> + ConversationSubsystem m GetSubConversationsResponse + FederationDeleteSubConversationForRemoteUser :: + Domain -> + DeleteSubConversationFedRequest -> + ConversationSubsystem m DeleteSubConversationResponse + FederationLeaveSubConversation :: + Domain -> + LeaveSubConversationRequest -> + ConversationSubsystem m LeaveSubConversationResponse + FederationGetOne2OneConversationV1 :: + Domain -> + GetOne2OneConversationRequest -> + ConversationSubsystem m GetOne2OneConversationResponse + FederationGetOne2OneConversation :: + Domain -> + GetOne2OneConversationRequest -> + ConversationSubsystem m GetOne2OneConversationResponseV2 + FederationOnClientRemoved :: + Domain -> + ClientRemovedRequest -> + ConversationSubsystem m EmptyResponse + FederationOnMessageSent :: + Domain -> + RemoteMessage ConvId -> + ConversationSubsystem m EmptyResponse + FederationOnMLSMessageSent :: + Domain -> + RemoteMLSMessage -> + ConversationSubsystem m EmptyResponse + FederationOnConversationUpdatedV0 :: + Domain -> + ConversationUpdateV0 -> + ConversationSubsystem m EmptyResponse + FederationOnConversationUpdated :: + Domain -> + ConversationUpdate -> + ConversationSubsystem m EmptyResponse + FederationOnUserDeleted :: + Domain -> + UserDeletedConversationsNotification -> + ConversationSubsystem m EmptyResponse + PostOtrMessageUnqualified :: + Local UserId -> + ConnId -> + ConvId -> + Maybe IgnoreMissing -> + Maybe ReportMissing -> + NewOtrMessage -> + ConversationSubsystem m (PostOtrResponse ClientMismatch) + PostOtrBroadcastUnqualified :: + Local UserId -> + ConnId -> + Maybe IgnoreMissing -> + Maybe ReportMissing -> + NewOtrMessage -> + ConversationSubsystem m (PostOtrResponse ClientMismatch) + PostProteusMessage :: + Local UserId -> + ConnId -> + Qualified ConvId -> + RawProto QualifiedNewOtrMessage -> + ConversationSubsystem m (PostOtrResponse MessageSendingStatus) + PostProteusBroadcast :: + Local UserId -> + ConnId -> + QualifiedNewOtrMessage -> + ConversationSubsystem m (PostOtrResponse MessageSendingStatus) + DeleteLocalConversation :: + Local UserId -> + ConnId -> + Local ConvId -> + ConversationSubsystem m (UpdateResult Event) + GetMLSPublicKeys :: + Maybe MLSPublicKeyFormat -> + ConversationSubsystem m (MLSKeysByPurpose (MLSKeys SomeKey)) + FeatureEnabledForTeam :: + forall cfg m. + (GetFeatureConfig cfg) => + Proxy cfg -> + TeamId -> + ConversationSubsystem m Bool + GetAllTeamFeaturesForUser :: + UserId -> + ConversationSubsystem m AllTeamFeatures + GetSingleFeatureForUser :: + forall cfg m. + (GetFeatureConfig cfg) => + UserId -> + ConversationSubsystem m (LockableFeature cfg) + ResetMLSConversation :: + Local UserId -> + MLSReset -> + ConversationSubsystem m () + GetSubConversation :: + Local UserId -> + Qualified ConvId -> + SubConvId -> + ConversationSubsystem m PublicSubConversation + GetUserStatus :: + Local UserId -> + TeamId -> + UserId -> + ConversationSubsystem m UserLegalHoldStatusResponse + GuardSecondFactorDisabled :: + UserId -> + ConvId -> + ConversationSubsystem m () + GetBotConversation :: + BotId -> + ConvId -> + ConversationSubsystem m Public.BotConvView + -- Query functions + GetUnqualifiedOwnConversation :: + Local UserId -> + ConvId -> + ConversationSubsystem m Public.OwnConversation + GetOwnConversation :: + Local UserId -> + Qualified ConvId -> + ConversationSubsystem m Public.OwnConversation + GetPaginatedConversations :: + Local UserId -> + Maybe (Range 1 32 (CommaSeparatedList ConvId)) -> + Maybe ConvId -> + Maybe (Range 1 500 Int32) -> + ConversationSubsystem m (Public.ConversationList Public.OwnConversation) + GetConversation :: + Local UserId -> + Qualified ConvId -> + ConversationSubsystem m Public.Conversation + GetConversationRoles :: + Local UserId -> + ConvId -> + ConversationSubsystem m ConversationRolesList + SearchChannels :: + Local UserId -> + TeamId -> + Maybe Text -> + Maybe SortOrder -> + Maybe PageSize -> + Maybe Text -> + Maybe ConvId -> + Bool -> + ConversationSubsystem m ConversationPage + GetGroupInfo :: + Local UserId -> + Qualified ConvId -> + ConversationSubsystem m GroupInfoData + ConversationIdsPageFromUnqualified :: + Local UserId -> + Maybe ConvId -> + Maybe (Range 1 1000 Int32) -> + ConversationSubsystem m (ConversationList ConvId) + ConversationIdsPageFromV2 :: + ListGlobalSelfConvs -> + Local UserId -> + Public.GetPaginatedConversationIds -> + ConversationSubsystem m Public.ConvIdsPage + ConversationIdsPageFrom :: + Local UserId -> + Public.GetPaginatedConversationIds -> + ConversationSubsystem m Public.ConvIdsPage + ListConversations :: + Local UserId -> + Public.ListConversations -> + ConversationSubsystem m ConversationsResponse + GetConversationByReusableCode :: + Local UserId -> + Code.Key -> + Code.Value -> + ConversationSubsystem m ConversationCoverView + GetMLSSelfConversationWithError :: + Local UserId -> + ConversationSubsystem m Public.OwnConversation + GetMLSOne2OneConversationV5 :: + Local UserId -> + Qualified UserId -> + ConversationSubsystem m Public.OwnConversation + GetMLSOne2OneConversationV6 :: + Local UserId -> + Qualified UserId -> + ConversationSubsystem m (MLSOne2OneConversation MLSPublicKey) + GetMLSOne2OneConversation :: + Local UserId -> + Qualified UserId -> + Maybe MLSPublicKeyFormat -> + ConversationSubsystem m (MLSOne2OneConversation SomeKey) + GetLocalSelf :: + Local UserId -> + ConvId -> + ConversationSubsystem m (Maybe Public.Member) + GetSelfMember :: + Local UserId -> + Qualified ConvId -> + ConversationSubsystem m (Maybe Public.Member) + GetConversationGuestLinksStatus :: + UserId -> + ConvId -> + ConversationSubsystem m (LockableFeature GuestLinksConfig) + GetCode :: + Maybe Text -> + Local UserId -> + ConvId -> + ConversationSubsystem m ConversationCodeInfo + -- Update functions + AddMembersUnqualified :: + Local UserId -> + ConnId -> + ConvId -> + Invite -> + ConversationSubsystem m (UpdateResult Event) + AddMembersUnqualifiedV2 :: + Local UserId -> + ConnId -> + ConvId -> + InviteQualified -> + ConversationSubsystem m (UpdateResult Event) + AddMembers :: + Local UserId -> + ConnId -> + Qualified ConvId -> + InviteQualified -> + ConversationSubsystem m (UpdateResult Event) + ReplaceMembers :: + Local UserId -> + ConnId -> + Qualified ConvId -> + InviteQualified -> + ConversationSubsystem m () + JoinConversationById :: + Local UserId -> + ConnId -> + ConvId -> + ConversationSubsystem m (UpdateResult Event) + JoinConversationByReusableCode :: + Local UserId -> + ConnId -> + JoinConversationByCode -> + ConversationSubsystem m (UpdateResult Event) + CheckReusableCode :: + IpAddr -> + ConversationCode -> + ConversationSubsystem m () + AddCodeUnqualified :: + Maybe CreateConversationCodeRequest -> + UserId -> + Maybe ZHostValue -> + Maybe ConnId -> + ConvId -> + ConversationSubsystem m AddCodeResult + AddCodeUnqualifiedWithReqBody :: + UserId -> + Maybe Text -> + Maybe ConnId -> + ConvId -> + CreateConversationCodeRequest -> + ConversationSubsystem m AddCodeResult + RmCodeUnqualified :: + Local UserId -> + ConnId -> + ConvId -> + ConversationSubsystem m Event + MemberTypingUnqualified :: + Local UserId -> + ConnId -> + ConvId -> + TypingStatus -> + ConversationSubsystem m () + MemberTyping :: + Local UserId -> + ConnId -> + Qualified ConvId -> + TypingStatus -> + ConversationSubsystem m () + RemoveMemberUnqualified :: + Local UserId -> + ConnId -> + ConvId -> + UserId -> + ConversationSubsystem m (Maybe Event) + RemoveMemberQualified :: + Local UserId -> + ConnId -> + Qualified ConvId -> + Qualified UserId -> + ConversationSubsystem m (Maybe Event) + UpdateOtherMemberUnqualified :: + Local UserId -> + ConnId -> + ConvId -> + UserId -> + OtherMemberUpdate -> + ConversationSubsystem m () + UpdateOtherMember :: + Local UserId -> + ConnId -> + Qualified ConvId -> + Qualified UserId -> + OtherMemberUpdate -> + ConversationSubsystem m () + UpdateUnqualifiedConversationName :: + Local UserId -> + ConnId -> + ConvId -> + ConversationRename -> + ConversationSubsystem m (UpdateResult Event) + UpdateConversationName :: + Local UserId -> + ConnId -> + Qualified ConvId -> + ConversationRename -> + ConversationSubsystem m (UpdateResult Event) + UpdateConversationMessageTimerUnqualified :: + Local UserId -> + ConnId -> + ConvId -> + ConversationMessageTimerUpdate -> + ConversationSubsystem m (UpdateResult Event) + UpdateConversationMessageTimer :: + Local UserId -> + ConnId -> + Qualified ConvId -> + ConversationMessageTimerUpdate -> + ConversationSubsystem m (UpdateResult Event) + UpdateConversationReceiptModeUnqualified :: + Local UserId -> + ConnId -> + ConvId -> + ConversationReceiptModeUpdate -> + ConversationSubsystem m (UpdateResult Event) + UpdateConversationReceiptMode :: + Local UserId -> + ConnId -> + Qualified ConvId -> + ConversationReceiptModeUpdate -> + ConversationSubsystem m (UpdateResult Event) + UpdateConversationAccessUnqualified :: + Local UserId -> + ConnId -> + ConvId -> + ConversationAccessData -> + ConversationSubsystem m (UpdateResult Event) + UpdateConversationAccess :: + Local UserId -> + ConnId -> + Qualified ConvId -> + ConversationAccessData -> + ConversationSubsystem m (UpdateResult Event) + UpdateConversationHistory :: + Local UserId -> + ConnId -> + Qualified ConvId -> + ConversationHistoryUpdate -> + ConversationSubsystem m (UpdateResult Event) + UpdateUnqualifiedSelfMember :: + Local UserId -> + ConnId -> + ConvId -> + MemberUpdate -> + ConversationSubsystem m () + UpdateSelfMember :: + Local UserId -> + ConnId -> + Qualified ConvId -> + MemberUpdate -> + ConversationSubsystem m () + UpdateConversationProtocolWithLocalUser :: + Local UserId -> + ConnId -> + Qualified ConvId -> + ProtocolUpdate -> + ConversationSubsystem m (UpdateResult Event) + UpdateChannelAddPermission :: + Local UserId -> + ConnId -> + Qualified ConvId -> + AddPermissionUpdate -> + ConversationSubsystem m (UpdateResult Event) + PostBotMessageUnqualified :: + BotId -> + ConvId -> + Maybe IgnoreMissing -> + Maybe ReportMissing -> + NewOtrMessage -> + ConversationSubsystem m (Either (MessageNotSent ClientMismatch) ClientMismatch) + -- Sub-conversation functions + DeleteSubConversation :: + Local UserId -> + Qualified ConvId -> + SubConvId -> + MLSReset -> + ConversationSubsystem m () + GetSubConversationGroupInfo :: + Local UserId -> + Qualified ConvId -> + SubConvId -> + ConversationSubsystem m GroupInfoData + LeaveSubConversation :: + Local UserId -> + ClientId -> + Qualified ConvId -> + SubConvId -> + ConversationSubsystem m () + SendConversationActionNotifications :: + forall tag m. + Sing tag -> + Qualified UserId -> + Bool -> + Maybe ConnId -> + Local StoredConversation -> + Util.BotsAndMembers -> + ConversationAction (tag :: ConversationActionTag) -> + ExtraConversationData -> + ConversationSubsystem m LocalConversationUpdate + PermissionCheck :: + (IsPerm teamAssociation perm) => + perm -> Maybe teamAssociation -> ConversationSubsystem m teamAssociation + PermissionCheckSAbs :: + PermissionCheckArgs teamAssociation -> + ConversationSubsystem m teamAssociation + EnsureReAuthorised :: + UserId -> + Maybe PlainTextPassword6 -> + Maybe Code.Value -> + Maybe VerificationAction -> + ConversationSubsystem m () + QualifyLocal :: + a -> + ConversationSubsystem m (Local a) + AssertOnTeam :: + UserId -> + TeamId -> + ConversationSubsystem m () + CheckConsent :: + Map UserId TeamId -> + UserId -> + ConversationSubsystem m Util.ConsentGiven + GetLHStatusForUsers :: + [UserId] -> + ConversationSubsystem m [(UserId, UserLegalHoldStatus)] + EnsureConnectedToLocals :: + UserId -> + [UserId] -> + ConversationSubsystem m () + GetTeamMembersForFanout :: + TeamId -> + ConversationSubsystem m TeamMemberList + AssertTeamExists :: + TeamId -> + ConversationSubsystem m () + InternalUpsertOne2OneConversation :: + UpsertOne2OneConversationRequest -> + ConversationSubsystem m () + AcceptConv :: + QualifiedWithTag QLocal UserId -> + Maybe ConnId -> + ConvId -> + ConversationSubsystem m OwnConversation + BlockConv :: + QualifiedWithTag QLocal UserId -> + Qualified ConvId -> + ConversationSubsystem m () + UnblockConv :: + QualifiedWithTag QLocal UserId -> + Maybe ConnId -> + Qualified ConvId -> + ConversationSubsystem m () makeSem ''ConversationSubsystem + +permissionCheckS :: forall k (p :: k) teamAssociation r. (Member ConversationSubsystem r, SingKind k, IsPerm teamAssociation (Demote k)) => Sing p -> Maybe teamAssociation -> Sem r teamAssociation +permissionCheckS p mTeam = send (PermissionCheckSAbs (PermissionCheckArgs p mTeam)) diff --git a/services/galley/src/Galley/API/Action.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Action.hs similarity index 97% rename from services/galley/src/Galley/API/Action.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/Action.hs index 132d304fb88..8fb060eb0be 100644 --- a/services/galley/src/Galley/API/Action.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Action.hs @@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.Action +module Wire.ConversationSubsystem.Action ( -- * Conversation action types ConversationActionTag (..), ConversationJoin (..), @@ -73,14 +73,6 @@ import Data.Set qualified as Set import Data.Singletons import Data.Time.Clock import GHC.TypeLits (KnownNat) -import Galley.API.Action.Kick -import Galley.API.Action.Leave -import Galley.API.Action.Notify -import Galley.API.Action.Reset -import Galley.API.MLS.Conversation -import Galley.API.MLS.Migration -import Galley.API.MLS.Removal -import Galley.API.Teams.Features.Get import Galley.Types.Error import Imports hiding ((\\)) import Polysemy @@ -122,7 +114,14 @@ import Wire.BrigAPIAccess qualified as E import Wire.CodeStore import Wire.CodeStore qualified as E import Wire.ConversationStore qualified as E -import Wire.ConversationSubsystem +import Wire.ConversationSubsystem.Action.Kick +import Wire.ConversationSubsystem.Action.Leave +import Wire.ConversationSubsystem.Action.Notify +import Wire.ConversationSubsystem.Action.Reset +import Wire.ConversationSubsystem.Features +import Wire.ConversationSubsystem.MLS.Conversation +import Wire.ConversationSubsystem.MLS.Migration +import Wire.ConversationSubsystem.MLS.Removal import Wire.ConversationSubsystem.Util import Wire.ExternalAccess import Wire.FeaturesConfigSubsystem @@ -189,7 +188,6 @@ instance IsConversationAction 'ConversationJoinTag where HasConversationActionEffects 'ConversationJoinTag r = ( -- TODO: Replace with subsystems Member BackendNotificationQueueAccess r, - Member ConversationSubsystem r, Member TeamCollaboratorsSubsystem r, Member FederationSubsystem r, Member TeamSubsystem r, @@ -461,7 +459,6 @@ instance IsConversationAction 'ConversationAccessDataTag where Member Random r, Member (Error FederationError) r, Member BackendNotificationQueueAccess r, - Member ConversationSubsystem r, Member TeamSubsystem r ) @@ -694,7 +691,6 @@ instance IsConversationAction 'ConversationResetTag where ( Member BackendNotificationQueueAccess r, Member (E.FederationAPIAccess FederatorClient) r, Member ExternalAccess r, - Member ConversationSubsystem r, Member E.ConversationStore r, Member NotificationSubsystem r, Member ProposalStore r, @@ -825,7 +821,7 @@ performConversationJoin qusr lconv (ConversationJoin invited role joinType) = do -- - ensure that a consented conv admin exists -- - and kick all existing members that do not consent to LH from the conversation -- See also: "Brig.API.Connection.checkLegalholdPolicyConflict" - -- and "Galley.API.LegalHold.Conflicts.guardLegalholdPolicyConflictsUid". + -- and "Wire.ConversationSubsystem.LegalholdConflicts.guardLegalholdPolicyConflictsUid". checkLHPolicyConflictsLocal :: [UserId] -> Sem r () @@ -985,7 +981,6 @@ updateLocalConversationJoin :: Member (ErrorS ('ActionDenied (ConversationActionPermission 'ConversationJoinTag))) r, Member (ErrorS 'InvalidOperation) r, Member (ErrorS 'ConvNotFound) r, - Member ConversationSubsystem r, Member FederationSubsystem r, Member TeamCollaboratorsSubsystem r, Member TeamSubsystem r, @@ -1024,7 +1019,6 @@ updateLocalConversationLeave :: Member (ErrorS ('ActionDenied (ConversationActionPermission 'ConversationLeaveTag))) r, Member (ErrorS 'InvalidOperation) r, Member (ErrorS 'ConvNotFound) r, - Member ConversationSubsystem r, Member TeamSubsystem r, Member (Input ConversationSubsystemConfig) r, Member ExternalAccess r, @@ -1047,7 +1041,10 @@ updateLocalConversationMemberUpdate :: Member (ErrorS ('ActionDenied (ConversationActionPermission 'ConversationMemberUpdateTag))) r, Member (ErrorS 'InvalidOperation) r, Member (ErrorS 'ConvNotFound) r, - Member ConversationSubsystem r, + Member NotificationSubsystem r, + Member Now r, + Member ExternalAccess r, + Member BackendNotificationQueueAccess r, Member TeamSubsystem r, Member (ErrorS ConvMemberNotFound) r, Member E.ConversationStore r @@ -1064,7 +1061,10 @@ updateLocalConversationDelete :: ( Member (ErrorS ('ActionDenied (ConversationActionPermission 'ConversationDeleteTag))) r, Member (ErrorS 'InvalidOperation) r, Member (ErrorS 'ConvNotFound) r, - Member ConversationSubsystem r, + Member NotificationSubsystem r, + Member Now r, + Member ExternalAccess r, + Member BackendNotificationQueueAccess r, Member TeamSubsystem r, Member CodeStore r, Member E.ConversationStore r, @@ -1083,7 +1083,10 @@ updateLocalConversationRename :: ( Member (Error FederationError) r, Member (ErrorS ('ActionDenied (ConversationActionPermission 'ConversationRenameTag))) r, Member (ErrorS 'ConvNotFound) r, - Member ConversationSubsystem r, + Member NotificationSubsystem r, + Member Now r, + Member ExternalAccess r, + Member BackendNotificationQueueAccess r, Member TeamSubsystem r, Member (Error InvalidInput) r, Member E.ConversationStore r, @@ -1102,7 +1105,10 @@ updateLocalConversationMessageTimerUpdate :: Member (ErrorS ('ActionDenied (ConversationActionPermission 'ConversationMessageTimerUpdateTag))) r, Member (ErrorS 'InvalidOperation) r, Member (ErrorS 'ConvNotFound) r, - Member ConversationSubsystem r, + Member NotificationSubsystem r, + Member Now r, + Member ExternalAccess r, + Member BackendNotificationQueueAccess r, Member TeamSubsystem r, Member E.ConversationStore r, Member (Error NoChanges) r @@ -1120,7 +1126,10 @@ updateLocalConversationReceiptModeUpdate :: Member (ErrorS ('ActionDenied (ConversationActionPermission 'ConversationReceiptModeUpdateTag))) r, Member (ErrorS 'InvalidOperation) r, Member (ErrorS 'ConvNotFound) r, - Member ConversationSubsystem r, + Member NotificationSubsystem r, + Member Now r, + Member ExternalAccess r, + Member BackendNotificationQueueAccess r, Member TeamSubsystem r, Member E.ConversationStore r, Member (Error NoChanges) r, @@ -1139,7 +1148,6 @@ updateLocalConversationAccessData :: Member (Error FederationError) r, Member (ErrorS ('ActionDenied (ConversationActionPermission 'ConversationAccessDataTag))) r, Member (ErrorS 'ConvNotFound) r, - Member ConversationSubsystem r, Member (Error NoChanges) r, Member TinyLog r, Member E.ConversationStore r, @@ -1169,7 +1177,6 @@ updateLocalConversationRemoveMembers :: ( Member BackendNotificationQueueAccess r, Member (Error FederationError) r, Member (ErrorS ('ActionDenied (ConversationActionPermission 'ConversationRemoveMembersTag))) r, - Member ConversationSubsystem r, Member (Error NoChanges) r, Member TinyLog r, Member E.ConversationStore r, @@ -1197,7 +1204,6 @@ updateLocalConversationUpdateProtocol :: Member (ErrorS ('ActionDenied (ConversationActionPermission 'ConversationUpdateProtocolTag))) r, Member (ErrorS 'InvalidOperation) r, Member (ErrorS 'ConvNotFound) r, - Member ConversationSubsystem r, Member (Error NoChanges) r, Member (E.FederationAPIAccess FederatorClient) r, Member TinyLog r, @@ -1227,7 +1233,10 @@ updateLocalConversationUpdateAddPermission :: Member (ErrorS ('ActionDenied (ConversationActionPermission 'ConversationUpdateAddPermissionTag))) r, Member (ErrorS 'InvalidOperation) r, Member (ErrorS 'ConvNotFound) r, - Member ConversationSubsystem r, + Member NotificationSubsystem r, + Member Now r, + Member ExternalAccess r, + Member BackendNotificationQueueAccess r, Member (Error NoChanges) r, Member E.ConversationStore r, Member TeamSubsystem r, @@ -1247,7 +1256,6 @@ updateLocalConversationReset :: Member (ErrorS ('ActionDenied (ConversationActionPermission 'ConversationResetTag))) r, Member (ErrorS 'InvalidOperation) r, Member (ErrorS 'ConvNotFound) r, - Member ConversationSubsystem r, Member (E.FederationAPIAccess FederatorClient) r, Member TinyLog r, Member E.ConversationStore r, @@ -1275,7 +1283,10 @@ updateLocalConversationHistoryUpdate :: Member (ErrorS ('ActionDenied (ConversationActionPermission 'ConversationHistoryUpdateTag))) r, Member (ErrorS 'InvalidOperation) r, Member (ErrorS 'ConvNotFound) r, - Member ConversationSubsystem r, + Member NotificationSubsystem r, + Member Now r, + Member ExternalAccess r, + Member BackendNotificationQueueAccess r, Member E.ConversationStore r, Member TeamSubsystem r, Member (ErrorS HistoryNotSupported) r @@ -1294,7 +1305,6 @@ updateLocalConversationUncheckedJoin :: Member (ErrorS ('ActionDenied (ConversationActionPermission 'ConversationJoinTag))) r, Member (ErrorS 'InvalidOperation) r, Member (ErrorS 'ConvNotFound) r, - Member ConversationSubsystem r, Member FederationSubsystem r, Member TeamCollaboratorsSubsystem r, Member TeamSubsystem r, @@ -1333,7 +1343,6 @@ updateLocalConversationUncheckedRemoveMembers :: Member (ErrorS ('ActionDenied (ConversationActionPermission 'ConversationRemoveMembersTag))) r, Member (ErrorS 'InvalidOperation) r, Member (ErrorS 'ConvNotFound) r, - Member ConversationSubsystem r, Member TeamSubsystem r, Member (Input ConversationSubsystemConfig) r, Member (Error NoChanges) r, @@ -1360,7 +1369,10 @@ updateLocalConversation :: Member (ErrorS ('ActionDenied (ConversationActionPermission tag))) r, Member (ErrorS 'InvalidOperation) r, Member (ErrorS 'ConvNotFound) r, - Member ConversationSubsystem r, + Member NotificationSubsystem r, + Member Now r, + Member ExternalAccess r, + Member BackendNotificationQueueAccess r, HasConversationActionEffects tag r, IsConversationAction tag, SingI tag, @@ -1395,7 +1407,10 @@ updateLocalConversationUnchecked :: Member (ErrorS ('ActionDenied (ConversationActionPermission tag))) r, Member (ErrorS 'ConvNotFound) r, Member (ErrorS 'InvalidOperation) r, - Member ConversationSubsystem r, + Member NotificationSubsystem r, + Member Now r, + Member ExternalAccess r, + Member BackendNotificationQueueAccess r, HasConversationActionEffects tag r, Member TeamSubsystem r ) => diff --git a/services/galley/src/Galley/API/Action/Kick.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Action/Kick.hs similarity index 93% rename from services/galley/src/Galley/API/Action/Kick.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/Action/Kick.hs index 7356b04c10b..ddb1caae8a0 100644 --- a/services/galley/src/Galley/API/Action/Kick.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Action/Kick.hs @@ -15,14 +15,12 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.Action.Kick where +module Wire.ConversationSubsystem.Action.Kick where import Data.Default import Data.Id import Data.Qualified import Data.Singletons -import Galley.API.Action.Leave -import Galley.API.Action.Notify import Imports hiding ((\\)) import Polysemy import Polysemy.Error @@ -35,7 +33,8 @@ import Wire.API.Event.LeaveReason import Wire.API.Federation.Error import Wire.BackendNotificationQueueAccess import Wire.ConversationStore (ConversationStore) -import Wire.ConversationSubsystem +import Wire.ConversationSubsystem.Action.Leave +import Wire.ConversationSubsystem.Action.Notify import Wire.ConversationSubsystem.Util import Wire.ExternalAccess import Wire.NotificationSubsystem @@ -53,7 +52,6 @@ kickMember :: ( Member BackendNotificationQueueAccess r, Member (Error FederationError) r, Member ExternalAccess r, - Member ConversationSubsystem r, Member NotificationSubsystem r, Member ProposalStore r, Member Now r, diff --git a/services/galley/src/Galley/API/Action/Leave.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Action/Leave.hs similarity index 94% rename from services/galley/src/Galley/API/Action/Leave.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/Action/Leave.hs index 0141cc4ad2c..4e88fd77f9a 100644 --- a/services/galley/src/Galley/API/Action/Leave.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Action/Leave.hs @@ -15,12 +15,11 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.Action.Leave (leaveConversation) where +module Wire.ConversationSubsystem.Action.Leave (leaveConversation) where import Control.Lens import Data.Id import Data.Qualified -import Galley.API.MLS.Removal import Imports hiding ((\\)) import Polysemy import Polysemy.Error @@ -30,6 +29,7 @@ import Wire.API.Conversation.Config (ConversationSubsystemConfig) import Wire.API.Federation.Error import Wire.BackendNotificationQueueAccess import Wire.ConversationStore (ConversationStore) +import Wire.ConversationSubsystem.MLS.Removal import Wire.ConversationSubsystem.Util import Wire.ExternalAccess import Wire.NotificationSubsystem diff --git a/services/galley/src/Galley/API/Action/Notify.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Action/Notify.hs similarity index 76% rename from services/galley/src/Galley/API/Action/Notify.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/Action/Notify.hs index 77c8167ebc3..4a11ac20012 100644 --- a/services/galley/src/Galley/API/Action/Notify.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Action/Notify.hs @@ -15,24 +15,34 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.Action.Notify where +module Wire.ConversationSubsystem.Action.Notify where import Data.Id import Data.Qualified import Data.Singletons import Imports hiding ((\\)) import Polysemy +import Polysemy.Error import Wire.API.Conversation hiding (Conversation, Member) import Wire.API.Conversation.Action import Wire.API.Event.Conversation -import Wire.ConversationSubsystem +import Wire.API.Federation.Error +import Wire.BackendNotificationQueueAccess (BackendNotificationQueueAccess) +import Wire.ConversationSubsystem.Notify (notifyConversationActionImpl) import Wire.ConversationSubsystem.Util +import Wire.ExternalAccess (ExternalAccess) import Wire.NotificationSubsystem +import Wire.Sem.Now (Now) import Wire.StoredConversation sendConversationActionNotifications :: forall tag r. - (Member ConversationSubsystem r) => + ( Member BackendNotificationQueueAccess r, + Member ExternalAccess r, + Member (Error FederationError) r, + Member Now r, + Member NotificationSubsystem r + ) => Sing tag -> Qualified UserId -> Bool -> @@ -43,7 +53,7 @@ sendConversationActionNotifications :: ExtraConversationData -> Sem r LocalConversationUpdate sendConversationActionNotifications tag quid notifyOrigDomain con lconv targets action extraData = do - notifyConversationAction + notifyConversationActionImpl tag (EventFromUser quid) notifyOrigDomain diff --git a/services/galley/src/Galley/API/Action/Reset.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Action/Reset.hs similarity index 96% rename from services/galley/src/Galley/API/Action/Reset.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/Action/Reset.hs index 49614ab300d..c31399a3b4d 100644 --- a/services/galley/src/Galley/API/Action/Reset.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Action/Reset.hs @@ -15,15 +15,13 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.Action.Reset (resetLocalMLSMainConversation) where +module Wire.ConversationSubsystem.Action.Reset (resetLocalMLSMainConversation) where import Control.Monad.Codensity hiding (reset) import Data.Aeson qualified as A import Data.ByteString.Conversion (toByteString') import Data.Id import Data.Qualified -import Galley.API.Action.Kick -import Galley.API.MLS.Util import Imports import Polysemy import Polysemy.Error @@ -47,7 +45,8 @@ import Wire.API.Routes.Public.Galley.MLS import Wire.API.VersionInfo import Wire.BackendNotificationQueueAccess import Wire.ConversationStore -import Wire.ConversationSubsystem +import Wire.ConversationSubsystem.Action.Kick +import Wire.ConversationSubsystem.MLS.Util import Wire.ConversationSubsystem.Util import Wire.ExternalAccess import Wire.FederationAPIAccess @@ -65,7 +64,6 @@ resetLocalMLSMainConversation :: Member BackendNotificationQueueAccess r, Member (FederationAPIAccess FederatorClient) r, Member ExternalAccess r, - Member ConversationSubsystem r, Member NotificationSubsystem r, Member ProposalStore r, Member Random r, diff --git a/services/galley/src/Galley/API/Clients.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Clients.hs similarity index 93% rename from services/galley/src/Galley/API/Clients.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/Clients.hs index 7aa4c2f12e9..2b09f61a197 100644 --- a/services/galley/src/Galley/API/Clients.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Clients.hs @@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.Clients +module Wire.ConversationSubsystem.Clients ( getClients, rmClient, ) @@ -25,8 +25,6 @@ import Data.Id import Data.Proxy import Data.Qualified import Data.Range -import Galley.API.MLS.Removal -import Galley.API.Query qualified as Query import Galley.Types.Clients (clientIds) import Galley.Types.Error import Imports @@ -44,6 +42,7 @@ import Wire.API.Federation.Error import Wire.API.MLS.Keys (MLSKeysByPurpose, MLSPrivateKeys) import Wire.API.Routes.MultiTablePaging import Wire.BackendNotificationQueueAccess +import Wire.BrigAPIAccess import Wire.ConversationStore (ConversationStore, getConversation) import Wire.ConversationSubsystem qualified as ConvSubsystem import Wire.ExternalAccess (ExternalAccess) @@ -55,10 +54,13 @@ import Wire.UserClientIndexStore qualified as E import Wire.Util getClients :: - (Member ConvSubsystem.ConversationSubsystem r) => + ( Member BrigAPIAccess r, + Member E.UserClientIndexStore r, + Member (Input ConversationSubsystemConfig) r + ) => UserId -> Sem r [ClientId] -getClients usr = clientIds usr <$> ConvSubsystem.internalGetClientIds [usr] +getClients usr = clientIds usr <$> internalGetClientIds [usr] -- | Remove a client from conversations it is part of according to the -- conversation protocol (Proteus or MLS). In addition, remove the client from @@ -67,7 +69,6 @@ rmClient :: forall r. ( Member E.UserClientIndexStore r, Member ConversationStore r, - Member ConvSubsystem.ConversationSubsystem r, Member (Error FederationError) r, Member ExternalAccess r, Member BackendNotificationQueueAccess r, diff --git a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Create.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Create.hs new file mode 100644 index 00000000000..26c7fab9178 --- /dev/null +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Create.hs @@ -0,0 +1,258 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DuplicateRecordFields #-} + +-- This file is part of the Wire Server implementation. +-- +-- Copyright (C) 2022 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.ConversationSubsystem.Create where + +import Data.Id +import Data.Qualified +import Data.Set qualified as Set +import Galley.Types.Error +import Imports +import Polysemy +import Polysemy.Error +import Polysemy.Input +import Polysemy.TinyLog qualified as P +import Wire.API.Conversation hiding (Member) +import Wire.API.Conversation qualified as Public +import Wire.API.Conversation.Config +import Wire.API.Error +import Wire.API.Error.Galley +import Wire.API.Event.Conversation +import Wire.API.Federation.Client (FederatorClient) +import Wire.API.Federation.Error +import Wire.API.FederationStatus (RemoteDomains (..)) +import Wire.API.Routes.Public.Galley.Conversation +import Wire.API.Routes.Public.Util (ResponseForExistedCreated (..)) +import Wire.API.User +import Wire.BackendNotificationQueueAccess (BackendNotificationQueueAccess) +import Wire.BrigAPIAccess +import Wire.ConversationStore (ConversationStore) +import Wire.ConversationSubsystem.CreateInternal +import Wire.ConversationSubsystem.Mapping +import Wire.FeaturesConfigSubsystem +import Wire.FederationAPIAccess (FederationAPIAccess) +import Wire.FederationSubsystem (FederationSubsystem, checkFederationStatus, enforceFederationProtocol) +import Wire.LegalHoldStore (LegalHoldStore) +import Wire.NotificationSubsystem as NS +import Wire.Sem.Now (Now) +import Wire.Sem.Random (Random) +import Wire.TeamCollaboratorsSubsystem +import Wire.TeamStore (TeamStore) +import Wire.TeamSubsystem (TeamSubsystem) + +---------------------------------------------------------------------------- +-- API Handlers + +createGroupConversationUpToV3 :: + ( Member BrigAPIAccess r, + Member ConversationStore r, + Member (ErrorS 'ConvAccessDenied) r, + Member (Error InvalidInput) r, + Member (ErrorS 'NotATeamMember) r, + Member (ErrorS OperationDenied) r, + Member (ErrorS 'NotConnected) r, + Member (ErrorS 'MLSNotEnabled) r, + Member (ErrorS 'MLSNonEmptyMemberList) r, + Member (ErrorS 'MissingLegalholdConsent) r, + Member (ErrorS 'ChannelsNotEnabled) r, + Member (ErrorS 'NotAnMlsConversation) r, + Member (ErrorS HistoryNotSupported) r, + Member (Input ConversationSubsystemConfig) r, + Member LegalHoldStore r, + Member TeamStore r, + Member FeaturesConfigSubsystem r, + Member TeamCollaboratorsSubsystem r, + Member Random r, + Member TeamSubsystem r, + Member Now r, + Member NotificationSubsystem r, + Member (Error FederationError) r, + Member BackendNotificationQueueAccess r, + Member (FederationAPIAccess FederatorClient) r, + Member (Error UnreachableBackendsLegacy) r, + Member (Error InternalError) r, + Member P.TinyLog r + ) => + Local UserId -> + Maybe ConnId -> + NewConv -> + Sem r (ConversationResponse Public.OwnConversation) +createGroupConversationUpToV3 lusr conn newConv = mapError UnreachableBackendsLegacy $ do + dbConv <- createGroupConversationGeneric lusr conn newConv + Created <$> conversationViewV9 lusr dbConv + +createGroupOwnConversation :: + ( Member BrigAPIAccess r, + Member ConversationStore r, + Member (ErrorS 'ConvAccessDenied) r, + Member (Error InvalidInput) r, + Member (ErrorS 'NotATeamMember) r, + Member (ErrorS OperationDenied) r, + Member (ErrorS 'NotConnected) r, + Member (ErrorS 'MLSNotEnabled) r, + Member (ErrorS 'MLSNonEmptyMemberList) r, + Member (ErrorS 'MissingLegalholdConsent) r, + Member (ErrorS 'ChannelsNotEnabled) r, + Member (ErrorS 'NotAnMlsConversation) r, + Member (ErrorS HistoryNotSupported) r, + Member (Input ConversationSubsystemConfig) r, + Member LegalHoldStore r, + Member TeamStore r, + Member FeaturesConfigSubsystem r, + Member TeamCollaboratorsSubsystem r, + Member Random r, + Member TeamSubsystem r, + Member Now r, + Member NotificationSubsystem r, + Member (Error FederationError) r, + Member (Error UnreachableBackends) r, + Member BackendNotificationQueueAccess r, + Member (FederationAPIAccess FederatorClient) r, + Member (Error InternalError) r, + Member FederationSubsystem r, + Member P.TinyLog r + ) => + Local UserId -> + Maybe ConnId -> + NewConv -> + Sem r CreateGroupConversationResponseV9 +createGroupOwnConversation lusr conn newConv = do + let remoteDomains = void <$> snd (partitionQualified lusr $ newConv.newConvQualifiedUsers) + enforceFederationProtocol (baseProtocolToProtocol newConv.newConvProtocol) remoteDomains + checkFederationStatus (RemoteDomains $ Set.fromList remoteDomains) + dbConv <- createGroupConversationGeneric lusr conn newConv + GroupConversationCreatedV9 <$> (CreateGroupOwnConversation <$> conversationViewV9 lusr dbConv <*> pure mempty) + +createGroupConversation :: + ( Member BrigAPIAccess r, + Member ConversationStore r, + Member (ErrorS 'ConvAccessDenied) r, + Member (Error InvalidInput) r, + Member (ErrorS 'NotATeamMember) r, + Member (ErrorS OperationDenied) r, + Member (ErrorS 'NotConnected) r, + Member (ErrorS 'MLSNotEnabled) r, + Member (ErrorS 'MLSNonEmptyMemberList) r, + Member (ErrorS 'MissingLegalholdConsent) r, + Member (ErrorS 'ChannelsNotEnabled) r, + Member (ErrorS 'NotAnMlsConversation) r, + Member (ErrorS HistoryNotSupported) r, + Member (Input ConversationSubsystemConfig) r, + Member LegalHoldStore r, + Member TeamStore r, + Member FeaturesConfigSubsystem r, + Member TeamCollaboratorsSubsystem r, + Member Random r, + Member TeamSubsystem r, + Member Now r, + Member NotificationSubsystem r, + Member (Error FederationError) r, + Member (Error UnreachableBackends) r, + Member BackendNotificationQueueAccess r, + Member (FederationAPIAccess FederatorClient) r, + Member FederationSubsystem r + ) => + Local UserId -> + Maybe ConnId -> + NewConv -> + Sem r CreateGroupConversation +createGroupConversation lusr conn newConv = do + let remoteDomains = void <$> snd (partitionQualified lusr $ newConv.newConvQualifiedUsers) + enforceFederationProtocol (baseProtocolToProtocol newConv.newConvProtocol) remoteDomains + checkFederationStatus (RemoteDomains $ Set.fromList remoteDomains) + dbConv <- createGroupConversationGeneric lusr conn newConv + pure $ + CreateGroupConversation + { conversation = conversationView (qualifyAs lusr ()) (Just lusr) dbConv, + failedToAdd = mempty + } + +createProteusSelfConversation :: + ( Member ConversationStore r, + Member (Error InternalError) r, + Member P.TinyLog r + ) => + Local UserId -> + Sem r (ConversationResponse Public.OwnConversation) +createProteusSelfConversation lusr = do + (c, created) <- createProteusSelfConversationLogic lusr + if created + then Created <$> conversationViewV9 lusr c + else Existed <$> conversationViewV9 lusr c + +createOne2OneConversation :: + ( Member BrigAPIAccess r, + Member ConversationStore r, + Member (Error FederationError) r, + Member (Error UnreachableBackends) r, + Member (Error InvalidInput) r, + Member (ErrorS 'NotATeamMember) r, + Member (ErrorS OperationDenied) r, + Member (ErrorS 'NonBindingTeam) r, + Member (ErrorS 'NoBindingTeamMembers) r, + Member (ErrorS 'TeamNotFound) r, + Member (ErrorS 'InvalidOperation) r, + Member (ErrorS 'NotConnected) r, + Member TeamStore r, + Member TeamCollaboratorsSubsystem r, + Member TeamSubsystem r, + Member Now r, + Member NotificationSubsystem r, + Member BackendNotificationQueueAccess r, + Member (FederationAPIAccess FederatorClient) r, + Member (Error InternalError) r, + Member P.TinyLog r + ) => + Local UserId -> + ConnId -> + NewOne2OneConv -> + Sem r (ConversationResponse Public.OwnConversation) +createOne2OneConversation lusr zcon j = do + (c, created) <- createOne2OneConversationLogic lusr zcon j + if created + then Created <$> conversationViewV9 lusr c + else Existed <$> conversationViewV9 lusr c + +---------------------------------------------------------------------------- +-- Helpers + +createConnectConversation :: + ( Member ConversationStore r, + Member (Error FederationError) r, + Member (Error InternalError) r, + Member (Error InvalidInput) r, + Member (Error UnreachableBackends) r, + Member (ErrorS 'ConvNotFound) r, + Member (ErrorS 'InvalidOperation) r, + Member NotificationSubsystem r, + Member BackendNotificationQueueAccess r, + Member Now r, + Member (FederationAPIAccess FederatorClient) r, + Member P.TinyLog r + ) => + Local UserId -> + Maybe ConnId -> + Connect -> + Sem r (ConversationResponse Public.OwnConversation) +createConnectConversation lusr conn j = do + (c, created) <- createConnectConversationLogic lusr conn j + if created + then Created <$> conversationViewV9 lusr c + else Existed <$> conversationViewV9 lusr c diff --git a/services/galley/src/Galley/API/Teams/Features/Get.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Features.hs similarity index 99% rename from services/galley/src/Galley/API/Teams/Features/Get.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/Features.hs index 231aa5d3c85..02bb7168d63 100644 --- a/services/galley/src/Galley/API/Teams/Features/Get.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Features.hs @@ -17,7 +17,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.Teams.Features.Get +module Wire.ConversationSubsystem.Features ( getFeature, getFeatureInternal, getAllTeamFeaturesForServer, diff --git a/services/galley/src/Galley/API/Federation/Handlers.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Federation.hs similarity index 92% rename from services/galley/src/Galley/API/Federation/Handlers.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/Federation.hs index e631be0b10d..c454ee1a4db 100644 --- a/services/galley/src/Galley/API/Federation/Handlers.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Federation.hs @@ -3,7 +3,7 @@ -- This file is part of the Wire Server implementation. -- --- Copyright (C) 2026 Wire Swiss GmbH +-- Copyright (C) 2022 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 @@ -18,7 +18,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.Federation.Handlers where +module Wire.ConversationSubsystem.Federation where import Control.Error hiding (note) import Control.Lens @@ -36,21 +36,6 @@ import Data.Set qualified as Set import Data.Singletons (SingI (..), demote, sing) import Data.Tagged import Data.Text.Lazy qualified as LT -import Galley.API.Action -import Galley.API.MLS -import Galley.API.MLS.Enabled -import Galley.API.MLS.GroupInfo -import Galley.API.MLS.GroupInfoCheck (GroupInfoCheckEnabled) -import Galley.API.MLS.Message -import Galley.API.MLS.One2One -import Galley.API.MLS.Removal -import Galley.API.MLS.SubConversation hiding (leaveSubConversation) -import Galley.API.MLS.Util -import Galley.API.MLS.Welcome -import Galley.API.Mapping -import Galley.API.Mapping qualified as Mapping -import Galley.API.Message -import Galley.Options import Galley.Types.Conversations.One2One import Galley.Types.Error import Imports @@ -84,12 +69,26 @@ import Wire.API.Message import Wire.API.Push.V2 (RecipientClients (..)) import Wire.API.Routes.Public.Galley.MLS import Wire.API.ServantProto +import Wire.API.Team.FeatureFlags (FeatureFlags) import Wire.API.User (BaseProtocolTag (..)) import Wire.BackendNotificationQueueAccess import Wire.BrigAPIAccess (BrigAPIAccess) import Wire.CodeStore import Wire.ConversationStore qualified as E -import Wire.ConversationSubsystem +import Wire.ConversationSubsystem.Action +import Wire.ConversationSubsystem.MLS.Enabled +import Wire.ConversationSubsystem.MLS.GroupInfo +import Wire.ConversationSubsystem.MLS.GroupInfoCheck (GroupInfoCheckEnabled) +import Wire.ConversationSubsystem.MLS.IncomingMessage +import Wire.ConversationSubsystem.MLS.Message +import Wire.ConversationSubsystem.MLS.One2One +import Wire.ConversationSubsystem.MLS.Removal +import Wire.ConversationSubsystem.MLS.SubConversation hiding (leaveSubConversation) +import Wire.ConversationSubsystem.MLS.Util +import Wire.ConversationSubsystem.MLS.Welcome +import Wire.ConversationSubsystem.Mapping +import Wire.ConversationSubsystem.Mapping qualified as Mapping +import Wire.ConversationSubsystem.Message import Wire.ConversationSubsystem.Util import Wire.ExternalAccess (ExternalAccess) import Wire.FeaturesConfigSubsystem @@ -192,7 +191,7 @@ getConversationsV1 :: GetConversationsRequest -> Sem r GetConversationsResponse getConversationsV1 domain req = - getConversationsResponseFromV2 <$> Galley.API.Federation.Handlers.getConversations domain req + getConversationsResponseFromV2 <$> getConversations domain req getConversations :: ( Member E.ConversationStore r, @@ -246,7 +245,6 @@ leaveConversation :: Member E.ConversationStore r, Member (Error InternalError) r, Member ExternalAccess r, - Member ConversationSubsystem r, Member NotificationSubsystem r, Member (Input (Local ())) r, Member Now r, @@ -365,13 +363,14 @@ onMessageSent domain rmUnqualified = do sendMessage :: ( Member BrigAPIAccess r, Member UserClientIndexStore r, + Member (Input IntraListing) r, + Member (Input FeatureFlags) r, Member E.ConversationStore r, Member (Error InvalidInput) r, Member (FederationAPIAccess FederatorClient) r, Member BackendNotificationQueueAccess r, Member NotificationSubsystem r, Member (Input (Local ())) r, - Member (Input Opts) r, Member Now r, Member ExternalAccess r, Member TeamSubsystem r, @@ -394,7 +393,6 @@ onUserDeleted :: Member E.FireAndForget r, Member (Error FederationError) r, Member ExternalAccess r, - Member ConversationSubsystem r, Member NotificationSubsystem r, Member (Input (Local ())) r, Member Now r, @@ -456,7 +454,6 @@ updateConversation :: Member (Error InvalidInput) r, Member ExternalAccess r, Member (FederationAPIAccess FederatorClient) r, - Member ConversationSubsystem r, Member NotificationSubsystem r, Member Now r, Member LegalHoldStore r, @@ -559,6 +556,15 @@ updateConversation origDomain updateRequest = do . runError @UnreachableBackends . fmap ConversationUpdateResponseUpdate +type MLSBundleStaticErrors = + Append + MLSMessageStaticErrors + '[ ErrorS 'MLSWelcomeMismatch, + ErrorS 'MLSIdentityMismatch, + ErrorS 'GroupIdVersionNotSupported, + ErrorS 'MLSInvalidLeafNodeSignature + ] + handleMLSMessageErrors :: ( r1 ~ Append @@ -599,8 +605,11 @@ sendMLSCommitBundle :: Member ExternalAccess r, Member (Error FederationError) r, Member (Error InternalError) r, + Member (ErrorS 'MLSGroupConversationMismatch) r, + Member (ErrorS 'MLSClientMismatch) r, + Member (ErrorS 'MLSInvalidLeafNodeIndex) r, + Member (ErrorS 'MLSUnsupportedProposal) r, Member (FederationAPIAccess FederatorClient) r, - Member ConversationSubsystem r, Member NotificationSubsystem r, Member (Input (Local ())) r, Member (Input (Maybe GroupInfoCheckEnabled)) r, @@ -635,25 +644,25 @@ sendMLSCommitBundle remoteDomain msr = handleMLSMessageErrors $ do when (qUnqualified qConvOrSub /= msr.convOrSubId) $ throwS @'MLSGroupConversationMismatch -- this cannot throw the error since we always pass the sender which is qualified to be remote - runInputConst (fromMaybe def msr.enableOutOfSyncCheck) $ - MLSMessageResponseUpdates - . fmap lcuUpdate - <$> mapToRuntimeError @MLSLegalholdIncompatible - (InternalErrorWithDescription "expected group conversation while handling policy conflicts") - ( postMLSCommitBundle - loc - -- Type application to prevent future changes from introducing errors. - -- It is only safe to assume that we can discard the error when the sender - -- is actually remote. - -- Since `tUntagged` works on local and remote, a future changed may - -- go unchecked without this. - (tUntagged @QRemote sender) - msr.senderClient - ctype - qConvOrSub - Nothing - ibundle - ) + MLSMessageResponseUpdates + . fmap lcuUpdate + <$> mapToRuntimeError @MLSLegalholdIncompatible + (InternalErrorWithDescription "expected group conversation while handling policy conflicts") + ( postMLSCommitBundle + loc + -- Type application to prevent future changes from introducing errors. + -- It is only safe to assume that we can discard the error when the sender + -- is actually remote. + -- Since `tUntagged` works on local and remote, a future changed may + -- go unchecked without this. + (tUntagged @QRemote sender) + msr.senderClient + ctype + qConvOrSub + Nothing + (fromMaybe def msr.enableOutOfSyncCheck) + ibundle + ) sendMLSMessage :: ( Member BackendNotificationQueueAccess r, @@ -663,6 +672,10 @@ sendMLSMessage :: Member (Error FederationError) r, Member (Error InternalError) r, Member (FederationAPIAccess FederatorClient) r, + Member (ErrorS 'MLSGroupConversationMismatch) r, + Member (ErrorS 'MLSClientMismatch) r, + Member (ErrorS 'MLSInvalidLeafNodeIndex) r, + Member (ErrorS 'MLSUnsupportedProposal) r, Member NotificationSubsystem r, Member (Input (Local ())) r, Member (Input (Maybe (MLSKeysByPurpose MLSPrivateKeys))) r, @@ -684,16 +697,16 @@ sendMLSMessage remoteDomain msr = handleMLSMessageErrors $ do msg <- noteS @'MLSUnsupportedMessage $ mkIncomingMessage raw (ctype, qConvOrSub) <- getConvFromGroupId msg.groupId when (qUnqualified qConvOrSub /= msr.convOrSubId) $ throwS @'MLSGroupConversationMismatch - runInputConst (fromMaybe def msr.enableOutOfSyncCheck) $ - MLSMessageResponseUpdates . map lcuUpdate - <$> postMLSMessage - loc - (tUntagged sender) - msr.senderClient - ctype - qConvOrSub - Nothing - msg + MLSMessageResponseUpdates . map lcuUpdate + <$> postMLSMessage + loc + (tUntagged sender) + msr.senderClient + ctype + qConvOrSub + Nothing + (fromMaybe def msr.enableOutOfSyncCheck) + msg getSubConversationForRemoteUser :: ( Member E.ConversationStore r, diff --git a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Internal.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Internal.hs index 68bf8c32a3d..2d36e8eeb7c 100644 --- a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Internal.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Internal.hs @@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Wire.ConversationSubsystem.Internal (internalGetClientIdsImpl) where +module Wire.ConversationSubsystem.Internal (internalGetClientIds) where import Data.Id import Galley.Types.Clients (Clients, fromUserClients) @@ -27,14 +27,14 @@ import Wire.BrigAPIAccess import Wire.UserClientIndexStore (UserClientIndexStore) import Wire.UserClientIndexStore qualified as UserClientIndexStore -internalGetClientIdsImpl :: +internalGetClientIds :: ( Member BrigAPIAccess r, Member UserClientIndexStore r, Member (Input ConversationSubsystemConfig) r ) => [UserId] -> Sem r Clients -internalGetClientIdsImpl users = do +internalGetClientIds users = do isInternal <- inputs (.listClientsUsingBrig) if isInternal then fromUserClients <$> lookupClients users diff --git a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Interpreter.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Interpreter.hs index 9a3d9270bc4..a031f31d721 100644 --- a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Interpreter.hs @@ -19,39 +19,79 @@ module Wire.ConversationSubsystem.Interpreter ( interpretConversationSubsystem, + GroupInfoCheckEnabled (..), + IntraListing (..), + Update.GuestLinkTTLSeconds (..), ) where +import Data.Proxy (Proxy (..)) +import Data.Qualified +import Data.Singletons (fromSing) import Galley.Types.Error (InternalError, InvalidInput (..)) import Imports import Polysemy import Polysemy.Error import Polysemy.Input +import Polysemy.Internal.Tactics (liftT) +import Polysemy.Resource (Resource) +import Polysemy.TinyLog (TinyLog) import Wire.API.Conversation.Config +import Wire.API.Conversation.Role qualified as ConvRole import Wire.API.Error import Wire.API.Error.Galley import Wire.API.Federation.Client (FederatorClient) import Wire.API.Federation.Error +import Wire.API.MLS.Keys (MLSKeysByPurpose, MLSPrivateKeys) +import Wire.API.Team.Feature (LegalholdConfig) +import Wire.API.Team.FeatureFlags (FanoutLimit, FeatureDefaults, FeatureFlags) import Wire.BackendNotificationQueueAccess (BackendNotificationQueueAccess) import Wire.BrigAPIAccess +import Wire.CodeStore (CodeStore) import Wire.ConversationStore (ConversationStore) import Wire.ConversationStore qualified as ConvStore import Wire.ConversationSubsystem +import Wire.ConversationSubsystem.Action.Notify qualified as ActionNotify +import Wire.ConversationSubsystem.Clients qualified as Clients +import Wire.ConversationSubsystem.Create qualified as Create import Wire.ConversationSubsystem.CreateInternal qualified as CreateInternal +import Wire.ConversationSubsystem.Features qualified as Features +import Wire.ConversationSubsystem.Federation qualified as Federation import Wire.ConversationSubsystem.Fetch qualified as Fetch import Wire.ConversationSubsystem.Internal qualified as Internal +import Wire.ConversationSubsystem.Legalhold qualified as Legalhold +import Wire.ConversationSubsystem.MLS qualified as MLS +import Wire.ConversationSubsystem.MLS.Enabled qualified as MLSEnabled +import Wire.ConversationSubsystem.MLS.GroupInfo qualified as MLSGroupInfo +import Wire.ConversationSubsystem.MLS.GroupInfoCheck (GroupInfoCheckEnabled (..)) +import Wire.ConversationSubsystem.MLS.Message qualified as MLSMessage +import Wire.ConversationSubsystem.MLS.Removal qualified as MLSRemoval +import Wire.ConversationSubsystem.MLS.Reset qualified as MLSReset +import Wire.ConversationSubsystem.MLS.SubConversation qualified as MLSSubConversation +import Wire.ConversationSubsystem.Message (IntraListing (..)) import Wire.ConversationSubsystem.Notify qualified as Notify +import Wire.ConversationSubsystem.One2One qualified as One2One +import Wire.ConversationSubsystem.Query qualified as Query +import Wire.ConversationSubsystem.Update qualified as Update +import Wire.ConversationSubsystem.Util qualified as Util import Wire.ExternalAccess (ExternalAccess) import Wire.FeaturesConfigSubsystem +import Wire.FeaturesConfigSubsystem.Types (ExposeInvitationURLsAllowlist) import Wire.FederationAPIAccess (FederationAPIAccess) +import Wire.FederationSubsystem (FederationSubsystem) +import Wire.FireAndForget (FireAndForget) +import Wire.HashPassword (HashPassword) import Wire.LegalHoldStore (LegalHoldStore) import Wire.NotificationSubsystem as NS +import Wire.ProposalStore (ProposalStore) +import Wire.RateLimit (RateLimit) import Wire.Sem.Now (Now) import Wire.Sem.Random (Random) import Wire.TeamCollaboratorsSubsystem import Wire.TeamStore (TeamStore) import Wire.TeamSubsystem (TeamSubsystem) import Wire.UserClientIndexStore (UserClientIndexStore) +import Wire.UserGroupStore (UserGroupStore) interpretConversationSubsystem :: ( Member (Error FederationError) r, @@ -61,6 +101,8 @@ interpretConversationSubsystem :: Member (ErrorS 'ConvAccessDenied) r, Member (ErrorS 'NotATeamMember) r, Member (ErrorS OperationDenied) r, + Member (Error AuthenticationError) r, + Member (Input (FeatureDefaults LegalholdConfig)) r, Member (ErrorS 'NotConnected) r, Member (ErrorS 'MLSNotEnabled) r, Member (ErrorS 'MLSNonEmptyMemberList) r, @@ -72,7 +114,62 @@ interpretConversationSubsystem :: Member (ErrorS 'ConvNotFound) r, Member (ErrorS 'ChannelsNotEnabled) r, Member (ErrorS 'NotAnMlsConversation) r, - Member (ErrorS HistoryNotSupported) r, + Member (ErrorS 'MLSLegalholdIncompatible) r, + Member (ErrorS 'MLSIdentityMismatch) r, + Member (ErrorS 'MLSUnsupportedMessage) r, + Member (ErrorS 'MLSStaleMessage) r, + Member (ErrorS 'MLSProposalNotFound) r, + Member (ErrorS 'MLSCommitMissingReferences) r, + Member (ErrorS 'MLSSelfRemovalNotAllowed) r, + Member (ErrorS 'MLSClientSenderUserMismatch) r, + Member (ErrorS 'MLSSubConvClientNotInParent) r, + Member (ErrorS 'MLSInvalidLeafNodeSignature) r, + Member (ErrorS 'MLSClientMismatch) r, + Member (ErrorS 'MLSInvalidLeafNodeIndex) r, + Member (ErrorS 'MLSUnsupportedProposal) r, + Member (Error MLSProtocolError) r, + Member (Error GroupInfoDiagnostics) r, + Member (Error MLSOutOfSyncError) r, + Member (Error MLSProposalFailure) r, + Member (Error NonFederatingBackends) r, + Member (ErrorS 'GroupIdVersionNotSupported) r, + Member (ErrorS 'ConvMemberNotFound) r, + Member (ErrorS 'HistoryNotSupported) r, + Member (Error UnreachableBackendsLegacy) r, + Member (ErrorS MLSGroupConversationMismatch) r, + Member (ErrorS ('ActionDenied ConvRole.LeaveConversation)) r, + Member (ErrorS ('ActionDenied ConvRole.RemoveConversationMember)) r, + Member (ErrorS ('ActionDenied ConvRole.DeleteConversation)) r, + Member (ErrorS 'BroadcastLimitExceeded) r, + Member (ErrorS 'MLSFederatedResetNotSupported) r, + Member (ErrorS 'MLSSubConvUnsupportedConvType) r, + Member (ErrorS 'TeamMemberNotFound) r, + Member (ErrorS 'AccessDenied) r, + Member (ErrorS 'MLSMissingGroupInfo) r, + Member (ErrorS 'CodeNotFound) r, + Member (ErrorS 'InvalidConversationPassword) r, + Member (ErrorS 'GuestLinksDisabled) r, + Member (ErrorS 'MLSFederatedOne2OneNotSupported) r, + Member (ErrorS 'TooManyMembers) r, + Member (ErrorS 'CreateConversationCodeConflict) r, + Member (ErrorS 'InvalidTarget) r, + Member (ErrorS 'MLSReadReceiptsNotAllowed) r, + Member (ErrorS 'InvalidTargetAccess) r, + Member (ErrorS 'ConvInvalidProtocolTransition) r, + Member (ErrorS 'MLSMigrationCriteriaNotSatisfied) r, + Member (ErrorS ('ActionDenied ConvRole.AddConversationMember)) r, + Member (ErrorS ('ActionDenied ConvRole.ModifyOtherConversationMember)) r, + Member (ErrorS ('ActionDenied ConvRole.ModifyConversationName)) r, + Member (ErrorS ('ActionDenied ConvRole.ModifyConversationMessageTimer)) r, + Member (ErrorS ('ActionDenied ConvRole.ModifyConversationReceiptMode)) r, + Member (ErrorS ('ActionDenied ConvRole.ModifyConversationAccess)) r, + Member (ErrorS ('ActionDenied ConvRole.ModifyAddPermission)) r, + Member UserGroupStore r, + Member (Input (Maybe Update.GuestLinkTTLSeconds)) r, + Member HashPassword r, + Member RateLimit r, + Member CodeStore r, + Member FireAndForget r, Member BackendNotificationQueueAccess r, Member NotificationSubsystem r, Member ExternalAccess r, @@ -84,29 +181,298 @@ interpretConversationSubsystem :: Member TeamCollaboratorsSubsystem r, Member Random r, Member TeamSubsystem r, + Member (Input FeatureFlags) r, + Member (Input IntraListing) r, Member (Input ConversationSubsystemConfig) r, + Member (Input (Local ())) r, + Member (Input (Maybe GroupInfoCheckEnabled)) r, + Member ProposalStore r, Member LegalHoldStore r, + Member (Input ExposeInvitationURLsAllowlist) r, Member TeamStore r, - Member UserClientIndexStore r + Member ConvStore.MLSCommitLockStore r, + Member FederationSubsystem r, + Member Resource r, + Member (Input (Maybe (MLSKeysByPurpose MLSPrivateKeys))) r, + Member UserClientIndexStore r, + Member (Input FanoutLimit) r, + Member TinyLog r ) => Sem (ConversationSubsystem : r) a -> Sem r a -interpretConversationSubsystem = interpret $ \case +interpretConversationSubsystem = interpretH $ \case NotifyConversationAction tag quid notifyOrigDomain con lconv targetsLocal targetsRemote targetsBots action extraData -> - Notify.notifyConversationActionImpl tag quid notifyOrigDomain con lconv targetsLocal targetsRemote targetsBots action extraData + liftT $ Notify.notifyConversationActionImpl tag quid notifyOrigDomain con lconv targetsLocal targetsRemote targetsBots action extraData + InternalCreateGroupConversation lusr conn newConv -> + liftT $ CreateInternal.createGroupConversationGeneric lusr conn newConv + CreateGroupConversationUpToV3 lusr conn newConv -> + liftT $ Create.createGroupConversationUpToV3 lusr conn newConv + CreateGroupOwnConversation lusr conn newConv -> + liftT $ Create.createGroupOwnConversation lusr conn newConv CreateGroupConversation lusr conn newConv -> - CreateInternal.createGroupConversationGeneric lusr conn newConv - CreateOne2OneConversation lusr conn newOne2One -> - CreateInternal.createOne2OneConversationLogic lusr conn newOne2One + liftT $ Create.createGroupConversation lusr conn newConv CreateProteusSelfConversation lusr -> - CreateInternal.createProteusSelfConversationLogic lusr + liftT $ Create.createProteusSelfConversation lusr + CreateOne2OneConversation lusr zcon j -> + liftT $ Create.createOne2OneConversation lusr zcon j CreateConnectConversation lusr conn j -> - CreateInternal.createConnectConversationLogic lusr conn j + liftT $ Create.createConnectConversation lusr conn j GetConversations convIds -> - ConvStore.getConversations convIds + liftT $ ConvStore.getConversations convIds GetConversationIds lusr maxIds pagingState -> - Fetch.getConversationIdsImpl lusr maxIds pagingState + liftT $ Fetch.getConversationIdsImpl lusr maxIds pagingState InternalGetClientIds uids -> - Internal.internalGetClientIdsImpl uids + liftT $ Internal.internalGetClientIds uids InternalGetLocalMember cid uid -> - ConvStore.getLocalMember cid uid + liftT $ ConvStore.getLocalMember cid uid + PostMLSCommitBundle loc qusr c ctype qConvOrSub conn oosCheck bundle -> + liftT $ MLSMessage.postMLSCommitBundle loc qusr c ctype qConvOrSub conn oosCheck bundle + PostMLSCommitBundleFromLocalUser v lusr c conn bundle -> + liftT $ MLSMessage.postMLSCommitBundleFromLocalUser v lusr c conn bundle + PostMLSMessage loc qusr c ctype qconvOrSub con oosCheck msg -> + liftT $ MLSMessage.postMLSMessage loc qusr c ctype qconvOrSub con oosCheck msg + PostMLSMessageFromLocalUser v lusr c conn smsg -> + liftT $ MLSMessage.postMLSMessageFromLocalUser v lusr c conn smsg + IsMLSEnabled -> + liftT $ MLSEnabled.isMLSEnabled + IterateConversations luid pageSize handleConvs -> do + handleConvsT <- bindT handleConvs + ins <- getInitialStateT + void $ raise $ interpretConversationSubsystem $ Query.iterateConversations luid pageSize $ handleConvsT . ($>) ins + pureT () + RemoveMemberFromLocalConv lcnv lusr con victim -> + liftT $ Update.removeMemberFromLocalConv lcnv lusr con victim + FederationOnConversationCreated domain rc -> + liftT $ Federation.onConversationCreated domain rc + FederationGetConversationsV1 domain req -> + liftT $ Federation.getConversationsV1 domain req + FederationGetConversations domain req -> + liftT $ Federation.getConversations domain req + FederationLeaveConversation domain lc -> + liftT $ Federation.leaveConversation domain lc + FederationSendMessage domain msr -> + liftT $ Federation.sendMessage domain msr + FederationUpdateConversation domain uc -> + liftT $ Federation.updateConversation domain uc + FederationMlsSendWelcome domain req -> + liftT $ Federation.mlsSendWelcome domain req + FederationSendMLSMessage domain msr -> + liftT $ Federation.sendMLSMessage domain msr + FederationSendMLSCommitBundle domain msr -> + liftT $ Federation.sendMLSCommitBundle domain msr + FederationQueryGroupInfo domain req -> + liftT $ Federation.queryGroupInfo domain req + FederationUpdateTypingIndicator domain req -> + liftT $ Federation.updateTypingIndicator domain req + FederationOnTypingIndicatorUpdated domain td -> + liftT $ Federation.onTypingIndicatorUpdated domain td + FederationGetSubConversationForRemoteUser domain req -> + liftT $ Federation.getSubConversationForRemoteUser domain req + FederationDeleteSubConversationForRemoteUser domain req -> + liftT $ Federation.deleteSubConversationForRemoteUser domain req + FederationLeaveSubConversation domain lscr -> + liftT $ Federation.leaveSubConversation domain lscr + FederationGetOne2OneConversationV1 domain req -> + liftT $ Federation.getOne2OneConversationV1 domain req + FederationGetOne2OneConversation domain req -> + liftT $ Federation.getOne2OneConversation domain req + FederationOnClientRemoved domain req -> + liftT $ Federation.onClientRemoved domain req + FederationOnMessageSent domain rm -> + liftT $ Federation.onMessageSent domain rm + FederationOnMLSMessageSent domain rmm -> + liftT $ Federation.onMLSMessageSent domain rmm + FederationOnConversationUpdatedV0 domain cu -> + liftT $ Federation.onConversationUpdatedV0 domain cu + FederationOnConversationUpdated domain cu -> + liftT $ Federation.onConversationUpdated domain cu + FederationOnUserDeleted domain udcn -> + liftT $ Federation.onUserDeleted domain udcn + PostOtrMessageUnqualified lusr con cnv ignore report msg -> + liftT $ Update.postOtrMessageUnqualified lusr con cnv ignore report msg + PostOtrBroadcastUnqualified lusr con ignore report msg -> + liftT $ Update.postOtrBroadcastUnqualified lusr con ignore report msg + PostProteusMessage lusr con cnv msg -> + liftT $ Update.postProteusMessage lusr con cnv msg + PostProteusBroadcast lusr con msg -> + liftT $ Update.postProteusBroadcast lusr con msg + DeleteLocalConversation lusr con lcnv -> + liftT $ Update.deleteLocalConversation lusr con lcnv + GetMLSPublicKeys fmt -> + liftT $ MLS.getMLSPublicKeys fmt + ResetMLSConversation lusr reset -> + liftT $ MLSReset.resetMLSConversation lusr reset + GetSubConversation lusr cnv sub -> + liftT $ MLSSubConversation.getSubConversation lusr cnv sub + GetUserStatus lusr tid uid -> + liftT $ Legalhold.getUserStatus lusr tid uid + GuardSecondFactorDisabled uid cid -> + liftT $ Features.guardSecondFactorDisabled uid cid + GetBotConversation bid cnv -> + liftT $ Query.getBotConversation bid cnv + GetUnqualifiedOwnConversation lusr cnv -> + liftT $ Query.getUnqualifiedOwnConversation lusr cnv + GetOwnConversation lusr qcnv -> + liftT $ Query.getOwnConversation lusr qcnv + GetConversation lusr qcnv -> + liftT $ Query.getConversation lusr qcnv + GetConversationRoles lusr cnv -> + liftT $ Query.getConversationRoles lusr cnv + GetGroupInfo lusr qcnv -> + liftT $ MLSGroupInfo.getGroupInfo lusr qcnv + ConversationIdsPageFromUnqualified lusr mstart msize -> + liftT $ Query.conversationIdsPageFromUnqualified lusr mstart msize + ConversationIdsPageFromV2 listGlobalSelf lself req -> + liftT $ Query.conversationIdsPageFromV2 listGlobalSelf lself req + ConversationIdsPageFrom lusr req -> + liftT $ Query.conversationIdsPageFrom lusr req + ListConversations luser req -> + liftT $ Query.listConversations luser req + GetConversationByReusableCode lusr key value -> + liftT $ Query.getConversationByReusableCode lusr key value + GetMLSSelfConversationWithError lusr -> + liftT $ Query.getMLSSelfConversationWithError lusr + GetMLSOne2OneConversationV5 lself qother -> + liftT $ Query.getMLSOne2OneConversationV5 lself qother + GetMLSOne2OneConversationV6 lself qother -> + liftT $ Query.getMLSOne2OneConversationV6 lself qother + GetMLSOne2OneConversation lself qother fmt -> + liftT $ Query.getMLSOne2OneConversation lself qother fmt + GetLocalSelf lusr cnv -> + liftT $ Query.getLocalSelf lusr cnv + GetSelfMember lusr qcnv -> + liftT $ Query.getSelfMember lusr qcnv + GetConversationGuestLinksStatus uid cid -> + liftT $ Query.getConversationGuestLinksStatus uid cid + GetCode mcode lusr cnv -> + liftT $ Update.getCode mcode lusr cnv + AddMembersUnqualified lusr con cnv invite -> + liftT $ Update.addMembersUnqualified lusr con cnv invite + AddMembersUnqualifiedV2 lusr con cnv invite -> + liftT $ Update.addMembersUnqualifiedV2 lusr con cnv invite + AddMembers lusr zcon qcnv invite -> + liftT $ Update.addMembers lusr zcon qcnv invite + ReplaceMembers lusr zcon qcnv invite -> + liftT $ Update.replaceMembers lusr zcon qcnv invite + JoinConversationById lusr con cnv -> + liftT $ Update.joinConversationById lusr con cnv + JoinConversationByReusableCode lusr con req -> + liftT $ Update.joinConversationByReusableCode lusr con req + CheckReusableCode addr code -> + liftT $ Update.checkReusableCode addr code + AddCodeUnqualified mReq usr mbZHost mZcon cnv -> + liftT $ Update.addCodeUnqualified mReq usr mbZHost mZcon cnv + AddCodeUnqualifiedWithReqBody lusr mname mconn cnv req -> + liftT $ Update.addCodeUnqualifiedWithReqBody lusr mname mconn cnv req + RmCodeUnqualified lusr con cnv -> + liftT $ Update.rmCodeUnqualified lusr con cnv + MemberTypingUnqualified lusr con cnv status -> + liftT $ Update.memberTypingUnqualified lusr con cnv status + MemberTyping lusr con qcnv status -> + liftT $ Update.memberTyping lusr con qcnv status + RemoveMemberUnqualified lusr con cnv uid -> + liftT $ Update.removeMemberUnqualified lusr con cnv uid + RemoveMemberQualified lusr con qcnv quid -> + liftT $ Update.removeMemberQualified lusr con qcnv quid + UpdateOtherMemberUnqualified lusr con cnv uid update -> + liftT $ Update.updateOtherMemberUnqualified lusr con cnv uid update + UpdateOtherMember lusr con qcnv quid update -> + liftT $ Update.updateOtherMember lusr con qcnv quid update + UpdateUnqualifiedConversationName lusr con cnv rename -> + liftT $ Update.updateUnqualifiedConversationName lusr con cnv rename + UpdateConversationName lusr zcon qcnv rename -> + liftT $ Update.updateConversationName lusr zcon qcnv rename + UpdateConversationMessageTimerUnqualified lusr con cnv update -> + liftT $ Update.updateConversationMessageTimerUnqualified lusr con cnv update + UpdateConversationMessageTimer lusr zcon qcnv update -> + liftT $ Update.updateConversationMessageTimer lusr zcon qcnv update + UpdateConversationReceiptModeUnqualified lusr con cnv update -> + liftT $ Update.updateConversationReceiptModeUnqualified lusr con cnv update + UpdateConversationReceiptMode lusr zcon qcnv update -> + liftT $ Update.updateConversationReceiptMode lusr zcon qcnv update + UpdateConversationAccessUnqualified lusr con cnv update -> + liftT $ Update.updateConversationAccessUnqualified lusr con cnv update + UpdateConversationAccess lusr zcon qcnv update -> + liftT $ Update.updateConversationAccess lusr zcon qcnv update + UpdateConversationHistory lusr zcon qcnv update -> + liftT $ Update.updateConversationHistory lusr zcon qcnv update + UpdateUnqualifiedSelfMember lusr con cnv update -> + liftT $ Update.updateUnqualifiedSelfMember lusr con cnv update + UpdateSelfMember lusr zcon qcnv update -> + liftT $ Update.updateSelfMember lusr zcon qcnv update + UpdateConversationProtocolWithLocalUser lusr conn qcnv update -> + liftT $ Update.updateConversationProtocolWithLocalUser lusr conn qcnv update + UpdateChannelAddPermission lusr conn qcnv update -> + liftT $ Update.updateChannelAddPermission lusr conn qcnv update + PostBotMessageUnqualified bid cnv ignore report msg -> + liftT $ Update.postBotMessageUnqualified bid cnv ignore report msg + DeleteSubConversation lusr qcnv sub reset -> + liftT $ MLSSubConversation.deleteSubConversation lusr qcnv sub reset + GetSubConversationGroupInfo lusr qcnv sub -> + liftT $ MLSSubConversation.getSubConversationGroupInfo lusr qcnv sub + LeaveSubConversation lusr cli qcnv sub -> + liftT $ MLSSubConversation.leaveSubConversation lusr cli qcnv sub + SendConversationActionNotifications tag quid notifyOrigDomain con lconv targets action extraData -> + liftT $ ActionNotify.sendConversationActionNotifications tag quid notifyOrigDomain con lconv targets action extraData + GetPaginatedConversations lusr mids mstart msize -> + liftT $ Query.getConversations lusr mids mstart msize + SearchChannels lusr tid searchString sortOrder pageSize lastName lastId discoverable -> + liftT $ Query.searchChannels lusr tid searchString sortOrder pageSize lastName lastId discoverable + FeatureEnabledForTeam (Proxy :: Proxy cfg) tid -> + liftT $ Features.featureEnabledForTeam @cfg tid + GetAllTeamFeaturesForUser uid -> + liftT $ Features.getAllTeamFeaturesForUser uid + GetSingleFeatureForUser uid -> + liftT $ Features.getSingleFeatureForUser uid + PermissionCheck p mTeam -> + liftT $ Util.permissionCheck p mTeam + PermissionCheckSAbs (PermissionCheckArgs p mTeam) -> + liftT $ Util.permissionCheck (fromSing p) mTeam + EnsureReAuthorised u secret mbAction mbCode -> + liftT $ Util.ensureReAuthorised u secret mbAction mbCode + QualifyLocal a -> + liftT $ Util.qualifyLocal a + AssertOnTeam uid tid -> + liftT $ Util.assertOnTeam uid tid + CheckConsent teamsOfUsers other -> + liftT $ Util.checkConsent teamsOfUsers other + GetLHStatusForUsers uids -> + liftT $ Util.getLHStatusForUsers uids + EnsureConnectedToLocals u uids -> + liftT $ Util.ensureConnectedToLocals u uids + GetTeamMembersForFanout tid -> + liftT $ Util.getTeamMembersForFanout tid + AssertTeamExists tid -> + liftT $ Util.assertTeamExists tid + InternalGetMember qcnv usr -> + liftT $ Query.internalGetMember qcnv usr + GetConversationMeta cnv -> + liftT $ Query.getConversationMeta cnv + GetMLSOne2OneConversationInternal lself qother -> + liftT $ Query.getMLSOne2OneConversationInternal lself qother + IsMLSOne2OneEstablished lself qother -> + liftT $ Query.isMLSOne2OneEstablished lself qother + GetLocalConversationInternal cid -> + liftT $ Query.getLocalConversationInternal cid + RmClient usr cid -> + liftT $ Clients.rmClient usr cid + GetClients usr -> + liftT $ Clients.getClients usr + AddBot lusr zcon b -> + liftT $ Update.addBot lusr zcon b + RmBot lusr zcon b -> + liftT $ Update.rmBot lusr zcon b + GetFeatureInternal tid -> + liftT $ Features.getFeatureInternal tid + UpdateCellsState cnv state -> + liftT $ Update.updateCellsState cnv state + RemoveUser lc includeMain qusr -> + liftT $ MLSRemoval.removeUser lc includeMain qusr + InternalUpsertOne2OneConversation req -> + liftT $ One2One.internalUpsertOne2OneConversation req + AcceptConv lusr conn cnv -> + liftT $ Update.acceptConv lusr conn cnv + BlockConv lusr qcnv -> + liftT $ Update.blockConv lusr qcnv + UnblockConv lusr conn qcnv -> + liftT $ Update.unblockConv lusr conn qcnv diff --git a/services/galley/src/Galley/API/LegalHold/Get.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Legalhold.hs similarity index 97% rename from services/galley/src/Galley/API/LegalHold/Get.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/Legalhold.hs index bc3c036beb7..1cdc1122013 100644 --- a/services/galley/src/Galley/API/LegalHold/Get.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Legalhold.hs @@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.LegalHold.Get (getUserStatus) where +module Wire.ConversationSubsystem.Legalhold (getUserStatus) where import Control.Lens (view) import Data.ByteString.Conversion (toByteString') diff --git a/services/galley/src/Galley/API/LegalHold/Conflicts.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/LegalholdConflicts.hs similarity index 96% rename from services/galley/src/Galley/API/LegalHold/Conflicts.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/LegalholdConflicts.hs index 56bec323294..0c45f09b645 100644 --- a/services/galley/src/Galley/API/LegalHold/Conflicts.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/LegalholdConflicts.hs @@ -17,7 +17,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.LegalHold.Conflicts +module Wire.ConversationSubsystem.LegalholdConflicts ( guardQualifiedLegalholdPolicyConflicts, guardLegalholdPolicyConflicts, LegalholdConflicts (LegalholdConflicts), @@ -33,7 +33,6 @@ import Data.Map qualified as Map import Data.Misc import Data.Qualified import Data.Set qualified as Set -import Galley.Options import Imports import Polysemy import Polysemy.Error @@ -59,7 +58,7 @@ guardQualifiedLegalholdPolicyConflicts :: ( Member BrigAPIAccess r, Member (Error LegalholdConflicts) r, Member (Input (Local ())) r, - Member (Input Opts) r, + Member (Input FeatureFlags) r, Member P.TinyLog r, Member TeamSubsystem r ) => @@ -83,7 +82,7 @@ guardQualifiedLegalholdPolicyConflicts protectee qclients = do guardLegalholdPolicyConflicts :: ( Member BrigAPIAccess r, Member (Error LegalholdConflicts) r, - Member (Input Opts) r, + Member (Input FeatureFlags) r, Member P.TinyLog r, Member TeamSubsystem r ) => @@ -94,7 +93,7 @@ guardLegalholdPolicyConflicts LegalholdPlusFederationNotImplemented _otherClient guardLegalholdPolicyConflicts UnprotectedBot _otherClients = pure () guardLegalholdPolicyConflicts (ProtectedUser self) otherClients = do opts <- input - case view (settings . featureFlags . to npProject) opts of + case view (to npProject) opts of FeatureLegalHoldDisabledPermanently -> case FutureWork @'LegalholdPlusFederationNotImplemented () of FutureWork () -> -- FUTUREWORK: once we support federation and LH in combination, we still need to run @@ -107,7 +106,7 @@ guardLegalholdPolicyConflicts (ProtectedUser self) otherClients = do -- | Guard notification handling against legal-hold policy conflicts. -- Ensures that if any user has a LH client then no user can be missing consent. -- See also: "Brig.API.Connection.checkLegalholdPolicyConflict" --- and "Galley.API.Action.checkLHPolicyConflictsLocal". +-- and "Wire.ConversationSubsystem.Action.checkLHPolicyConflictsLocal". guardLegalholdPolicyConflictsUid :: forall r. ( Member BrigAPIAccess r, diff --git a/services/galley/src/Galley/API/MLS.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS.hs similarity index 88% rename from services/galley/src/Galley/API/MLS.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS.hs index 8da6d00620c..a8fedb7fd5d 100644 --- a/services/galley/src/Galley/API/MLS.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS.hs @@ -15,28 +15,22 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.MLS - ( isMLSEnabled, - assertMLSEnabled, - postMLSMessage, - postMLSCommitBundleFromLocalUser, - postMLSMessageFromLocalUser, - getMLSPublicKeys, +module Wire.ConversationSubsystem.MLS + ( getMLSPublicKeys, formatPublicKeys, ) where import Data.Default -import Galley.API.MLS.Enabled -import Galley.API.MLS.Message import Galley.Types.Error import Imports import Polysemy import Polysemy.Error import Polysemy.Input -import Wire.API.Error +import Wire.API.Error (ErrorS) import Wire.API.Error.Galley import Wire.API.MLS.Keys +import Wire.ConversationSubsystem.MLS.Enabled (getMLSPrivateKeys) getMLSPublicKeys :: ( Member (Input (Maybe (MLSKeysByPurpose MLSPrivateKeys))) r, diff --git a/services/galley/src/Galley/API/MLS/CheckClients.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/CheckClients.hs similarity index 98% rename from services/galley/src/Galley/API/MLS/CheckClients.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/CheckClients.hs index 22ff3de4813..388d6a16422 100644 --- a/services/galley/src/Galley/API/MLS/CheckClients.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/CheckClients.hs @@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.MLS.CheckClients +module Wire.ConversationSubsystem.MLS.CheckClients ( checkClients, getClientData, ClientData (..), @@ -29,7 +29,6 @@ import Data.Map qualified as Map import Data.Qualified import Data.Set qualified as Set import Data.Tuple.Extra -import Galley.API.MLS.Commit.Core import Imports import Polysemy import Polysemy.Error @@ -43,6 +42,7 @@ import Wire.API.MLS.LeafNode import Wire.API.User.Client import Wire.BrigAPIAccess (BrigAPIAccess) import Wire.ConversationStore.MLS.Types +import Wire.ConversationSubsystem.MLS.Commit.Core import Wire.FederationAPIAccess (FederationAPIAccess) checkClients :: diff --git a/services/galley/src/Galley/API/MLS/Commit/Core.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Commit/Core.hs similarity index 98% rename from services/galley/src/Galley/API/MLS/Commit/Core.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Commit/Core.hs index 0318881e606..a51f0adcbcd 100644 --- a/services/galley/src/Galley/API/MLS/Commit/Core.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Commit/Core.hs @@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.MLS.Commit.Core +module Wire.ConversationSubsystem.MLS.Commit.Core ( getCommitData, incrementEpoch, getClientInfo, @@ -31,9 +31,6 @@ where import Control.Comonad import Data.Id import Data.Qualified -import Galley.API.MLS.Conversation -import Galley.API.MLS.IncomingMessage -import Galley.API.MLS.Proposal import Galley.Types.Error import Imports import Polysemy @@ -66,6 +63,9 @@ import Wire.BackendNotificationQueueAccess import Wire.BrigAPIAccess import Wire.ConversationStore import Wire.ConversationStore.MLS.Types +import Wire.ConversationSubsystem.MLS.Conversation +import Wire.ConversationSubsystem.MLS.IncomingMessage +import Wire.ConversationSubsystem.MLS.Proposal import Wire.ExternalAccess import Wire.FederationAPIAccess import Wire.LegalHoldStore (LegalHoldStore) diff --git a/services/galley/src/Galley/API/MLS/Commit/ExternalCommit.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Commit/ExternalCommit.hs similarity index 96% rename from services/galley/src/Galley/API/MLS/Commit/ExternalCommit.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Commit/ExternalCommit.hs index 9aec3fa0ea1..10fa4e6bd58 100644 --- a/services/galley/src/Galley/API/MLS/Commit/ExternalCommit.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Commit/ExternalCommit.hs @@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.MLS.Commit.ExternalCommit +module Wire.ConversationSubsystem.MLS.Commit.ExternalCommit ( ExternalCommitAction (..), getExternalCommitData, processExternalCommit, @@ -28,11 +28,6 @@ import Control.Monad.Codensity import Data.Map qualified as Map import Data.Qualified import Data.Set qualified as Set -import Galley.API.MLS.Commit.Core -import Galley.API.MLS.IncomingMessage -import Galley.API.MLS.Proposal -import Galley.API.MLS.Removal -import Galley.API.MLS.Util import Imports import Polysemy import Polysemy.Error @@ -51,6 +46,11 @@ import Wire.API.MLS.ProposalTag import Wire.API.MLS.SubConversation import Wire.ConversationStore import Wire.ConversationStore.MLS.Types +import Wire.ConversationSubsystem.MLS.Commit.Core +import Wire.ConversationSubsystem.MLS.IncomingMessage +import Wire.ConversationSubsystem.MLS.Proposal +import Wire.ConversationSubsystem.MLS.Removal +import Wire.ConversationSubsystem.MLS.Util data ExternalCommitAction = ExternalCommitAction { add :: LeafIndex, diff --git a/services/galley/src/Galley/API/MLS/Commit/InternalCommit.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Commit/InternalCommit.hs similarity index 96% rename from services/galley/src/Galley/API/MLS/Commit/InternalCommit.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Commit/InternalCommit.hs index 53c51cfa62a..8abc46eefb0 100644 --- a/services/galley/src/Galley/API/MLS/Commit/InternalCommit.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Commit/InternalCommit.hs @@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.MLS.Commit.InternalCommit (processInternalCommit) where +module Wire.ConversationSubsystem.MLS.Commit.InternalCommit (processInternalCommit) where import Control.Comonad import Control.Error.Util (hush) @@ -29,14 +29,6 @@ import Data.Map qualified as Map import Data.Qualified import Data.Set qualified as Set import Data.Tuple.Extra -import Galley.API.Action -import Galley.API.MLS.CheckClients -import Galley.API.MLS.Commit.Core -import Galley.API.MLS.Conversation -import Galley.API.MLS.IncomingMessage -import Galley.API.MLS.One2One -import Galley.API.MLS.Proposal -import Galley.API.MLS.Util import Galley.Types.Error import Imports import Polysemy @@ -60,7 +52,14 @@ import Wire.API.MLS.SubConversation import Wire.API.Unreachable import Wire.ConversationStore import Wire.ConversationStore.MLS.Types -import Wire.ConversationSubsystem +import Wire.ConversationSubsystem.Action +import Wire.ConversationSubsystem.MLS.CheckClients +import Wire.ConversationSubsystem.MLS.Commit.Core +import Wire.ConversationSubsystem.MLS.Conversation +import Wire.ConversationSubsystem.MLS.IncomingMessage +import Wire.ConversationSubsystem.MLS.One2One +import Wire.ConversationSubsystem.MLS.Proposal +import Wire.ConversationSubsystem.MLS.Util import Wire.ConversationSubsystem.Util import Wire.FederationSubsystem import Wire.ProposalStore @@ -78,7 +77,6 @@ processInternalCommit :: Member (ErrorS 'MLSIdentityMismatch) r, Member (ErrorS 'MissingLegalholdConsent) r, Member (ErrorS 'GroupIdVersionNotSupported) r, - Member ConversationSubsystem r, Member Resource r, Member Random r, Member (ErrorS MLSInvalidLeafNodeSignature) r, @@ -258,7 +256,6 @@ processInternalCommit senderIdentity con lConvOrSub ciphersuite ciphersuiteUpdat addMembers :: ( HasProposalActionEffects r, - Member ConversationSubsystem r, Member FederationSubsystem r, Member TeamSubsystem r ) => @@ -286,7 +283,6 @@ addMembers qusr con lConvOrSub users = case tUnqualified lConvOrSub of removeMembers :: ( HasProposalActionEffects r, - Member ConversationSubsystem r, Member TeamSubsystem r ) => Qualified UserId -> diff --git a/services/galley/src/Galley/API/MLS/Conversation.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Conversation.hs similarity index 97% rename from services/galley/src/Galley/API/MLS/Conversation.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Conversation.hs index 0b979880816..b129d31c00e 100644 --- a/services/galley/src/Galley/API/MLS/Conversation.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Conversation.hs @@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.MLS.Conversation +module Wire.ConversationSubsystem.MLS.Conversation ( mkMLSConversation, newMLSConversation, mcConv, diff --git a/services/galley/src/Galley/API/MLS/Enabled.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Enabled.hs similarity index 95% rename from services/galley/src/Galley/API/MLS/Enabled.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Enabled.hs index 158d511e291..1910f5945fb 100644 --- a/services/galley/src/Galley/API/MLS/Enabled.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Enabled.hs @@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.MLS.Enabled where +module Wire.ConversationSubsystem.MLS.Enabled where import Imports hiding (getFirst) import Polysemy @@ -25,7 +25,7 @@ import Wire.API.Error.Galley import Wire.API.MLS.Keys (MLSKeysByPurpose, MLSPrivateKeys) isMLSEnabled :: (Member (Input (Maybe (MLSKeysByPurpose MLSPrivateKeys))) r) => Sem r Bool -isMLSEnabled = inputs (isJust) +isMLSEnabled = inputs isJust -- | Fail if MLS is not enabled. Only use this function at the beginning of an -- MLS endpoint, NOT in utility functions. diff --git a/services/galley/src/Galley/API/MLS/GroupInfo.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/GroupInfo.hs similarity index 95% rename from services/galley/src/Galley/API/MLS/GroupInfo.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/GroupInfo.hs index 69f667cab77..f52928307d2 100644 --- a/services/galley/src/Galley/API/MLS/GroupInfo.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/GroupInfo.hs @@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.MLS.GroupInfo +module Wire.ConversationSubsystem.MLS.GroupInfo ( MLSGroupInfoStaticErrors, getGroupInfo, getGroupInfoFromLocalConv, @@ -26,8 +26,6 @@ where import Data.Id as Id import Data.Json.Util import Data.Qualified -import Galley.API.MLS.Enabled -import Galley.API.MLS.Util import Imports import Polysemy import Polysemy.Error @@ -42,6 +40,8 @@ import Wire.API.MLS.GroupInfo import Wire.API.MLS.Keys (MLSKeysByPurpose, MLSPrivateKeys) import Wire.API.MLS.SubConversation import Wire.ConversationStore qualified as E +import Wire.ConversationSubsystem.MLS.Enabled +import Wire.ConversationSubsystem.MLS.Util import Wire.ConversationSubsystem.Util import Wire.FederationAPIAccess qualified as E diff --git a/services/galley/src/Galley/API/MLS/GroupInfoCheck.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/GroupInfoCheck.hs similarity index 97% rename from services/galley/src/Galley/API/MLS/GroupInfoCheck.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/GroupInfoCheck.hs index 667a29dc3fe..e0c05e2b540 100644 --- a/services/galley/src/Galley/API/MLS/GroupInfoCheck.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/GroupInfoCheck.hs @@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.MLS.GroupInfoCheck +module Wire.ConversationSubsystem.MLS.GroupInfoCheck ( checkGroupState, GroupInfoMismatch (..), GroupInfoCheckEnabled (..), @@ -24,7 +24,6 @@ where import Data.Bifunctor import Data.Id -import Galley.API.Teams.Features.Get import Imports import Polysemy import Polysemy.Error @@ -43,6 +42,7 @@ import Wire.API.MLS.Serialisation import Wire.API.Team.Feature import Wire.ConversationStore import Wire.ConversationStore.MLS.Types +import Wire.ConversationSubsystem.Features import Wire.FeaturesConfigSubsystem (FeaturesConfigSubsystem) data GroupInfoMismatch = GroupInfoMismatch diff --git a/services/galley/src/Galley/API/MLS/IncomingMessage.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/IncomingMessage.hs similarity index 98% rename from services/galley/src/Galley/API/MLS/IncomingMessage.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/IncomingMessage.hs index 3a3fba62514..b88c90fbf7d 100644 --- a/services/galley/src/Galley/API/MLS/IncomingMessage.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/IncomingMessage.hs @@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.MLS.IncomingMessage +module Wire.ConversationSubsystem.MLS.IncomingMessage ( IncomingMessage (..), IncomingMessageContent (..), IncomingPublicMessageContent (..), diff --git a/services/galley/src/Galley/API/MLS/Keys.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Keys.hs similarity index 95% rename from services/galley/src/Galley/API/MLS/Keys.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Keys.hs index ddafc4e0e2d..b5a84d89f50 100644 --- a/services/galley/src/Galley/API/MLS/Keys.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Keys.hs @@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.MLS.Keys (getMLSRemovalKey, SomeKeyPair (..)) where +module Wire.ConversationSubsystem.MLS.Keys (getMLSRemovalKey, SomeKeyPair (..)) where import Control.Error.Util (hush) import Data.Proxy diff --git a/services/galley/src/Galley/API/MLS/Message.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Message.hs similarity index 90% rename from services/galley/src/Galley/API/MLS/Message.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Message.hs index 6425512192b..719149f6980 100644 --- a/services/galley/src/Galley/API/MLS/Message.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Message.hs @@ -15,17 +15,12 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.MLS.Message - ( IncomingBundle (..), - mkIncomingBundle, - IncomingMessage (..), - mkIncomingMessage, +module Wire.ConversationSubsystem.MLS.Message + ( MLSMessageStaticErrors, postMLSCommitBundle, postMLSCommitBundleFromLocalUser, postMLSMessageFromLocalUser, postMLSMessage, - MLSMessageStaticErrors, - MLSBundleStaticErrors, ) where @@ -40,27 +35,11 @@ import Data.Set qualified as Set import Data.Tagged import Data.Text.Lazy qualified as LT import Data.Tuple.Extra -import Galley.API.Action -import Galley.API.LegalHold.Get (getUserStatus) -import Galley.API.MLS.Commit.Core (getCommitData) -import Galley.API.MLS.Commit.ExternalCommit -import Galley.API.MLS.Commit.InternalCommit -import Galley.API.MLS.Conversation -import Galley.API.MLS.Enabled -import Galley.API.MLS.GroupInfoCheck -import Galley.API.MLS.IncomingMessage -import Galley.API.MLS.One2One -import Galley.API.MLS.OutOfSync -import Galley.API.MLS.Propagate -import Galley.API.MLS.Proposal -import Galley.API.MLS.Util -import Galley.API.MLS.Welcome (sendWelcomes) import Galley.Types.Error import Imports import Polysemy import Polysemy.Error import Polysemy.Input -import Polysemy.Internal import Polysemy.Output import Polysemy.Resource (Resource) import Polysemy.TinyLog @@ -88,7 +67,21 @@ import Wire.API.Team.LegalHold import Wire.BrigAPIAccess (BrigAPIAccess) import Wire.ConversationStore import Wire.ConversationStore.MLS.Types -import Wire.ConversationSubsystem +import Wire.ConversationSubsystem.Action +import Wire.ConversationSubsystem.Legalhold (getUserStatus) +import Wire.ConversationSubsystem.MLS.Commit.Core (getCommitData) +import Wire.ConversationSubsystem.MLS.Commit.ExternalCommit +import Wire.ConversationSubsystem.MLS.Commit.InternalCommit +import Wire.ConversationSubsystem.MLS.Conversation +import Wire.ConversationSubsystem.MLS.Enabled +import Wire.ConversationSubsystem.MLS.GroupInfoCheck +import Wire.ConversationSubsystem.MLS.IncomingMessage +import Wire.ConversationSubsystem.MLS.One2One +import Wire.ConversationSubsystem.MLS.OutOfSync +import Wire.ConversationSubsystem.MLS.Propagate +import Wire.ConversationSubsystem.MLS.Proposal +import Wire.ConversationSubsystem.MLS.Util +import Wire.ConversationSubsystem.MLS.Welcome (sendWelcomes) import Wire.ConversationSubsystem.Util import Wire.ExternalAccess import Wire.FeaturesConfigSubsystem @@ -116,25 +109,12 @@ type MLSMessageStaticErrors = ErrorS 'MLSStaleMessage, ErrorS 'MLSProposalNotFound, ErrorS 'MissingLegalholdConsent, - ErrorS 'MLSInvalidLeafNodeIndex, - ErrorS 'MLSClientMismatch, - ErrorS 'MLSUnsupportedProposal, ErrorS 'MLSCommitMissingReferences, ErrorS 'MLSSelfRemovalNotAllowed, ErrorS 'MLSClientSenderUserMismatch, - ErrorS 'MLSGroupConversationMismatch, ErrorS 'MLSSubConvClientNotInParent ] -type MLSBundleStaticErrors = - Append - MLSMessageStaticErrors - '[ ErrorS 'MLSWelcomeMismatch, - ErrorS 'MLSIdentityMismatch, - ErrorS 'GroupIdVersionNotSupported, - ErrorS 'MLSInvalidLeafNodeSignature - ] - enableOutOfSyncCheckFromVersion :: Version -> EnableOutOfSyncCheck enableOutOfSyncCheckFromVersion v | v < V13 = DisableOutOfSyncCheck @@ -149,7 +129,6 @@ postMLSMessageFromLocalUser :: Member (ErrorS 'MissingLegalholdConsent) r, Member (ErrorS 'MLSClientSenderUserMismatch) r, Member (ErrorS 'MLSCommitMissingReferences) r, - Member (ErrorS 'MLSGroupConversationMismatch) r, Member (ErrorS 'MLSNotEnabled) r, Member (ErrorS 'MLSProposalNotFound) r, Member (ErrorS 'MLSSelfRemovalNotAllowed) r, @@ -171,9 +150,8 @@ postMLSMessageFromLocalUser v lusr c conn smsg = do imsg <- noteS @'MLSUnsupportedMessage $ mkIncomingMessage smsg (ctype, cnvOrSub) <- getConvFromGroupId imsg.groupId events <- - runInputConst (enableOutOfSyncCheckFromVersion v) $ - map lcuEvent - <$> postMLSMessage lusr (tUntagged lusr) c ctype cnvOrSub (Just conn) imsg + map lcuEvent + <$> postMLSMessage lusr (tUntagged lusr) c ctype cnvOrSub (Just conn) (enableOutOfSyncCheckFromVersion v) imsg t <- toUTCTimeMillis <$> Now.get pure $ MLSMessageSendingStatus events t @@ -183,13 +161,12 @@ postMLSCommitBundle :: Member (Error GroupInfoDiagnostics) r, Member (Error MLSOutOfSyncError) r, Member (ErrorS GroupIdVersionNotSupported) r, - Member (Input EnableOutOfSyncCheck) r, Member (Input (Maybe GroupInfoCheckEnabled)) r, Member Random r, Member Resource r, - Members MLSBundleStaticErrors r, + Members MLSMessageStaticErrors r, + Member (ErrorS 'MLSInvalidLeafNodeSignature) r, HasProposalEffects r, - Member ConversationSubsystem r, Member MLSCommitLockStore r, Member FederationSubsystem r, Member TeamSubsystem r, @@ -202,14 +179,16 @@ postMLSCommitBundle :: ConvType -> Qualified ConvOrSubConvId -> Maybe ConnId -> + EnableOutOfSyncCheck -> IncomingBundle -> Sem r [LocalConversationUpdate] -postMLSCommitBundle loc qusr c ctype qConvOrSub conn bundle = - foldQualified - loc - (postMLSCommitBundleToLocalConv qusr c conn bundle ctype) - (postMLSCommitBundleToRemoteConv loc qusr c conn bundle ctype) - qConvOrSub +postMLSCommitBundle loc qusr c ctype qConvOrSub conn oosCheck bundle = + runInputConst oosCheck $ + foldQualified + loc + (postMLSCommitBundleToLocalConv qusr c conn bundle ctype) + (postMLSCommitBundleToRemoteConv loc qusr c conn bundle ctype) + qConvOrSub postMLSCommitBundleFromLocalUser :: ( Member (ErrorS MLSLegalholdIncompatible) r, @@ -221,9 +200,9 @@ postMLSCommitBundleFromLocalUser :: Member (Input (Maybe (MLSKeysByPurpose MLSPrivateKeys))) r, Member Random r, Member Resource r, - Members MLSBundleStaticErrors r, + Members MLSMessageStaticErrors r, + Member (ErrorS 'MLSInvalidLeafNodeSignature) r, HasProposalEffects r, - Member ConversationSubsystem r, Member MLSCommitLockStore r, Member FederationSubsystem r, Member TeamSubsystem r, @@ -242,9 +221,8 @@ postMLSCommitBundleFromLocalUser v lusr c conn bundle = do (ctype, qConvOrSub) <- getConvFromGroupId ibundle.groupId events <- - runInputConst (enableOutOfSyncCheckFromVersion v) $ - map lcuEvent - <$> postMLSCommitBundle lusr (tUntagged lusr) c ctype qConvOrSub (Just conn) ibundle + map lcuEvent + <$> postMLSCommitBundle lusr (tUntagged lusr) c ctype qConvOrSub (Just conn) (enableOutOfSyncCheckFromVersion v) ibundle t <- toUTCTimeMillis <$> Now.get pure $ MLSMessageSendingStatus events t @@ -258,9 +236,9 @@ postMLSCommitBundleToLocalConv :: Member (Input (Maybe GroupInfoCheckEnabled)) r, Member Random r, Member Resource r, - Members MLSBundleStaticErrors r, + Members MLSMessageStaticErrors r, + Member (ErrorS 'MLSInvalidLeafNodeSignature) r, HasProposalEffects r, - Member ConversationSubsystem r, Member MLSCommitLockStore r, Member FederationSubsystem r, Member TeamSubsystem r, @@ -405,7 +383,7 @@ handleGroupInfoMismatch lConvId bundle m = postMLSCommitBundleToRemoteConv :: ( Member BrigAPIAccess r, - Members MLSBundleStaticErrors r, + Members MLSMessageStaticErrors r, Member (Error FederationError) r, Member (Error MLSProtocolError) r, Member (Error MLSProposalFailure) r, @@ -448,7 +426,7 @@ postMLSCommitBundleToRemoteConv loc qusr c con bundle ctype rConvOrSubId = do enableOutOfSyncCheck } case resp of - MLSMessageResponseError e -> rethrowErrors @MLSBundleStaticErrors e + MLSMessageResponseError e -> rethrowErrors @MLSMessageStaticErrors e MLSMessageResponseProtocolError e -> throw (mlsProtocolError e) MLSMessageResponseProposalFailure e -> throw (MLSProposalFailure e) MLSMessageResponseUnreachableBackends ds -> throw (UnreachableBackends (toList ds)) @@ -470,7 +448,6 @@ postMLSMessage :: Member (ErrorS 'MissingLegalholdConsent) r, Member (ErrorS 'MLSClientSenderUserMismatch) r, Member (ErrorS 'MLSCommitMissingReferences) r, - Member (ErrorS 'MLSGroupConversationMismatch) r, Member (ErrorS 'MLSProposalNotFound) r, Member (ErrorS 'MLSSelfRemovalNotAllowed) r, Member (ErrorS 'MLSStaleMessage) r, @@ -478,8 +455,7 @@ postMLSMessage :: Member (ErrorS 'MLSSubConvClientNotInParent) r, Member (ErrorS MLSInvalidLeafNodeSignature) r, Member (Error MLSOutOfSyncError) r, - Member (Error GroupInfoDiagnostics) r, - Member (Input EnableOutOfSyncCheck) r + Member (Error GroupInfoDiagnostics) r ) => Local x -> Qualified UserId -> @@ -487,14 +463,16 @@ postMLSMessage :: ConvType -> Qualified ConvOrSubConvId -> Maybe ConnId -> + EnableOutOfSyncCheck -> IncomingMessage -> Sem r [LocalConversationUpdate] -postMLSMessage loc qusr c ctype qconvOrSub con msg = do - foldQualified - loc - (postMLSMessageToLocalConv qusr c con msg ctype) - (postMLSMessageToRemoteConv loc qusr c con msg) - qconvOrSub +postMLSMessage loc qusr c ctype qconvOrSub con oosCheck msg = do + runInputConst oosCheck $ + foldQualified + loc + (postMLSMessageToLocalConv qusr c con msg ctype) + (postMLSMessageToRemoteConv loc qusr c con msg) + qconvOrSub getSenderIdentity :: ( Member (ErrorS 'MLSClientSenderUserMismatch) r, diff --git a/services/galley/src/Galley/API/MLS/Migration.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Migration.hs similarity index 98% rename from services/galley/src/Galley/API/MLS/Migration.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Migration.hs index 1db5ce29576..0922d1588f2 100644 --- a/services/galley/src/Galley/API/MLS/Migration.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Migration.hs @@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.MLS.Migration where +module Wire.ConversationSubsystem.MLS.Migration where import Data.Qualified import Data.Set qualified as Set diff --git a/services/galley/src/Galley/API/MLS/One2One.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/One2One.hs similarity index 99% rename from services/galley/src/Galley/API/MLS/One2One.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/One2One.hs index 3f8550b4b94..3082a3dc257 100644 --- a/services/galley/src/Galley/API/MLS/One2One.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/One2One.hs @@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.MLS.One2One +module Wire.ConversationSubsystem.MLS.One2One ( localMLSOne2OneConversation, localMLSOne2OneConversationAsRemote, localMLSOne2OneConversationMetadata, diff --git a/services/galley/src/Galley/API/MLS/OutOfSync.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/OutOfSync.hs similarity index 97% rename from services/galley/src/Galley/API/MLS/OutOfSync.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/OutOfSync.hs index 03556f89ade..b9aaceb5bbd 100644 --- a/services/galley/src/Galley/API/MLS/OutOfSync.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/OutOfSync.hs @@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.MLS.OutOfSync +module Wire.ConversationSubsystem.MLS.OutOfSync ( checkConversationOutOfSync, updateOutOfSyncFlag, ) @@ -25,7 +25,6 @@ import Data.Id import Data.Map qualified as Map import Data.Qualified import Data.Set qualified as Set -import Galley.API.MLS.CheckClients import Imports import Polysemy import Polysemy.Error @@ -39,6 +38,7 @@ import Wire.API.MLS.SubConversation import Wire.BrigAPIAccess (BrigAPIAccess) import Wire.ConversationStore import Wire.ConversationStore.MLS.Types +import Wire.ConversationSubsystem.MLS.CheckClients import Wire.FederationAPIAccess (FederationAPIAccess) import Wire.StoredConversation diff --git a/services/galley/src/Galley/API/MLS/Propagate.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Propagate.hs similarity index 98% rename from services/galley/src/Galley/API/MLS/Propagate.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Propagate.hs index 8e71e7463e3..8445a089f4b 100644 --- a/services/galley/src/Galley/API/MLS/Propagate.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Propagate.hs @@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.MLS.Propagate where +module Wire.ConversationSubsystem.MLS.Propagate where import Control.Comonad import Data.Id diff --git a/services/galley/src/Galley/API/MLS/Proposal.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Proposal.hs similarity index 99% rename from services/galley/src/Galley/API/MLS/Proposal.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Proposal.hs index fdff658e2ad..6179e83b674 100644 --- a/services/galley/src/Galley/API/MLS/Proposal.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Proposal.hs @@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.MLS.Proposal +module Wire.ConversationSubsystem.MLS.Proposal ( -- * Proposal processing derefOrCheckProposal, checkProposal, @@ -38,7 +38,6 @@ import Data.Id import Data.Map qualified as Map import Data.Qualified import Data.Set qualified as Set -import Galley.API.MLS.IncomingMessage import Galley.Types.Error import Imports import Polysemy diff --git a/services/galley/src/Galley/API/MLS/Removal.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Removal.hs similarity index 98% rename from services/galley/src/Galley/API/MLS/Removal.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Removal.hs index fbdc427bbcf..3daa9b661ed 100644 --- a/services/galley/src/Galley/API/MLS/Removal.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Removal.hs @@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.MLS.Removal +module Wire.ConversationSubsystem.MLS.Removal ( createAndSendRemoveProposals, removeExtraneousClients, removeClient, @@ -31,9 +31,6 @@ import Data.Map qualified as Map import Data.Proxy import Data.Qualified import Data.Set qualified as Set -import Galley.API.MLS.Conversation -import Galley.API.MLS.Keys -import Galley.API.MLS.Propagate import Imports import Polysemy import Polysemy.Error @@ -54,6 +51,9 @@ import Wire.API.MLS.SubConversation import Wire.BackendNotificationQueueAccess import Wire.ConversationStore import Wire.ConversationStore.MLS.Types +import Wire.ConversationSubsystem.MLS.Conversation +import Wire.ConversationSubsystem.MLS.Keys +import Wire.ConversationSubsystem.MLS.Propagate import Wire.ExternalAccess import Wire.NotificationSubsystem import Wire.ProposalStore diff --git a/services/galley/src/Galley/API/MLS/Reset.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Reset.hs similarity index 93% rename from services/galley/src/Galley/API/MLS/Reset.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Reset.hs index 18955e75c1e..0f88ab79699 100644 --- a/services/galley/src/Galley/API/MLS/Reset.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Reset.hs @@ -15,14 +15,10 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.MLS.Reset (resetMLSConversation) where +module Wire.ConversationSubsystem.MLS.Reset (resetMLSConversation) where import Data.Id import Data.Qualified -import Galley.API.Action -import Galley.API.MLS.Enabled -import Galley.API.MLS.Util -import Galley.API.Update import Galley.Types.Error import Imports import Polysemy @@ -42,7 +38,10 @@ import Wire.API.Routes.Public.Galley.MLS import Wire.BackendNotificationQueueAccess import Wire.BrigAPIAccess (BrigAPIAccess) import Wire.ConversationStore -import Wire.ConversationSubsystem +import Wire.ConversationSubsystem.Action +import Wire.ConversationSubsystem.MLS.Enabled (assertMLSEnabled) +import Wire.ConversationSubsystem.MLS.Util +import Wire.ConversationSubsystem.Update import Wire.ExternalAccess (ExternalAccess) import Wire.FederationAPIAccess (FederationAPIAccess) import Wire.NotificationSubsystem @@ -52,10 +51,8 @@ import Wire.Sem.Random (Random) import Wire.TeamSubsystem (TeamSubsystem) resetMLSConversation :: - ( Member (Input (Maybe (MLSKeysByPurpose MLSPrivateKeys))) r, - Member Now r, + ( Member Now r, Member (Input (Local ())) r, - Member (ErrorS MLSNotEnabled) r, Member (ErrorS MLSStaleMessage) r, Member (ErrorS (ActionDenied LeaveConversation)) r, Member (ErrorS ConvNotFound) r, @@ -63,6 +60,8 @@ resetMLSConversation :: Member (Error InternalError) r, Member (ErrorS InvalidOperation) r, Member (ErrorS MLSFederatedResetNotSupported) r, + Member (Input (Maybe (MLSKeysByPurpose MLSPrivateKeys))) r, + Member (ErrorS MLSNotEnabled) r, Member BackendNotificationQueueAccess r, Member ConversationStore r, Member (FederationAPIAccess FederatorClient) r, @@ -70,7 +69,6 @@ resetMLSConversation :: Member (Error FederationError) r, Member BrigAPIAccess r, Member NotificationSubsystem r, - Member ConversationSubsystem r, Member ProposalStore r, Member Random r, Member Resource r, diff --git a/services/galley/src/Galley/API/MLS/SubConversation.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/SubConversation.hs similarity index 94% rename from services/galley/src/Galley/API/MLS/SubConversation.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/SubConversation.hs index a404476e377..14574b31c26 100644 --- a/services/galley/src/Galley/API/MLS/SubConversation.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/SubConversation.hs @@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.MLS.SubConversation +module Wire.ConversationSubsystem.MLS.SubConversation ( getSubConversation, getLocalSubConversation, deleteSubConversation, @@ -35,11 +35,6 @@ import Control.Arrow import Control.Monad.Codensity hiding (reset) import Data.Id import Data.Qualified -import Galley.API.MLS -import Galley.API.MLS.Conversation -import Galley.API.MLS.GroupInfo -import Galley.API.MLS.Removal -import Galley.API.MLS.Util import Imports import Polysemy import Polysemy.Error @@ -65,6 +60,11 @@ import Wire.API.Routes.Public.Galley.MLS import Wire.BackendNotificationQueueAccess import Wire.ConversationStore qualified as Conversation import Wire.ConversationStore.MLS.Types as Conversation +import Wire.ConversationSubsystem.MLS.Conversation (mkMLSConversation) +import Wire.ConversationSubsystem.MLS.Enabled (assertMLSEnabled) +import Wire.ConversationSubsystem.MLS.GroupInfo (getGroupInfoFromRemoteConv) +import Wire.ConversationSubsystem.MLS.Removal (createAndSendRemoveProposals) +import Wire.ConversationSubsystem.MLS.Util (getLocalConvForUser, withCommitLock) import Wire.ConversationSubsystem.Util import Wire.ExternalAccess (ExternalAccess) import Wire.FederationAPIAccess @@ -169,10 +169,12 @@ getSubConversationGroupInfo :: '[ Conversation.ConversationStore, Error FederationError, FederationAPIAccess FederatorClient, - Input (Maybe (MLSKeysByPurpose MLSPrivateKeys)) + Input (Maybe (MLSKeysByPurpose MLSPrivateKeys)), + ErrorS 'MLSNotEnabled, + ErrorS 'ConvNotFound, + ErrorS 'MLSMissingGroupInfo ] - r, - Members MLSGroupInfoStaticErrors r + r ) => Local UserId -> Qualified ConvId -> @@ -187,8 +189,10 @@ getSubConversationGroupInfo lusr qcnvId subconv = do qcnvId getSubConversationGroupInfoFromLocalConv :: - (Member Conversation.ConversationStore r) => - (Members MLSGroupInfoStaticErrors r) => + ( Member Conversation.ConversationStore r, + Member (ErrorS 'ConvNotFound) r, + Member (ErrorS 'MLSMissingGroupInfo) r + ) => Qualified UserId -> SubConvId -> Local ConvId -> diff --git a/services/galley/src/Galley/API/MLS/Util.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Util.hs similarity index 98% rename from services/galley/src/Galley/API/MLS/Util.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Util.hs index 742f820b1bb..814603f64ff 100644 --- a/services/galley/src/Galley/API/MLS/Util.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Util.hs @@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.MLS.Util where +module Wire.ConversationSubsystem.MLS.Util where import Control.Comonad import Control.Monad.Codensity diff --git a/services/galley/src/Galley/API/MLS/Welcome.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Welcome.hs similarity index 99% rename from services/galley/src/Galley/API/MLS/Welcome.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Welcome.hs index 44484dda0a5..b16bb87e1af 100644 --- a/services/galley/src/Galley/API/MLS/Welcome.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Welcome.hs @@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.MLS.Welcome +module Wire.ConversationSubsystem.MLS.Welcome ( sendWelcomes, sendLocalWelcomes, ) diff --git a/services/galley/src/Galley/API/Mapping.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Mapping.hs similarity index 99% rename from services/galley/src/Galley/API/Mapping.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/Mapping.hs index 3837b9e623b..c4cea7981d0 100644 --- a/services/galley/src/Galley/API/Mapping.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Mapping.hs @@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.Mapping +module Wire.ConversationSubsystem.Mapping ( conversationViewV9, conversationView, conversationViewWithCachedOthers, diff --git a/services/galley/src/Galley/API/Message.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Message.hs similarity index 97% rename from services/galley/src/Galley/API/Message.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/Message.hs index 502f2d53aa9..f3f1efd9172 100644 --- a/services/galley/src/Galley/API/Message.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Message.hs @@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.Message +module Wire.ConversationSubsystem.Message ( UserType (..), sendLocalMessages, postQualifiedOtrMessage, @@ -24,6 +24,7 @@ module Galley.API.Message legacyClientMismatchStrategy, Unqualify (..), MessageMetadata (..), + IntraListing (..), -- * Only exported for tests checkMessageClients, @@ -48,8 +49,6 @@ import Data.Range import Data.Set qualified as Set import Data.Set.Lens import Data.Time.Clock (UTCTime) -import Galley.API.LegalHold.Conflicts -import Galley.Options import Galley.Types.Clients qualified as Clients import Imports hiding (forkIO) import Network.AMQP qualified as Q @@ -58,6 +57,7 @@ import Polysemy.Error import Polysemy.Input import Polysemy.TinyLog qualified as P import System.Logger.Class qualified as Log +import Wire.API.Conversation.Config (ConversationSubsystemConfig) import Wire.API.Conversation.Protocol import Wire.API.Error import Wire.API.Error.Galley @@ -69,7 +69,7 @@ import Wire.API.Federation.Client (FederatorClient) import Wire.API.Federation.Error import Wire.API.Message import Wire.API.Routes.Public.Galley.Messaging -import Wire.API.Team.FeatureFlags (FanoutLimit) +import Wire.API.Team.FeatureFlags (FanoutLimit, FeatureFlags) import Wire.API.Team.LegalHold import Wire.API.Team.Member import Wire.API.User.Client @@ -77,7 +77,8 @@ import Wire.API.UserMap (UserMap (..)) import Wire.BackendNotificationQueueAccess import Wire.BrigAPIAccess import Wire.ConversationStore -import Wire.ConversationSubsystem qualified as ConvSubsystem +import Wire.ConversationSubsystem.Internal qualified as ConvSubsystem +import Wire.ConversationSubsystem.LegalholdConflicts import Wire.ConversationSubsystem.Util import Wire.ExternalAccess import Wire.FederationAPIAccess @@ -92,6 +93,10 @@ import Wire.UserClientIndexStore data UserType = User | Bot -- FUTUREWORK: there is UserType in Wire.API.User now, should we use that? (there is also UserType variant for searcho/contacts, but there is a good reason for that one.) +newtype IntraListing + = IntraListing {unIntraListing :: Bool} + deriving stock (Eq, Ord, Show) + userToProtectee :: UserType -> UserId -> LegalholdProtectee userToProtectee User user = ProtectedUser user userToProtectee Bot _ = UnprotectedBot @@ -259,14 +264,15 @@ postBroadcast :: Member (ErrorS 'NonBindingTeam) r, Member (ErrorS 'BroadcastLimitExceeded) r, Member ExternalAccess r, - Member (Input Opts) r, + Member (Input FeatureFlags) r, Member Now r, Member TeamStore r, Member P.TinyLog r, Member NotificationSubsystem r, Member (Input FanoutLimit) r, + Member (Input ConversationSubsystemConfig) r, Member TeamSubsystem r, - Member ConvSubsystem.ConversationSubsystem r + Member UserClientIndexStore r ) => Local UserId -> Maybe ConnId -> @@ -372,7 +378,8 @@ postQualifiedOtrMessage :: Member (FederationAPIAccess FederatorClient) r, Member BackendNotificationQueueAccess r, Member ExternalAccess r, - Member (Input Opts) r, + Member (Input IntraListing) r, + Member (Input FeatureFlags) r, Member Now r, Member P.TinyLog r, Member NotificationSubsystem r, @@ -411,7 +418,7 @@ postQualifiedOtrMessage senderType sender mconn lcnv msg = Set.fromList $ map (tUntagged . qualifyAs lcnv) localMemberIds <> map (tUntagged . (.id_)) conv.remoteMembers - isInternal <- view (settings . intraListing) <$> input + IntraListing isInternal <- input -- check if the sender is part of the conversation unless (Set.member sender members) $ @@ -533,7 +540,7 @@ postQualifiedOtrMessage senderType sender mconn lcnv msg = guardQualifiedLegalholdPolicyConflictsWrapper :: ( Member BrigAPIAccess r, Member (Error (MessageNotSent MessageSendingStatus)) r, - Member (Input Opts) r, + Member (Input FeatureFlags) r, Member P.TinyLog r, Member TeamSubsystem r ) => diff --git a/libs/wire-subsystems/src/Wire/ConversationSubsystem/One2One.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/One2One.hs index afe039381b8..a73b430d57b 100644 --- a/libs/wire-subsystems/src/Wire/ConversationSubsystem/One2One.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/One2One.hs @@ -19,7 +19,7 @@ module Wire.ConversationSubsystem.One2One ( one2OneConvId, - iUpsertOne2OneConversation, + internalUpsertOne2OneConversation, ) where @@ -51,12 +51,12 @@ newConnectConversationWithRemote creator users = groupId = Nothing } -iUpsertOne2OneConversation :: +internalUpsertOne2OneConversation :: forall r. (Member ConversationStore r) => UpsertOne2OneConversationRequest -> Sem r () -iUpsertOne2OneConversation UpsertOne2OneConversationRequest {..} = do +internalUpsertOne2OneConversation UpsertOne2OneConversationRequest {..} = do let dolocal :: Local ConvId -> Sem r () dolocal lconvId = do mbConv <- getConversation (tUnqualified lconvId) diff --git a/services/galley/src/Galley/API/Query.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Query.hs similarity index 98% rename from services/galley/src/Galley/API/Query.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/Query.hs index 201b7572bd3..20609b0f1e3 100644 --- a/services/galley/src/Galley/API/Query.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Query.hs @@ -18,7 +18,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.Query +module Wire.ConversationSubsystem.Query ( getBotConversation, getUnqualifiedOwnConversation, getOwnConversation, @@ -66,12 +66,6 @@ import Data.Qualified import Data.Range import Data.Set qualified as Set import Data.Tagged -import Galley.API.MLS -import Galley.API.MLS.Enabled -import Galley.API.MLS.One2One -import Galley.API.Mapping -import Galley.API.Mapping qualified as Mapping -import Galley.API.Teams.Features.Get import Galley.Types.Error import Imports import Polysemy @@ -107,7 +101,12 @@ import Wire.CodeStore.Code (Code (codeConversation)) import Wire.CodeStore.Code qualified as Data import Wire.ConversationStore qualified as ConversationStore import Wire.ConversationStore.MLS.Types -import Wire.ConversationSubsystem qualified as ConversationSubsystem +import Wire.ConversationSubsystem.Fetch (getConversationIdsImpl) +import Wire.ConversationSubsystem.MLS +import Wire.ConversationSubsystem.MLS.Enabled (assertMLSEnabled, getMLSPrivateKeys, isMLSEnabled) +import Wire.ConversationSubsystem.MLS.One2One (localMLSOne2OneConversation, remoteMLSOne2OneConversation) +import Wire.ConversationSubsystem.Mapping +import Wire.ConversationSubsystem.Mapping qualified as Mapping import Wire.ConversationSubsystem.One2One import Wire.ConversationSubsystem.Util import Wire.FeaturesConfigSubsystem @@ -402,13 +401,13 @@ conversationIdsPageFromUnqualified lusr start msize = do -- FUTUREWORK: Move the body of this function to 'conversationIdsPageFrom' once -- support for V2 is dropped. conversationIdsPageFromV2 :: - (Member ConversationSubsystem.ConversationSubsystem r) => + (Member ConversationStore.ConversationStore r) => ListGlobalSelfConvs -> Local UserId -> Public.GetPaginatedConversationIds -> Sem r Public.ConvIdsPage conversationIdsPageFromV2 listGlobalSelf lusr Public.GetMultiTablePageRequest {..} = do - filterOut <$> ConversationSubsystem.getConversationIds lusr gmtprSize gmtprState + filterOut <$> getConversationIdsImpl lusr gmtprSize gmtprState where -- MLS self-conversation of this user selfConvId = mlsSelfConvId (tUnqualified lusr) @@ -437,9 +436,8 @@ conversationIdsPageFromV2 listGlobalSelf lusr Public.GetMultiTablePageRequest {. conversationIdsPageFrom :: forall r. ( Member ConversationStore.ConversationStore r, - Member ConversationSubsystem.ConversationSubsystem r, - Member (Error InternalError) r, Member (Input (Maybe (MLSKeysByPurpose MLSPrivateKeys))) r, + Member (Error InternalError) r, Member P.TinyLog r ) => Local UserId -> @@ -711,9 +709,9 @@ getConversationGuestLinksFeatureStatus (Just tid) = getFeatureForTeam tid getMLSSelfConversationWithError :: forall r. ( Member ConversationStore.ConversationStore r, - Member (Error InternalError) r, - Member (ErrorS 'MLSNotEnabled) r, Member (Input (Maybe (MLSKeysByPurpose MLSPrivateKeys))) r, + Member (ErrorS MLSNotEnabled) r, + Member (Error InternalError) r, Member P.TinyLog r ) => Local UserId -> @@ -864,10 +862,10 @@ getMLSOne2OneConversation lself qother fmt = do getLocalMLSOne2OneConversation :: ( Member ConversationStore.ConversationStore r, - Member (Error InternalError) r, - Member P.TinyLog r, Member (Input (Maybe (MLSKeysByPurpose MLSPrivateKeys))) r, - Member (ErrorS MLSNotEnabled) r + Member (ErrorS MLSNotEnabled) r, + Member (Error InternalError) r, + Member P.TinyLog r ) => Local UserId -> Local ConvId -> diff --git a/services/galley/src/Galley/API/Update.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Update.hs similarity index 94% rename from services/galley/src/Galley/API/Update.hs rename to libs/wire-subsystems/src/Wire/ConversationSubsystem/Update.hs index 2dcf415c11b..45b12100ba3 100644 --- a/services/galley/src/Galley/API/Update.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Update.hs @@ -18,7 +18,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.API.Update +module Wire.ConversationSubsystem.Update ( -- * Managing Conversations acceptConv, blockConv, @@ -74,11 +74,13 @@ module Galley.API.Update addBot, rmBot, postBotMessageUnqualified, + GuestLinkTTLSeconds (..), ) where import Control.Error.Util (hush) import Control.Lens +import Data.Aeson (FromJSON (..)) import Data.Code import Data.Default import Data.Id @@ -90,14 +92,6 @@ import Data.Qualified import Data.Set qualified as Set import Data.Singletons import Data.Vector qualified as V -import Galley.API.Action -import Galley.API.Action.Kick (kickMember) -import Galley.API.Mapping -import Galley.API.Message -import Galley.API.Query qualified as Query -import Galley.API.Teams.Features.Get -import Galley.App -import Galley.Options import Galley.Types.Error import Imports hiding (forkIO) import Polysemy @@ -127,7 +121,7 @@ import Wire.API.Routes.Public.Galley.Messaging import Wire.API.Routes.Public.Util (UpdateResult (..)) import Wire.API.ServantProto (RawProto (..)) import Wire.API.Team.Feature -import Wire.API.Team.FeatureFlags (FanoutLimit) +import Wire.API.Team.FeatureFlags (FanoutLimit, FeatureFlags) import Wire.API.Team.Member import Wire.API.User.Client import Wire.API.UserGroup @@ -138,7 +132,11 @@ import Wire.CodeStore qualified as E import Wire.CodeStore.Code import Wire.ConversationStore (ConversationStore) import Wire.ConversationStore qualified as E -import Wire.ConversationSubsystem +import Wire.ConversationSubsystem.Action +import Wire.ConversationSubsystem.Action.Kick (kickMember) +import Wire.ConversationSubsystem.Mapping +import Wire.ConversationSubsystem.Message +import Wire.ConversationSubsystem.Query qualified as Query import Wire.ConversationSubsystem.Util import Wire.ExternalAccess qualified as E import Wire.FeaturesConfigSubsystem @@ -163,6 +161,22 @@ import Wire.UserGroupStore (UserGroupStore, getUserGroupsForConv) import Wire.UserList import Wire.Util +newtype GuestLinkTTLSeconds = GuestLinkTTLSeconds + { unGuestLinkTTLSeconds :: Int + } + deriving (Show, Generic) + +instance FromJSON GuestLinkTTLSeconds where + parseJSON x = do + n <- parseJSON x + if n > 0 && n <= 31536000 + then pure $ GuestLinkTTLSeconds n + else fail "GuestLinkTTLSeconds must be in (0, 31536000]" + +-- | Default guest link TTL in days. 365 days if not set. +defGuestLinkTTLSeconds :: GuestLinkTTLSeconds +defGuestLinkTTLSeconds = GuestLinkTTLSeconds $ 60 * 60 * 24 * 365 -- 1 year + acceptConv :: ( Member ConversationStore r, Member (Error InternalError) r, @@ -291,8 +305,6 @@ type UpdateConversationAccessEffects = E.FederationAPIAccess FederatorClient, FireAndForget, NotificationSubsystem, - ConversationSubsystem, - Input Env, Input ConversationSubsystemConfig, ProposalStore, Random, @@ -322,7 +334,10 @@ updateConversationHistory :: Member (ErrorS ConvNotFound) r, Member (ErrorS HistoryNotSupported) r, Member ConversationStore r, - Member ConversationSubsystem r, + Member NotificationSubsystem r, + Member E.ExternalAccess r, + Member BackendNotificationQueueAccess r, + Member Now r, Member TeamSubsystem r ) => Local UserId -> @@ -367,9 +382,10 @@ updateConversationReceiptMode :: Member (ErrorS 'InvalidOperation) r, Member (ErrorS 'MLSReadReceiptsNotAllowed) r, Member E.ExternalAccess r, + Member BackendNotificationQueueAccess r, + Member Now r, Member (E.FederationAPIAccess FederatorClient) r, Member NotificationSubsystem r, - Member ConversationSubsystem r, Member (Input (Local ())) r, Member TinyLog r, Member TeamSubsystem r @@ -444,9 +460,10 @@ updateConversationReceiptModeUnqualified :: Member (ErrorS 'InvalidOperation) r, Member (ErrorS 'MLSReadReceiptsNotAllowed) r, Member E.ExternalAccess r, + Member BackendNotificationQueueAccess r, + Member Now r, Member (E.FederationAPIAccess FederatorClient) r, Member NotificationSubsystem r, - Member ConversationSubsystem r, Member (Input (Local ())) r, Member TinyLog r, Member TeamSubsystem r @@ -464,7 +481,10 @@ updateConversationMessageTimer :: Member (ErrorS 'ConvNotFound) r, Member (ErrorS 'InvalidOperation) r, Member (Error FederationError) r, - Member ConversationSubsystem r, + Member NotificationSubsystem r, + Member E.ExternalAccess r, + Member Now r, + Member BackendNotificationQueueAccess r, Member TeamSubsystem r ) => Local UserId -> @@ -493,7 +513,10 @@ updateConversationMessageTimerUnqualified :: Member (ErrorS 'ConvNotFound) r, Member (ErrorS 'InvalidOperation) r, Member (Error FederationError) r, - Member ConversationSubsystem r, + Member NotificationSubsystem r, + Member E.ExternalAccess r, + Member Now r, + Member BackendNotificationQueueAccess r, Member TeamSubsystem r ) => Local UserId -> @@ -511,7 +534,10 @@ deleteLocalConversation :: Member (ErrorS ('ActionDenied 'DeleteConversation)) r, Member (ErrorS 'ConvNotFound) r, Member (ErrorS 'InvalidOperation) r, - Member ConversationSubsystem r, + Member NotificationSubsystem r, + Member E.ExternalAccess r, + Member Now r, + Member BackendNotificationQueueAccess r, Member ProposalStore r, Member TeamSubsystem r ) => @@ -536,7 +562,7 @@ addCodeUnqualifiedWithReqBody :: Member (Input (Local ())) r, Member Now r, Member HashPassword r, - Member (Input Opts) r, + Member (Input (Maybe GuestLinkTTLSeconds)) r, Member FeaturesConfigSubsystem r, Member RateLimit r, Member TeamSubsystem r @@ -561,7 +587,7 @@ addCodeUnqualified :: Member NotificationSubsystem r, Member (Input (Local ())) r, Member Now r, - Member (Input Opts) r, + Member (Input (Maybe GuestLinkTTLSeconds)) r, Member HashPassword r, Member FeaturesConfigSubsystem r, Member RateLimit r, @@ -590,7 +616,7 @@ addCode :: Member HashPassword r, Member NotificationSubsystem r, Member Now r, - Member (Input Opts) r, + Member (Input (Maybe GuestLinkTTLSeconds)) r, Member FeaturesConfigSubsystem r, Member RateLimit r, Member TeamSubsystem r @@ -612,7 +638,7 @@ addCode lusr mbZHost mZcon lcnv mReq = do key <- E.makeKey (tUnqualified lcnv) E.getCode key >>= \case Nothing -> do - ttl <- realToFrac . unGuestLinkTTLSeconds . fromMaybe defGuestLinkTTLSeconds . view (settings . guestLinkTTLSeconds) <$> input + ttl <- inputs (realToFrac . unGuestLinkTTLSeconds . fromMaybe defGuestLinkTTLSeconds) code <- E.generateCode (tUnqualified lcnv) (Timeout ttl) mPw <- for (mReq >>= (.password)) $ HashPassword.hashPassword8 (RateLimitUser (tUnqualified lusr)) E.createCode code mPw @@ -742,7 +768,6 @@ updateConversationProtocolWithLocalUser :: Member ConversationStore r, Member TinyLog r, Member NotificationSubsystem r, - Member ConversationSubsystem r, Member E.ExternalAccess r, Member (E.FederationAPIAccess FederatorClient) r, Member Random r, @@ -780,8 +805,9 @@ updateChannelAddPermission :: Member (ErrorS 'InvalidOperation) r, Member (Error FederationError) r, Member E.ExternalAccess r, + Member Now r, + Member BackendNotificationQueueAccess r, Member NotificationSubsystem r, - Member ConversationSubsystem r, Member (Input (Local ())) r, Member TinyLog r, Member (Error NonFederatingBackends) r, @@ -824,11 +850,15 @@ joinConversationByReusableCode :: Member (ErrorS 'InvalidOperation) r, Member (ErrorS 'NotATeamMember) r, Member (ErrorS 'TooManyMembers) r, - Member ConversationSubsystem r, + Member (Error FederationError) r, + Member BackendNotificationQueueAccess r, + Member NotificationSubsystem r, + Member E.ExternalAccess r, Member FeaturesConfigSubsystem r, Member HashPassword r, Member RateLimit r, Member TeamSubsystem r, + Member Now r, Member (Input ConversationSubsystemConfig) r ) => Local UserId -> @@ -850,8 +880,12 @@ joinConversationById :: Member (ErrorS 'InvalidOperation) r, Member (ErrorS 'NotATeamMember) r, Member (ErrorS 'TooManyMembers) r, - Member ConversationSubsystem r, + Member (Error FederationError) r, Member (Input ConversationSubsystemConfig) r, + Member BackendNotificationQueueAccess r, + Member NotificationSubsystem r, + Member E.ExternalAccess r, + Member Now r, Member TeamSubsystem r ) => Local UserId -> @@ -869,9 +903,13 @@ joinConversation :: Member (ErrorS 'InvalidOperation) r, Member (ErrorS 'NotATeamMember) r, Member (ErrorS 'TooManyMembers) r, - Member ConversationSubsystem r, + Member (Error FederationError) r, Member (Input ConversationSubsystemConfig) r, + Member BackendNotificationQueueAccess r, + Member E.ExternalAccess r, Member ConversationStore r, + Member Now r, + Member NotificationSubsystem r, Member TeamSubsystem r ) => Local UserId -> @@ -927,7 +965,6 @@ addMembers :: Member (Error UnreachableBackends) r, Member E.ExternalAccess r, Member (E.FederationAPIAccess FederatorClient) r, - Member ConversationSubsystem r, Member NotificationSubsystem r, Member Now r, Member LegalHoldStore r, @@ -979,7 +1016,6 @@ addMembersUnqualifiedV2 :: Member (Error UnreachableBackends) r, Member E.ExternalAccess r, Member (E.FederationAPIAccess FederatorClient) r, - Member ConversationSubsystem r, Member NotificationSubsystem r, Member Now r, Member LegalHoldStore r, @@ -1020,7 +1056,6 @@ addMembersUnqualified :: Member (Error UnreachableBackends) r, Member E.ExternalAccess r, Member (E.FederationAPIAccess FederatorClient) r, - Member ConversationSubsystem r, Member NotificationSubsystem r, Member Now r, Member LegalHoldStore r, @@ -1073,7 +1108,6 @@ replaceMembers :: Member TinyLog r, Member TeamCollaboratorsSubsystem r, Member UserGroupStore r, - Member ConversationSubsystem r, Member FederationSubsystem r, Member TeamSubsystem r, Member (Input ConversationSubsystemConfig) r @@ -1201,7 +1235,10 @@ updateOtherMemberLocalConv :: Member (ErrorS 'InvalidOperation) r, Member (ErrorS 'ConvNotFound) r, Member (ErrorS 'ConvMemberNotFound) r, - Member ConversationSubsystem r, + Member NotificationSubsystem r, + Member BackendNotificationQueueAccess r, + Member E.ExternalAccess r, + Member Now r, Member TeamSubsystem r ) => Local ConvId -> @@ -1224,7 +1261,10 @@ updateOtherMemberUnqualified :: Member (ErrorS 'InvalidOperation) r, Member (ErrorS 'ConvNotFound) r, Member (ErrorS 'ConvMemberNotFound) r, - Member ConversationSubsystem r, + Member NotificationSubsystem r, + Member BackendNotificationQueueAccess r, + Member E.ExternalAccess r, + Member Now r, Member TeamSubsystem r ) => Local UserId -> @@ -1246,7 +1286,10 @@ updateOtherMember :: Member (ErrorS 'InvalidOperation) r, Member (ErrorS 'ConvNotFound) r, Member (ErrorS 'ConvMemberNotFound) r, - Member ConversationSubsystem r, + Member NotificationSubsystem r, + Member BackendNotificationQueueAccess r, + Member E.ExternalAccess r, + Member Now r, Member TeamSubsystem r ) => Local UserId -> @@ -1279,7 +1322,6 @@ removeMemberUnqualified :: Member E.ExternalAccess r, Member (E.FederationAPIAccess FederatorClient) r, Member NotificationSubsystem r, - Member ConversationSubsystem r, Member Now r, Member ProposalStore r, Member Random r, @@ -1307,7 +1349,6 @@ removeMemberQualified :: Member E.ExternalAccess r, Member (E.FederationAPIAccess FederatorClient) r, Member NotificationSubsystem r, - Member ConversationSubsystem r, Member Now r, Member ProposalStore r, Member Random r, @@ -1382,7 +1423,6 @@ removeMemberFromLocalConv :: Member (ErrorS 'InvalidOperation) r, Member E.ExternalAccess r, Member NotificationSubsystem r, - Member ConversationSubsystem r, Member Now r, Member ProposalStore r, Member Random r, @@ -1425,7 +1465,6 @@ removeMemberFromChannel :: Member Now r, Member E.ExternalAccess r, Member NotificationSubsystem r, - Member ConversationSubsystem r, Member Random r, Member TinyLog r, Member (Error FederationError) r, @@ -1462,7 +1501,8 @@ postProteusMessage :: Member BackendNotificationQueueAccess r, Member NotificationSubsystem r, Member E.ExternalAccess r, - Member (Input Opts) r, + Member (Input FeatureFlags) r, + Member (Input IntraListing) r, Member Now r, Member TinyLog r, Member TeamSubsystem r @@ -1486,13 +1526,14 @@ postProteusBroadcast :: Member (ErrorS 'BroadcastLimitExceeded) r, Member NotificationSubsystem r, Member E.ExternalAccess r, - Member (Input Opts) r, + Member (Input FeatureFlags) r, Member Now r, Member TeamStore r, Member TinyLog r, Member (Input FanoutLimit) r, Member TeamSubsystem r, - Member ConversationSubsystem r + Member (Input ConversationSubsystemConfig) r, + Member E.UserClientIndexStore r ) => Local UserId -> ConnId -> @@ -1540,7 +1581,8 @@ postBotMessageUnqualified :: Member BackendNotificationQueueAccess r, Member NotificationSubsystem r, Member (Input (Local ())) r, - Member (Input Opts) r, + Member (Input FeatureFlags) r, + Member (Input IntraListing) r, Member TinyLog r, Member Now r, Member TeamSubsystem r @@ -1568,13 +1610,14 @@ postOtrBroadcastUnqualified :: Member (ErrorS 'BroadcastLimitExceeded) r, Member NotificationSubsystem r, Member E.ExternalAccess r, - Member (Input Opts) r, + Member (Input FeatureFlags) r, Member Now r, Member TeamStore r, Member TinyLog r, Member (Input FanoutLimit) r, Member TeamSubsystem r, - Member ConversationSubsystem r + Member (Input ConversationSubsystemConfig) r, + Member E.UserClientIndexStore r ) => Local UserId -> ConnId -> @@ -1595,7 +1638,8 @@ postOtrMessageUnqualified :: Member BackendNotificationQueueAccess r, Member E.ExternalAccess r, Member NotificationSubsystem r, - Member (Input Opts) r, + Member (Input FeatureFlags) r, + Member (Input IntraListing) r, Member Now r, Member TinyLog r, Member TeamSubsystem r @@ -1620,7 +1664,10 @@ updateConversationName :: Member (ErrorS ('ActionDenied 'ModifyConversationName)) r, Member (ErrorS 'ConvNotFound) r, Member (ErrorS 'InvalidOperation) r, - Member ConversationSubsystem r, + Member E.ExternalAccess r, + Member Now r, + Member BackendNotificationQueueAccess r, + Member NotificationSubsystem r, Member TeamSubsystem r ) => Local UserId -> @@ -1643,7 +1690,10 @@ updateUnqualifiedConversationName :: Member (ErrorS ('ActionDenied 'ModifyConversationName)) r, Member (ErrorS 'ConvNotFound) r, Member (ErrorS 'InvalidOperation) r, - Member ConversationSubsystem r, + Member E.ExternalAccess r, + Member Now r, + Member BackendNotificationQueueAccess r, + Member NotificationSubsystem r, Member TeamSubsystem r ) => Local UserId -> @@ -1662,7 +1712,10 @@ updateLocalConversationName :: Member (ErrorS ('ActionDenied 'ModifyConversationName)) r, Member (ErrorS 'ConvNotFound) r, Member (ErrorS 'InvalidOperation) r, - Member ConversationSubsystem r, + Member E.ExternalAccess r, + Member Now r, + Member BackendNotificationQueueAccess r, + Member NotificationSubsystem r, Member TeamSubsystem r ) => Local UserId -> diff --git a/libs/wire-subsystems/src/Wire/GalleyAPIAccess/Rpc.hs b/libs/wire-subsystems/src/Wire/GalleyAPIAccess/Rpc.hs index 1dd831fe3ef..2398bca77bd 100644 --- a/libs/wire-subsystems/src/Wire/GalleyAPIAccess/Rpc.hs +++ b/libs/wire-subsystems/src/Wire/GalleyAPIAccess/Rpc.hs @@ -138,7 +138,7 @@ galleyRequest req = do ep <- input rpcWithRetries "galley" ep req --- | Calls 'Galley.API.createSelfConversationH'. +-- | Calls 'Wire.ConversationSubsystem.createSelfConversationH'. createSelfConv :: ( Member Rpc r, Member TinyLog r, @@ -158,7 +158,7 @@ createSelfConv v u = do . zUser u . expect2xx --- | Calls 'Galley.API.getConversationH'. +-- | Calls 'Wire.ConversationSubsystem.getConversationH'. getConv :: ( Member (Error ParseException) r, Member Rpc r, @@ -191,7 +191,7 @@ getConv v usr lcnv = do . zUser usr . expect [status200, status404] --- | Calls 'Galley.API.getTeamConversationH'. +-- | Calls 'Wire.ConversationSubsystem.getTeamConversationH'. getTeamConv :: ( Member (Error ParseException) r, Member Rpc r, @@ -225,7 +225,7 @@ getTeamConv v usr tid cnv = do . zUser usr . expect [status200, status404] --- | Calls 'Galley.API.addClientH'. +-- | Calls 'Wire.ConversationSubsystem.addClientH'. newClient :: ( Member Rpc r, Member (Input Endpoint) r, @@ -246,7 +246,7 @@ newClient u c = do . zUser u . expect2xx --- | Calls 'Galley.API.canUserJoinTeamH'. +-- | Calls 'Wire.ConversationSubsystem.canUserJoinTeamH'. checkUserCanJoinTeam :: ( Member Rpc r, Member (Input Endpoint) r, @@ -270,7 +270,7 @@ checkUserCanJoinTeam tid = do . paths ["i", "teams", toByteString' tid, "members", "check"] . header "Content-Type" "application/json" --- | Calls 'Galley.API.uncheckedAddTeamMemberH'. +-- | Calls 'Wire.ConversationSubsystem.uncheckedAddTeamMemberH'. addTeamMember :: ( Member Rpc r, Member (Input Endpoint) r, @@ -300,7 +300,7 @@ addTeamMember u tid minvmeta role = do . expect [status200, status403] . lbytes (encode bdy) --- | Calls 'Galley.API.createBindingTeamH'. +-- | Calls 'Wire.ConversationSubsystem.createBindingTeamH'. createTeam :: ( Member Rpc r, Member (Input Endpoint) r, @@ -324,7 +324,7 @@ createTeam u t teamid = do . expect2xx . lbytes (encode t) --- | Calls 'Galley.API.uncheckedGetTeamMemberH'. +-- | Calls 'Wire.ConversationSubsystem.uncheckedGetTeamMemberH'. getTeamMember :: ( Member (Error ParseException) r, Member Rpc r, @@ -349,7 +349,7 @@ getTeamMember u tid = do . zUser u . expect [status200, status404] --- | Calls 'Galley.API.uncheckedGetTeamMembersH'. +-- | Calls 'Wire.ConversationSubsystem.uncheckedGetTeamMembersH'. -- -- | TODO: is now truncated. this is (only) used for team suspension / unsuspension, which -- means that only the first 2000 members of a team (according to some arbitrary order) will @@ -442,7 +442,7 @@ memberIsTeamOwner tid uid = do . paths ["i", "teams", toByteString' tid, "is-team-owner", toByteString' uid] pure $ responseStatus r /= status403 --- | Calls 'Galley.API.getBindingTeamIdH'. +-- | Calls 'Wire.ConversationSubsystem.getBindingTeamIdH'. getTeamId :: ( Member (Error ParseException) r, Member Rpc r, @@ -463,7 +463,7 @@ getTeamId u = do . paths ["i", "users", toByteString' u, "team"] . expect [status200, status404] --- | Calls 'Galley.API.getTeamInternalH'. +-- | Calls 'Wire.ConversationSubsystem.getTeamInternalH'. getTeam :: ( Member (Error ParseException) r, Member Rpc r, @@ -481,7 +481,7 @@ getTeam tid = do . paths ["i", "teams", toByteString' tid] . expect2xx --- | Calls 'Galley.API.getTeamInternalH'. +-- | Calls 'Wire.ConversationSubsystem.getTeamInternalH'. getTeamName :: ( Member (Error ParseException) r, Member Rpc r, @@ -499,7 +499,7 @@ getTeamName tid = do . paths ["i", "teams", toByteString' tid, "name"] . expect2xx --- | Calls 'Galley.API.getTeamFeatureStatusH'. +-- | Calls 'Wire.ConversationSubsystem.getTeamFeatureStatusH'. getTeamLegalHoldStatus :: ( Member (Error ParseException) r, Member Rpc r, @@ -517,7 +517,7 @@ getTeamLegalHoldStatus tid = do . paths ["i", "teams", toByteString' tid, "features", featureNameBS @LegalholdConfig] . expect2xx --- | Calls 'Galley.API.getSearchVisibilityInternalH'. +-- | Calls 'Wire.ConversationSubsystem.getSearchVisibilityInternalH'. getTeamSearchVisibility :: ( Member (Error ParseException) r, Member Rpc r, @@ -606,7 +606,7 @@ getConfiguredFeatureFlags = do . expect2xx ) --- | Calls 'Galley.API.updateTeamStatusH'. +-- | Calls 'Wire.ConversationSubsystem.updateTeamStatusH'. changeTeamStatus :: ( Member Rpc r, Member (Input Endpoint) r, diff --git a/libs/wire-subsystems/src/Wire/MeetingsSubsystem/Interpreter.hs b/libs/wire-subsystems/src/Wire/MeetingsSubsystem/Interpreter.hs index 45d06de2cae..9fa3d151686 100644 --- a/libs/wire-subsystems/src/Wire/MeetingsSubsystem/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/MeetingsSubsystem/Interpreter.hs @@ -122,7 +122,7 @@ createMeetingImpl zUser newMeeting = do } -- Create and store the conversation via ConversationSubsystem - storedConv <- ConversationSubsystem.createGroupConversation zUser Nothing newConv + storedConv <- ConversationSubsystem.internalCreateGroupConversation zUser Nothing newConv -- Store meeting (trial status is provided by caller) storedMeeting <- diff --git a/libs/wire-subsystems/src/Wire/NotificationSubsystem/Interpreter.hs b/libs/wire-subsystems/src/Wire/NotificationSubsystem/Interpreter.hs index c93258449c8..adb170507d4 100644 --- a/libs/wire-subsystems/src/Wire/NotificationSubsystem/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/NotificationSubsystem/Interpreter.hs @@ -23,7 +23,6 @@ import Data.Aeson import Data.Id import Data.List.NonEmpty (NonEmpty) import Data.List.NonEmpty qualified as NonEmpty -import Data.Proxy import Data.Range import Data.Set qualified as Set import Data.Time.Clock.DiffTime @@ -38,6 +37,7 @@ import Polysemy.TinyLog qualified as P import System.Logger.Class as Log import Wire.API.Push.V2 hiding (Push (..), Recipient, newPush) import Wire.API.Push.V2 qualified as V2 +import Wire.API.Team.FeatureFlags (defaultFanoutLimit) import Wire.API.Team.HardTruncationLimit (HardTruncationLimit) import Wire.GundeckAPIAccess (GundeckAPIAccess) import Wire.GundeckAPIAccess qualified as GundeckAPIAccess @@ -75,9 +75,6 @@ defaultNotificationSubsystemConfig :: RequestId -> NotificationSubsystemConfig defaultNotificationSubsystemConfig reqId = NotificationSubsystemConfig defaultFanoutLimit defaultChunkSize defaultSlowPushDelay reqId -defaultFanoutLimit :: Range 1 HardTruncationLimit Int32 -defaultFanoutLimit = toRange (Proxy @HardTruncationLimit) - defaultChunkSize :: Natural defaultChunkSize = 128 diff --git a/libs/wire-subsystems/src/Wire/TeamInvitationSubsystem/Interpreter.hs b/libs/wire-subsystems/src/Wire/TeamInvitationSubsystem/Interpreter.hs index c42d8c58d1e..d7e7b9e4682 100644 --- a/libs/wire-subsystems/src/Wire/TeamInvitationSubsystem/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/TeamInvitationSubsystem/Interpreter.hs @@ -286,7 +286,7 @@ logInvitationRequest context action = -- | Privilege escalation detection (make sure no `RoleMember` user creates a `RoleOwner`). -- --- There is some code duplication with 'Galley.API.Teams.ensureNotElevated'. +-- There is some code duplication with 'Wire.ConversationSubsystem.Teams.ensureNotElevated'. ensurePermissionToAddUser :: ( Member (Error TeamInvitationSubsystemError) r, Member TeamSubsystem r diff --git a/services/galley/test/unit/Test/Galley/Mapping.hs b/libs/wire-subsystems/test/unit/Wire/ConversationSubsystem/MappingSpec.hs similarity index 59% rename from services/galley/test/unit/Test/Galley/Mapping.hs rename to libs/wire-subsystems/test/unit/Wire/ConversationSubsystem/MappingSpec.hs index 575995457a1..69b7025e092 100644 --- a/services/galley/test/unit/Test/Galley/Mapping.hs +++ b/libs/wire-subsystems/test/unit/Wire/ConversationSubsystem/MappingSpec.hs @@ -18,22 +18,22 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Test.Galley.Mapping where +module Wire.ConversationSubsystem.MappingSpec where import Data.Containers.ListUtils (nubOrdOn) import Data.Domain import Data.Id import Data.Qualified import Data.Set qualified as Set -import Galley.API.Mapping import Galley.Types.Error (InternalError) import Imports import Polysemy (Sem) import Polysemy qualified as P import Polysemy.Error qualified as P import Polysemy.TinyLog qualified as P -import Test.Tasty -import Test.Tasty.QuickCheck +import Test.Hspec +import Test.Hspec.QuickCheck +import Test.QuickCheck (Arbitrary (..), Gen, listOf, (==>)) import Wire.API.Conversation import Wire.API.Conversation.Protocol import Wire.API.Conversation.Role @@ -41,63 +41,61 @@ import Wire.API.Federation.API.Galley ( RemoteConvMembers (..), RemoteConversationV2 (..), ) +import Wire.ConversationSubsystem.Mapping import Wire.Sem.Logger qualified as P import Wire.StoredConversation run :: Sem '[P.TinyLog, P.Error InternalError] a -> Either InternalError a run = P.run . P.runError . P.discardLogs -tests :: TestTree -tests = - testGroup - "ConversationMapping" - [ testProperty "conversation view V9 for a valid user is non-empty" $ - \(ConvWithLocalUser c luid) -> isRight (run (conversationViewV9 luid c)), - testProperty "conversation view V10 for a valid user is non-empty" $ - \(ConvWithLocalUser c luid) -> isRight (run (pure $ conversationView (qualifyAs luid ()) (Just luid) c)), - testProperty "self user in conversation view is correct" $ - \(ConvWithLocalUser c luid) -> - fmap (memId . cmSelf . cnvMembers) (run (conversationViewV9 luid c)) - == Right (tUntagged luid), - testProperty "conversation view metadata is correct" $ - \(ConvWithLocalUser c luid) -> - fmap cnvMetadata (run (conversationViewV9 luid c)) - == Right c.metadata, - testProperty "other members in conversation view do not contain self" $ - \(ConvWithLocalUser c luid) -> case run $ conversationViewV9 luid c of - Left _ -> False - Right cnv -> - tUntagged luid - `notElem` map omQualifiedId (cmOthers (cnvMembers cnv)), - testProperty "conversation view contains all users" $ - \(ConvWithLocalUser c luid) -> - fmap (sort . cnvUids) (run (conversationViewV9 luid c)) - == Right (sort (convUids (tDomain luid) c)), - testProperty "conversation view for an invalid user is empty" $ - \(RandomConversation c) luid -> - notElem (tUnqualified luid) (map (.id_) c.localMembers) ==> - isLeft (run (conversationViewV9 luid c)), - testProperty "remote conversation view for a valid user is non-empty" $ - \(ConvWithRemoteUser c ruid) dom -> - qDomain (tUntagged ruid) /= dom ==> - isJust (conversationToRemote dom ruid c), - testProperty "self user role in remote conversation view is correct" $ - \(ConvWithRemoteUser c ruid) dom -> - qDomain (tUntagged ruid) /= dom ==> - fmap (selfRole . (.members)) (conversationToRemote dom ruid c) - == Just roleNameWireMember, - testProperty "remote conversation view metadata is correct" $ - \(ConvWithRemoteUser c ruid) dom -> - qDomain (tUntagged ruid) /= dom ==> - fmap (.metadata) (conversationToRemote dom ruid c) - == Just c.metadata, - testProperty "remote conversation view does not contain self" $ - \(ConvWithRemoteUser c ruid) dom -> case conversationToRemote dom ruid c of - Nothing -> False - Just rcnv -> - tUntagged ruid - `notElem` map omQualifiedId rcnv.members.others - ] +spec :: Spec +spec = describe "ConversationMapping" do + prop "conversation view V9 for a valid user is non-empty" $ + \(ConvWithLocalUser c luid) -> isRight (run (conversationViewV9 luid c)) + prop "conversation view V10 for a valid user is non-empty" $ + \(ConvWithLocalUser c luid) -> isRight (run (pure $ conversationView (qualifyAs luid ()) (Just luid) c)) + prop "self user in conversation view is correct" $ + \(ConvWithLocalUser c luid) -> + fmap (memId . cmSelf . cnvMembers) (run (conversationViewV9 luid c)) + == Right (tUntagged luid) + prop "conversation view metadata is correct" $ + \(ConvWithLocalUser c luid) -> + fmap cnvMetadata (run (conversationViewV9 luid c)) + == Right c.metadata + prop "other members in conversation view do not contain self" $ + \(ConvWithLocalUser c luid) -> case run $ conversationViewV9 luid c of + Left _ -> False + Right cnv -> + tUntagged luid + `notElem` map omQualifiedId (cmOthers (cnvMembers cnv)) + prop "conversation view contains all users" $ + \(ConvWithLocalUser c luid) -> + fmap (sort . cnvUids) (run (conversationViewV9 luid c)) + == Right (sort (convUids (tDomain luid) c)) + prop "conversation view for an invalid user is empty" $ + \(RandomConversation c) luid -> + notElem (tUnqualified luid) (map (.id_) c.localMembers) ==> + isLeft (run (conversationViewV9 luid c)) + prop "remote conversation view for a valid user is non-empty" $ + \(ConvWithRemoteUser c ruid) dom -> + qDomain (tUntagged ruid) /= dom ==> + isJust (conversationToRemote dom ruid c) + prop "self user role in remote conversation view is correct" $ + \(ConvWithRemoteUser c ruid) dom -> + qDomain (tUntagged ruid) /= dom ==> + fmap (selfRole . (.members)) (conversationToRemote dom ruid c) + == Just roleNameWireMember + prop "remote conversation view metadata is correct" $ + \(ConvWithRemoteUser c ruid) dom -> + qDomain (tUntagged ruid) /= dom ==> + fmap (.metadata) (conversationToRemote dom ruid c) + == Just c.metadata + prop "remote conversation view does not contain self" $ + \(ConvWithRemoteUser c ruid) dom -> case conversationToRemote dom ruid c of + Nothing -> False + Just rcnv -> + tUntagged ruid + `notElem` map omQualifiedId rcnv.members.others cnvUids :: OwnConversation -> [Qualified UserId] cnvUids c = diff --git a/services/galley/test/unit/Test/Galley/API/Message.hs b/libs/wire-subsystems/test/unit/Wire/ConversationSubsystem/MessageSpec.hs similarity index 56% rename from services/galley/test/unit/Test/Galley/API/Message.hs rename to libs/wire-subsystems/test/unit/Wire/ConversationSubsystem/MessageSpec.hs index 18c9512c665..55e9b03d7c6 100644 --- a/services/galley/test/unit/Test/Galley/API/Message.hs +++ b/libs/wire-subsystems/test/unit/Wire/ConversationSubsystem/MessageSpec.hs @@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Test.Galley.API.Message where +module Wire.ConversationSubsystem.MessageSpec where import Control.Lens import Data.Domain @@ -23,27 +23,23 @@ import Data.Id import Data.Map qualified as Map import Data.Set qualified as Set import Data.Set.Lens -import Data.UUID.Types -import Galley.API.Message +import Data.UUID qualified as UUID import Imports -import Test.Tasty -import Test.Tasty.QuickCheck +import Test.Hspec +import Test.Hspec.QuickCheck +import Test.QuickCheck ((===), (==>)) import Wire.API.Message import Wire.API.User.Client (QualifiedUserClients (..)) +import Wire.ConversationSubsystem.Message -tests :: TestTree -tests = - testGroup - "Galley.API.Message" - [ testGroup - "checkMessageClients" - [ checkMessageClientSuccess, - checkMessageClientEverythingReported, - checkMessageClientRedundantSender, - checkMessageClientMissingSubsetOfStrategy - ], - testBuildFailedToSend - ] +spec :: Spec +spec = describe "Galley.API.Message" do + describe "checkMessageClients" do + checkMessageClientSuccess + checkMessageClientEverythingReported + checkMessageClientRedundantSender + checkMessageClientMissingSubsetOfStrategy + testBuildFailedToSend flatten :: Map Domain (Map UserId (Set ClientId)) -> Set (Domain, UserId, ClientId) flatten = @@ -57,8 +53,8 @@ type QualifiedUserClient = (Domain, UserId, ClientId) recipientSetToMap :: Set QualifiedUserClient -> Map (Domain, UserId) (Set ClientId) recipientSetToMap = Set.foldr (\(d, u, c) m -> Map.insertWith Set.union (d, u) (Set.singleton c) m) mempty -checkMessageClientSuccess :: TestTree -checkMessageClientSuccess = testProperty "success" $ +checkMessageClientSuccess :: Spec +checkMessageClientSuccess = prop "success" $ \(sender :: QualifiedUserClient) (msg :: Map QualifiedUserClient ByteString) (strat :: ClientMismatchStrategy) -> let expectedRecipients = Map.keysSet msg expectedRecipientMap = recipientSetToMap expectedRecipients @@ -66,8 +62,8 @@ checkMessageClientSuccess = testProperty "success" $ checkMessageClients sender expectedRecipientMap msg strat === (True, msg, QualifiedMismatch mempty mempty mempty) -checkMessageClientRedundantSender :: TestTree -checkMessageClientRedundantSender = testProperty "sender should be part of redundant" $ +checkMessageClientRedundantSender :: Spec +checkMessageClientRedundantSender = prop "sender should be part of redundant" $ \(msg0 :: Map QualifiedUserClient ByteString) (sender :: QualifiedUserClient) (strat :: ClientMismatchStrategy) -> let msg = Map.insert sender "msg to self" msg0 expectedRecipients = Map.keysSet msg0 @@ -79,8 +75,8 @@ checkMessageClientRedundantSender = testProperty "sender should be part of redun -- expected'' are used along with msg to generate expected, this ensures that we -- don't always get a disjoint set between the intended recipietns and expected -- recipients. -checkMessageClientEverythingReported :: TestTree -checkMessageClientEverythingReported = testProperty "all intended and expected recipients should be part of valid and extras" $ +checkMessageClientEverythingReported :: Spec +checkMessageClientEverythingReported = prop "all intended and expected recipients should be part of valid and extras" $ \(sender :: QualifiedUserClient) (expected' :: Set QualifiedUserClient) (msg0 :: Map QualifiedUserClient ByteString) (msg' :: Map QualifiedUserClient ByteString) -> let expectedRecipients = Map.keysSet msg0 <> expected' expectedRecipientMap = recipientSetToMap expectedRecipients @@ -92,8 +88,8 @@ checkMessageClientEverythingReported = testProperty "all intended and expected r in validRecipients <> extraRecipients === intendedRecipients <> expectedRecipients -checkMessageClientMissingSubsetOfStrategy :: TestTree -checkMessageClientMissingSubsetOfStrategy = testProperty "missing clients should be a subset of the clients determined by the strategy" $ +checkMessageClientMissingSubsetOfStrategy :: Spec +checkMessageClientMissingSubsetOfStrategy = prop "missing clients should be a subset of the clients determined by the strategy" $ \(sender :: QualifiedUserClient) (expected' :: Set QualifiedUserClient) (msg0 :: Map QualifiedUserClient ByteString) (msg' :: Map QualifiedUserClient ByteString) (strat :: ClientMismatchStrategy) -> let expected = Map.keysSet msg0 <> expected' expectedMap = recipientSetToMap expected @@ -103,52 +99,38 @@ checkMessageClientMissingSubsetOfStrategy = testProperty "missing clients should missing = flatten . qualifiedUserClients $ qmMissing mismatch in Set.isSubsetOf missing stratClients -testBuildFailedToSend :: TestTree -testBuildFailedToSend = - testGroup - "build failed to send map for post message qualified" - [ testProperty - "Empty case - trivial" - $ collectFailedToSend [] - === mempty, - testProperty - "Empty case - single empty map" - $ collectFailedToSend [mempty] - === mempty, - testProperty - "Empty case - multiple empty maps" - $ collectFailedToSend [mempty, mempty] - === mempty, - testProperty - "Single domain" - $ collectFailedToSend [Map.singleton (Domain "foo") mempty] - === Map.singleton (Domain "foo") mempty, - testProperty - "Single domain duplicated" - $ collectFailedToSend [Map.singleton (Domain "foo") mempty, Map.singleton (Domain "foo") mempty] - === Map.singleton (Domain "foo") mempty, - testProperty - "Mutliple domains in multiple maps" - $ collectFailedToSend [Map.singleton (Domain "foo") mempty, Map.singleton (Domain "bar") mempty] - === Map.fromList [(Domain "foo", mempty), (Domain "bar", mempty)], - testProperty - "Mutliple domains in single map" - $ collectFailedToSend [Map.fromList [(Domain "foo", mempty), (Domain "bar", mempty)]] - === Map.fromList [(Domain "foo", mempty), (Domain "bar", mempty)], - testProperty - "Single domain duplicated with unique sub-maps" - $ collectFailedToSend - [ Map.singleton (Domain "foo") $ Map.singleton idA mempty, - Map.singleton (Domain "foo") $ Map.singleton idB mempty - ] - === Map.singleton - (Domain "foo") - ( Map.fromList - [ (idA, mempty), - (idB, mempty) - ] - ) - ] +testBuildFailedToSend :: Spec +testBuildFailedToSend = describe "build failed to send map for post message qualified" do + prop "Empty case - trivial" $ + collectFailedToSend [] === mempty + prop "Empty case - single empty map" $ + collectFailedToSend [mempty] === mempty + prop "Empty case - multiple empty maps" $ + collectFailedToSend [mempty, mempty] === mempty + prop "Single domain" $ + collectFailedToSend [Map.singleton (Domain "foo") mempty] + === Map.singleton (Domain "foo") mempty + prop "Single domain duplicated" $ + collectFailedToSend [Map.singleton (Domain "foo") mempty, Map.singleton (Domain "foo") mempty] + === Map.singleton (Domain "foo") mempty + prop "Mutliple domains in multiple maps" $ + collectFailedToSend [Map.singleton (Domain "foo") mempty, Map.singleton (Domain "bar") mempty] + === Map.fromList [(Domain "foo", mempty), (Domain "bar", mempty)] + prop "Mutliple domains in single map" $ + collectFailedToSend [Map.fromList [(Domain "foo", mempty), (Domain "bar", mempty)]] + === Map.fromList [(Domain "foo", mempty), (Domain "bar", mempty)] + prop "Single domain duplicated with unique sub-maps" $ + collectFailedToSend + [ Map.singleton (Domain "foo") $ Map.singleton idA mempty, + Map.singleton (Domain "foo") $ Map.singleton idB mempty + ] + === Map.singleton + (Domain "foo") + ( Map.fromList + [ (idA, mempty), + (idB, mempty) + ] + ) where - idA = Id $ fromJust $ Data.UUID.Types.fromString "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" - idB = Id $ fromJust $ Data.UUID.Types.fromString "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb" + idA = Id $ fromJust $ UUID.fromString "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" + idB = Id $ fromJust $ UUID.fromString "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb" diff --git a/services/galley/test/unit/Test/Galley/API/One2One.hs b/libs/wire-subsystems/test/unit/Wire/ConversationSubsystem/One2OneSpec.hs similarity index 80% rename from services/galley/test/unit/Test/Galley/API/One2One.hs rename to libs/wire-subsystems/test/unit/Wire/ConversationSubsystem/One2OneSpec.hs index 88a0df0ff57..20c6658d77c 100644 --- a/services/galley/test/unit/Test/Galley/API/One2One.hs +++ b/libs/wire-subsystems/test/unit/Wire/ConversationSubsystem/One2OneSpec.hs @@ -16,36 +16,33 @@ -- with this program. If not, see . -- | Tests for one-to-one conversations -module Test.Galley.API.One2One where +module Wire.ConversationSubsystem.One2OneSpec where import Data.Id import Data.List.Extra import Data.Qualified import Imports -import Test.Tasty -import Test.Tasty.HUnit (Assertion, testCase, (@?=)) -import Test.Tasty.QuickCheck +import Test.Hspec +import Test.Hspec.QuickCheck +import Test.QuickCheck import Wire.API.User import Wire.ConversationSubsystem.One2One (one2OneConvId) -tests :: TestTree -tests = - testGroup - "one2OneConvId" - [ testProperty "symmetry" one2OneConvIdSymmetry, - testCase "non-collision" one2OneConvIdNonCollision - ] +spec :: Spec +spec = describe "one2OneConvId" do + prop "symmetry" one2OneConvIdSymmetry + it "non-collision" one2OneConvIdNonCollision one2OneConvIdSymmetry :: BaseProtocolTag -> Qualified UserId -> Qualified UserId -> Property one2OneConvIdSymmetry proto quid1 quid2 = one2OneConvId proto quid1 quid2 === one2OneConvId proto quid2 quid1 -- | Make sure that we never get the same conversation ID for a pair of -- (assumingly) distinct qualified user IDs -one2OneConvIdNonCollision :: Assertion +one2OneConvIdNonCollision :: IO () one2OneConvIdNonCollision = do let len = 10_000 -- A generator of lists of length 'len' of qualified user ID pairs let gen = vectorOf len arbitrary quids <- nubOrd <$> generate gen let hashes = nubOrd (fmap (uncurry (one2OneConvId BaseProtocolProteusTag)) quids) - length hashes @?= length quids + length hashes `shouldBe` length quids diff --git a/libs/wire-subsystems/test/unit/Wire/MockInterpreters/ConversationSubsystem.hs b/libs/wire-subsystems/test/unit/Wire/MockInterpreters/ConversationSubsystem.hs index 74230077f5a..e0dfadc71cc 100644 --- a/libs/wire-subsystems/test/unit/Wire/MockInterpreters/ConversationSubsystem.hs +++ b/libs/wire-subsystems/test/unit/Wire/MockInterpreters/ConversationSubsystem.hs @@ -41,8 +41,8 @@ type ConversationMembers = Map ConvId (Set UserId) inMemoryConversationSubsystemInterpreter :: (Member (State (Map ConvId StoredConversation)) r, Member (State ConversationMembers) r, Member Random r) => InterpreterFor ConversationSubsystem r -inMemoryConversationSubsystemInterpreter = interpret $ \case - CreateGroupConversation lusr _mconn newConv -> do +inMemoryConversationSubsystemInterpreter = interpretH $ \case + InternalCreateGroupConversation lusr _mconn newConv -> do cid <- Random.newId let conv = StoredConversation @@ -71,8 +71,8 @@ inMemoryConversationSubsystemInterpreter = interpret $ \case } modify (Map.insert cid conv) modify (Map.insert cid (Set.singleton (tUnqualified lusr))) - pure conv + pureT conv InternalGetLocalMember cid uid -> do members <- gets (Map.lookup cid) - pure $ if Set.member uid (fromMaybe Set.empty members) then Just (newMember uid) else Nothing + pureT $ if Set.member uid (fromMaybe Set.empty members) then Just (newMember uid) else Nothing _ -> error "ConversationSubsystem: not implemented in mock" diff --git a/libs/wire-subsystems/wire-subsystems.cabal b/libs/wire-subsystems/wire-subsystems.cabal index e721dc4c5fa..ab8c25cdd51 100644 --- a/libs/wire-subsystems/wire-subsystems.cabal +++ b/libs/wire-subsystems/wire-subsystems.cabal @@ -98,10 +98,12 @@ common common-all , base64-bytestring , bilge , bloodhound + , brig-types , bytestring , bytestring-conversion , case-insensitive , cassandra-util + , comonad , conduit , constraints , containers @@ -139,6 +141,7 @@ common common-all , imports , iproute , iso639 + , kan-extensions , lens , lens-aeson , lrucaching @@ -252,12 +255,49 @@ library Wire.ConversationStore.MLS.Types Wire.ConversationStore.Postgres Wire.ConversationSubsystem + Wire.ConversationSubsystem.Action + Wire.ConversationSubsystem.Action.Kick + Wire.ConversationSubsystem.Action.Leave + Wire.ConversationSubsystem.Action.Notify + Wire.ConversationSubsystem.Action.Reset + Wire.ConversationSubsystem.Clients + Wire.ConversationSubsystem.Create Wire.ConversationSubsystem.CreateInternal + Wire.ConversationSubsystem.Features + Wire.ConversationSubsystem.Federation Wire.ConversationSubsystem.Fetch Wire.ConversationSubsystem.Internal Wire.ConversationSubsystem.Interpreter + Wire.ConversationSubsystem.Legalhold + Wire.ConversationSubsystem.LegalholdConflicts + Wire.ConversationSubsystem.Mapping + Wire.ConversationSubsystem.Message + Wire.ConversationSubsystem.MLS + Wire.ConversationSubsystem.MLS.CheckClients + Wire.ConversationSubsystem.MLS.Commit.Core + Wire.ConversationSubsystem.MLS.Commit.ExternalCommit + Wire.ConversationSubsystem.MLS.Commit.InternalCommit + Wire.ConversationSubsystem.MLS.Conversation + Wire.ConversationSubsystem.MLS.Enabled + Wire.ConversationSubsystem.MLS.GroupInfo + Wire.ConversationSubsystem.MLS.GroupInfoCheck + Wire.ConversationSubsystem.MLS.IncomingMessage + Wire.ConversationSubsystem.MLS.Keys + Wire.ConversationSubsystem.MLS.Message + Wire.ConversationSubsystem.MLS.Migration + Wire.ConversationSubsystem.MLS.One2One + Wire.ConversationSubsystem.MLS.OutOfSync + Wire.ConversationSubsystem.MLS.Propagate + Wire.ConversationSubsystem.MLS.Proposal + Wire.ConversationSubsystem.MLS.Removal + Wire.ConversationSubsystem.MLS.Reset + Wire.ConversationSubsystem.MLS.SubConversation + Wire.ConversationSubsystem.MLS.Util + Wire.ConversationSubsystem.MLS.Welcome Wire.ConversationSubsystem.Notify Wire.ConversationSubsystem.One2One + Wire.ConversationSubsystem.Query + Wire.ConversationSubsystem.Update Wire.ConversationSubsystem.Util Wire.CustomBackendStore Wire.CustomBackendStore.Cassandra @@ -532,6 +572,9 @@ test-suite wire-subsystems-tests Wire.AuthenticationSubsystem.InterpreterSpec Wire.BrigAPIAccess.RpcSpec Wire.ClientSubsystem.InterpreterSpec + Wire.ConversationSubsystem.MappingSpec + Wire.ConversationSubsystem.MessageSpec + Wire.ConversationSubsystem.One2OneSpec Wire.EnterpriseLoginSubsystem.InterpreterSpec Wire.FederationSubsystem.InternalsSpec Wire.HashPassword.InterpreterSpec diff --git a/services/background-worker/background-worker.integration.yaml b/services/background-worker/background-worker.integration.yaml index 167dfc498b9..4b3978cab45 100644 --- a/services/background-worker/background-worker.integration.yaml +++ b/services/background-worker/background-worker.integration.yaml @@ -79,6 +79,37 @@ backgroundJobs: jobTimeout: 5s maxAttempts: 3 +settings: + maxTeamSize: 32 + maxFanoutSize: 18 + exposeInvitationURLsTeamAllowlist: [] + maxConvSize: 16 + intraListing: false + conversationCodeURI: https://account.wire.com/conversation-join/ + federationProtocols: ["mls", "proteus"] + guestLinkTTLSeconds: 604800 + passwordHashingOptions: + algorithm: argon2id + iterations: 1 + memory: 128 + parallelism: 1 + passwordHashingRateLimit: + ipAddrLimit: + burst: 5 + inverseRate: 300000000 + userLimit: + burst: 5 + inverseRate: 60000000 + internalLimit: + burst: 10 + inverseRate: 0 + ipv4CidrBlock: 32 + ipv6CidrBlock: 64 + ipAddressExceptions: + - 127.0.0.1/8 + maxRateLimitedKeys: 100000 + checkGroupInfo: false + postgresMigration: conversation: postgresql conversationCodes: postgresql diff --git a/services/background-worker/src/Wire/BackgroundWorker/Env.hs b/services/background-worker/src/Wire/BackgroundWorker/Env.hs index 4af2a9df1bc..97e5de053af 100644 --- a/services/background-worker/src/Wire/BackgroundWorker/Env.hs +++ b/services/background-worker/src/Wire/BackgroundWorker/Env.hs @@ -27,6 +27,7 @@ import Control.Monad.Catch import Control.Monad.Trans.Control import Data.Domain (Domain) import Data.Map.Strict qualified as Map +import Data.Misc (HttpsUrl) import HTTP2.Client.Manager import Hasql.Pool qualified as Hasql import Hasql.Pool.Extended @@ -45,6 +46,7 @@ import System.Logger.Extended qualified as Log import Util.Options import Wire.BackgroundWorker.Options import Wire.PostgresMigrationOpts +import Wire.RateLimit.Interpreter (RateLimitEnv, newRateLimitEnv) type IsWorking = Bool @@ -86,7 +88,10 @@ data Env = Env gundeckEndpoint :: Endpoint, sparEndpoint :: Endpoint, galleyEndpoint :: Endpoint, - brigEndpoint :: Endpoint + brigEndpoint :: Endpoint, + settings :: Settings, + convCodeURI :: Either HttpsUrl (Map Text HttpsUrl), + passwordHashingRateLimitEnv :: RateLimitEnv } data BackendNotificationMetrics = BackendNotificationMetrics @@ -137,6 +142,14 @@ mkEnv opts = do galleyEndpoint = opts.galley gundeckEndpoint = opts.gundeck sparEndpoint = opts.spar + settings = opts.settings + let errMsg = "Either conversationCodeURI or multiIngress needs to be set." + convCodeURI <- case (settings.conversationCodeURI, settings.multiIngress) of + (Nothing, Nothing) -> error errMsg + (Nothing, Just mi) -> pure (Right mi) + (Just uri, Nothing) -> pure (Left uri) + (Just _, Just _) -> error errMsg + passwordHashingRateLimitEnv <- newRateLimitEnv settings.passwordHashingRateLimit workerRunningGauge <- mkWorkerRunningGauge hasqlPool <- initPostgresPool opts.postgresqlPool opts.postgresql opts.postgresqlPassword amqpJobsPublisherChannel <- diff --git a/services/background-worker/src/Wire/BackgroundWorker/Jobs/Registry.hs b/services/background-worker/src/Wire/BackgroundWorker/Jobs/Registry.hs index 4c13bf2d047..9fe74b14375 100644 --- a/services/background-worker/src/Wire/BackgroundWorker/Jobs/Registry.hs +++ b/services/background-worker/src/Wire/BackgroundWorker/Jobs/Registry.hs @@ -22,6 +22,7 @@ where import Bilge qualified import Bilge.Retry +import Cassandra (ClientState) import Control.Monad.Catch import Control.Retry import Data.ByteString qualified as BS @@ -34,6 +35,7 @@ import Data.Text qualified as T import Data.Text.Lazy qualified as TL import Galley.Types.Error (InternalError, InvalidInput, internalErrorDescription, legalHoldServiceUnavailable) import Hasql.Pool (UsageError) +import Hasql.Pool qualified as Hasql import Imports import Network.HTTP.Client qualified as Http import OpenSSL.Session qualified as SSL @@ -42,41 +44,54 @@ import Polysemy.Async (asyncToIOFinal) import Polysemy.Conc import Polysemy.Error import Polysemy.Input +import Polysemy.Resource (resourceToIOFinal) import Polysemy.TinyLog qualified as P import Ssl.Util import System.Logger as Logger import System.Logger.Class qualified as Log import URI.ByteString (uriPath) import Wire.API.BackgroundJobs (Job (..)) -import Wire.API.Conversation.Config (ConversationSubsystemConfig) +import Wire.API.Conversation.Config (ConversationSubsystemConfig (..)) +import Wire.API.Conversation.Role qualified as Role import Wire.API.Error.Galley import Wire.API.Federation.Error (FederationError) +import Wire.API.MLS.Keys (MLSKeysByPurpose, MLSPrivateKeys) import Wire.API.Team.Collaborator (TeamCollaboratorsError) -import Wire.API.Team.FeatureFlags (FeatureDefaults (FeatureLegalHoldDisabledPermanently)) +import Wire.API.Team.Feature (LegalholdConfig) +import Wire.API.Team.FeatureFlags (FanoutLimit, FeatureDefaults (FeatureLegalHoldDisabledPermanently), currentFanoutLimit) import Wire.BackendNotificationQueueAccess.RabbitMq qualified as BackendNotificationQueueAccess import Wire.BackgroundJobsPublisher.RabbitMQ (interpretBackgroundJobsPublisherRabbitMQ) import Wire.BackgroundJobsRunner (runJob) import Wire.BackgroundJobsRunner.Interpreter hiding (runJob) import Wire.BackgroundWorker.Env (AppT, Env (..)) +import Wire.BackgroundWorker.Options (Settings (..)) import Wire.BrigAPIAccess.Rpc import Wire.ClientSubsystem.Error (ClientError) +import Wire.CodeStore.Cassandra (interpretCodeStoreToCassandra) +import Wire.CodeStore.DualWrite (interpretCodeStoreToCassandraAndPostgres) +import Wire.CodeStore.Postgres (interpretCodeStoreToPostgres) import Wire.ConversationStore.Cassandra import Wire.ConversationStore.Postgres (interpretConversationStoreToPostgres) -import Wire.ConversationSubsystem.Interpreter (interpretConversationSubsystem) +import Wire.ConversationSubsystem.Interpreter (GroupInfoCheckEnabled (..), GuestLinkTTLSeconds (..), IntraListing (..), interpretConversationSubsystem) import Wire.ExternalAccess.External import Wire.FeaturesConfigSubsystem (getAllTeamFeaturesForServer) import Wire.FeaturesConfigSubsystem.Interpreter (runFeaturesConfigSubsystem) import Wire.FeaturesConfigSubsystem.Types (ExposeInvitationURLsAllowlist (..)) import Wire.FederationAPIAccess.Interpreter (FederationAPIAccessConfig (..), interpretFederationAPIAccess) +import Wire.FederationSubsystem.Interpreter (runFederationSubsystem) import Wire.FireAndForget (interpretFireAndForget) import Wire.GalleyAPIAccess import Wire.GalleyAPIAccess.Rpc (interpretGalleyAPIAccessToRpc) import Wire.GundeckAPIAccess +import Wire.HashPassword.Interpreter (runHashPassword) import Wire.LegalHoldStore.Cassandra (interpretLegalHoldStoreToCassandra) import Wire.LegalHoldStore.Env (LegalHoldEnv (..)) import Wire.NotificationSubsystem.Interpreter import Wire.ParseException import Wire.PostgresMigrationOpts +import Wire.ProposalStore.Cassandra (interpretProposalStoreToCassandra) +import Wire.RateLimit (RateLimitExceeded) +import Wire.RateLimit.Interpreter (interpretRateLimit) import Wire.Rpc import Wire.Sem.Concurrency (ConcurrencySafety (Unsafe)) import Wire.Sem.Concurrency.IO (unsafelyPerformConcurrency) @@ -174,12 +189,18 @@ dispatchJob job = do let makeReq fpr url rb = makeVerifiedRequestIO env.logger extEnv fpr url rb makeReqFresh fpr url rb = makeVerifiedRequestFreshManagerIO env.logger fpr url rb in LegalHoldEnv {makeVerifiedRequest = makeReq, makeVerifiedRequestFreshManager = makeReqFresh} + convCodesStoreInterpreter = + case env.postgresMigration.conversationCodes of + CassandraStorage -> interpretCodeStoreToCassandra + MigrationToPostgresql -> interpretCodeStoreToCassandraAndPostgres + PostgresqlStorage -> interpretCodeStoreToPostgres runFinal @IO . unsafelyPerformConcurrency @_ @'Unsafe . embedToFinal @IO . asyncToIOFinal . interpretRace . runDelay + . resourceToIOFinal . runError . mapError @ClientError (T.pack . displayException) . mapError @FederationError (T.pack . displayException) @@ -206,13 +227,71 @@ dispatchJob job = do . mapError @(Tagged 'ConvNotFound ()) (const ("Conversation not found" :: Text)) . mapError @(Tagged 'ChannelsNotEnabled ()) (const ("Channels not enabled" :: Text)) . mapError @(Tagged 'NotAnMlsConversation ()) (const ("Not an MLS conversation" :: Text)) + . mapError @(Tagged ('ActionDenied Role.DeleteConversation) ()) (const ("Cannot delete conversation" :: Text)) + . mapError @(Tagged ('ActionDenied Role.LeaveConversation) ()) (const ("Cannot leave conversation" :: Text)) + . mapError @(Tagged ('ActionDenied Role.ModifyConversationAccess) ()) (const ("Cannot modify conversation access" :: Text)) + . mapError @(Tagged ('ActionDenied Role.ModifyConversationName) ()) (const ("Cannot modify conversation name" :: Text)) + . mapError @(Tagged ('ActionDenied Role.ModifyConversationMessageTimer) ()) (const ("Cannot modify conversation message timer" :: Text)) + . mapError @(Tagged ('ActionDenied Role.ModifyConversationReceiptMode) ()) (const ("Cannot modify conversation receipt mode" :: Text)) + . mapError @(Tagged ('ActionDenied Role.ModifyAddPermission) ()) (const ("Cannot modify add permission" :: Text)) + . mapError @(Tagged ('ActionDenied Role.AddConversationMember) ()) (const ("Cannot add member to conversation" :: Text)) + . mapError @(Tagged ('ActionDenied Role.ModifyOtherConversationMember) ()) (const ("Cannot modify member from conversation" :: Text)) + . mapError @(Tagged ('ActionDenied Role.RemoveConversationMember) ()) (const ("Cannot remove member from conversation" :: Text)) + . mapError @(Tagged 'MLSLegalholdIncompatible ()) (const ("MLS - Legalhold is incompatible" :: Text)) + . mapError @(Tagged 'MLSIdentityMismatch ()) (const ("MLS - identity mismatch" :: Text)) + . mapError @(Tagged 'MLSUnsupportedMessage ()) (const ("MLS - unsupported message" :: Text)) + . mapError @(Tagged 'MLSStaleMessage ()) (const ("MLS - stale message" :: Text)) + . mapError @(Tagged 'MLSProposalNotFound ()) (const ("MLS - proposal not found" :: Text)) + . mapError @(Tagged 'MLSUnsupportedProposal ()) (const ("MLS - proposal not supported" :: Text)) + . mapError @(Tagged 'MLSCommitMissingReferences ()) (const ("MLS - commit missing references" :: Text)) + . mapError @(Tagged 'MLSSelfRemovalNotAllowed ()) (const ("MLS - self-removal is denied" :: Text)) + . mapError @(Tagged 'MLSClientSenderUserMismatch ()) (const ("MLS - client-sender user mismatch" :: Text)) + . mapError @(Tagged 'MLSSubConvClientNotInParent ()) (const ("MLS - sub-conversation client is missing in the parent conversation" :: Text)) + . mapError @(Tagged 'MLSInvalidLeafNodeSignature ()) (const ("MLS - invalid leaf node signature" :: Text)) + . mapError @(Tagged 'MLSInvalidLeafNodeIndex ()) (const ("MLS - leaf node index" :: Text)) + . mapError @(Tagged 'MLSGroupConversationMismatch ()) (const ("MLS - group-conversation mismatch" :: Text)) + . mapError @(Tagged 'MLSFederatedResetNotSupported ()) (const ("MLS - federated reset not supported" :: Text)) + . mapError @(Tagged 'MLSSubConvUnsupportedConvType ()) (const ("MLS - sub-conversation unsopported ConvType" :: Text)) + . mapError @(Tagged 'MLSClientMismatch ()) (const ("MLS - client mismatch" :: Text)) + . mapError @(Tagged 'MLSMissingGroupInfo ()) (const ("MLS - missing group info" :: Text)) + . mapError @(Tagged 'MLSFederatedOne2OneNotSupported ()) (const ("MLS - federation One2One not supported" :: Text)) + . mapError @(Tagged 'MLSReadReceiptsNotAllowed ()) (const ("MLS - read receipts not allowed" :: Text)) + . mapError @(Tagged 'MLSMigrationCriteriaNotSatisfied ()) (const ("MLS - migration criteria not satisfied" :: Text)) + . mapError @(Tagged 'GroupIdVersionNotSupported ()) (const ("groupId version not supported" :: Text)) + . mapError @(Tagged 'ConvMemberNotFound ()) (const ("Conversation member not found" :: Text)) + . mapError @(Tagged 'BroadcastLimitExceeded ()) (const ("Broadcast limit exceeded" :: Text)) + . mapError @(Tagged 'TeamMemberNotFound ()) (const ("Team member not found" :: Text)) + . mapError @(Tagged 'AccessDenied ()) (const ("Access denied" :: Text)) + . mapError @(Tagged 'CodeNotFound ()) (const ("Conversation not found" :: Text)) + . mapError @(Tagged 'InvalidConversationPassword ()) (const ("Invalid conversation password" :: Text)) + . mapError @(Tagged 'TooManyMembers ()) (const ("Too many members" :: Text)) + . mapError @(Tagged 'CreateConversationCodeConflict ()) (const ("Create conversation code conflict" :: Text)) + . mapError @(Tagged 'InvalidTarget ()) (const ("Invalid target" :: Text)) + . mapError @(Tagged 'GuestLinksDisabled ()) (const ("Guest links disabled" :: Text)) + . mapError @(Tagged 'InvalidTargetAccess ()) (const ("Invalid target access" :: Text)) + . mapError @(Tagged 'ConvInvalidProtocolTransition ()) (const ("Conversation invalid protocol transition" :: Text)) + . mapError @MLSProtocolError (const ("MLS - protocol error" :: Text)) + . mapError @MLSOutOfSyncError (const ("MLS - out-of-sync error" :: Text)) + . mapError @MLSProposalFailure (const ("MLS - proposal failure" :: Text)) + . mapError @AuthenticationError (const ("Authentication error" :: Text)) + . mapError @GroupInfoDiagnostics (const ("Group info diagnostics" :: Text)) + . mapError @NonFederatingBackends (const ("Non federating backends" :: Text)) + . mapError @UnreachableBackendsLegacy (const ("Unreachable backends legacy" :: Text)) + . mapError @RateLimitExceeded (const ("Rate limit exceeded" :: Text)) . interpretTinyLog env job.requestId job.jobId - . runInputConst env.hasqlPool - . runInputConst (toLocalUnsafe env.federationDomain ()) - . runInputConst (FeatureLegalHoldDisabledPermanently) - . runInputConst env.cassandraGalley - . runInputConst legalHoldEnv - . runInputConst (ExposeInvitationURLsAllowlist []) + . runInputConst @Hasql.Pool env.hasqlPool + . runInputConst @(Local ()) (toLocalUnsafe env.federationDomain ()) + . runInputConst @(FeatureDefaults LegalholdConfig) FeatureLegalHoldDisabledPermanently + . runInputConst @ClientState env.cassandraGalley + . runInputConst @LegalHoldEnv legalHoldEnv + . runInputConst @ExposeInvitationURLsAllowlist (ExposeInvitationURLsAllowlist $ fromMaybe [] env.settings.exposeInvitationURLsTeamAllowlist) + . runInputConst @(Either HttpsUrl (Map Text HttpsUrl)) env.convCodeURI + . runInputConst @IntraListing (IntraListing env.settings.intraListing) + . runInputConst @(Maybe GroupInfoCheckEnabled) (GroupInfoCheckEnabled <$> env.settings.checkGroupInfo) + . runInputConst @(Maybe GuestLinkTTLSeconds) env.settings.guestLinkTTLSeconds + . runInputConst @FanoutLimit (currentFanoutLimit env.settings.maxTeamSize env.settings.maxFanoutSize) + . interpretMLSCommitLockStoreToCassandra env.cassandraGalley + . interpretProposalStoreToCassandra . interpretServiceStoreToCassandra env.cassandraBrig . interpretUserGroupStoreToPostgres . interpretTeamFeatureStoreToCassandra @@ -235,12 +314,20 @@ dispatchJob job = do . interpretBrigAccess env.brigEndpoint . interpretGalleyAPIAccessToRpc mempty env.galleyEndpoint . runInputSem getConversationSubsystemConfig + . runInputSem @(Maybe (MLSKeysByPurpose MLSPrivateKeys)) (inputs @ConversationSubsystemConfig (.mlsKeys)) . runInputSem getConfiguredFeatureFlags + . runHashPassword env.settings.passwordHashingOptions + . interpretRateLimit env.passwordHashingRateLimitEnv + . convCodesStoreInterpreter . interpretExternalAccess extEnv . interpretSparAPIAccessToRpc env.sparEndpoint . runNotificationSubsystemGundeck (defaultNotificationSubsystemConfig job.requestId) . interpretFederationAPIAccess federationAPIAccessConfig . interpretTeamSubsystem teamSubsystemConfig + . ( \m -> do + p <- inputs @ConversationSubsystemConfig (.federationProtocols) + runFederationSubsystem p m + ) . runFeaturesConfigSubsystem . runInputSem getAllTeamFeaturesForServer . interpretTeamCollaboratorsSubsystem @@ -252,6 +339,7 @@ dispatchJob job = do Sem r ConversationSubsystemConfig getConversationSubsystemConfig = getConversationConfig + backendQueueEnv :: Env -> BackendNotificationQueueAccess.Env backendQueueEnv env = BackendNotificationQueueAccess.Env { channelMVar = env.amqpBackendNotificationsChannel, diff --git a/services/background-worker/src/Wire/BackgroundWorker/Options.hs b/services/background-worker/src/Wire/BackgroundWorker/Options.hs index 981fc7538d7..74b3ca486ae 100644 --- a/services/background-worker/src/Wire/BackgroundWorker/Options.hs +++ b/services/background-worker/src/Wire/BackgroundWorker/Options.hs @@ -19,16 +19,21 @@ module Wire.BackgroundWorker.Options where import Data.Aeson import Data.Domain (Domain) +import Data.Id (TeamId) import Data.Misc import Data.Range (Range) import GHC.Generics import Hasql.Pool.Extended import Imports import Network.AMQP.Extended -import System.Logger.Extended +import System.Logger.Extended hiding (Settings) import Util.Options +import Wire.API.Conversation.Protocol (ProtocolTag) +import Wire.API.Team.FeatureFlags (FanoutLimit) +import Wire.ConversationSubsystem.Interpreter (GuestLinkTTLSeconds) import Wire.Migration import Wire.PostgresMigrationOpts +import Wire.RateLimit.Interpreter (RateLimitConfig) data Opts = Opts { logLevel :: !Level, @@ -57,11 +62,29 @@ data Opts = Opts migrateConversationCodes :: !Bool, migrateTeamFeatures :: !Bool, backgroundJobs :: BackgroundJobsConfig, - federationDomain :: Domain + federationDomain :: Domain, + settings :: !Settings } deriving (Show, Generic) deriving (FromJSON) via Generically Opts +data Settings = Settings + { maxTeamSize :: !Word32, + maxFanoutSize :: !(Maybe FanoutLimit), + exposeInvitationURLsTeamAllowlist :: !(Maybe [TeamId]), + maxConvSize :: !Word16, + intraListing :: !Bool, + conversationCodeURI :: !(Maybe HttpsUrl), + multiIngress :: !(Maybe (Map Text HttpsUrl)), + federationProtocols :: !(Maybe [ProtocolTag]), + guestLinkTTLSeconds :: !(Maybe GuestLinkTTLSeconds), + passwordHashingOptions :: !PasswordHashingOptions, + passwordHashingRateLimit :: !RateLimitConfig, + checkGroupInfo :: !(Maybe Bool) + } + deriving (Show, Generic) + deriving (FromJSON) via Generically Settings + data BackendNotificationsConfig = BackendNotificationsConfig { -- | Minimum amount of time (in microseconds) to wait before doing the first -- retry in pushing a notification. Futher retries are done in a jittered diff --git a/services/background-worker/test/Test/Wire/BackendNotificationPusherSpec.hs b/services/background-worker/test/Test/Wire/BackendNotificationPusherSpec.hs index 30a9c045733..122bd6964f6 100644 --- a/services/background-worker/test/Test/Wire/BackendNotificationPusherSpec.hs +++ b/services/background-worker/test/Test/Wire/BackendNotificationPusherSpec.hs @@ -70,6 +70,7 @@ import Wire.BackgroundWorker.Env import Wire.BackgroundWorker.Options import Wire.BackgroundWorker.Util import Wire.PostgresMigrationOpts +import Wire.RateLimit.Interpreter (newRateLimitEnv) spec :: Spec spec = do @@ -371,7 +372,23 @@ spec = do brigEndpoint = undefined sparEndpoint = undefined galleyEndpoint = undefined - + settings = + Settings + { maxTeamSize = 1000, + maxFanoutSize = Nothing, + exposeInvitationURLsTeamAllowlist = Nothing, + maxConvSize = 1000, + intraListing = True, + conversationCodeURI = Nothing, + multiIngress = Nothing, + federationProtocols = Nothing, + guestLinkTTLSeconds = Nothing, + passwordHashingOptions = undefined, + passwordHashingRateLimit = undefined, + checkGroupInfo = Nothing + } + convCodeURI = Left (fromRight (error "Failed to parse test HttpsUrl") $ httpsUrlFromText "https://localhost") + passwordHashingRateLimitEnv <- newRateLimitEnv undefined backendNotificationMetrics <- mkBackendNotificationMetrics workerRunningGauge <- mkWorkerRunningGauge domains <- runAppT Env {..} $ getRemoteDomains (fromJust rabbitmqAdminClient) @@ -412,6 +429,23 @@ spec = do brigEndpoint = undefined sparEndpoint = undefined galleyEndpoint = undefined + settings = + Settings + { maxTeamSize = 1000, + maxFanoutSize = Nothing, + exposeInvitationURLsTeamAllowlist = Nothing, + maxConvSize = 1000, + intraListing = True, + conversationCodeURI = Nothing, + multiIngress = Nothing, + federationProtocols = Nothing, + guestLinkTTLSeconds = Nothing, + passwordHashingOptions = undefined, + passwordHashingRateLimit = undefined, + checkGroupInfo = Nothing + } + convCodeURI = Left (fromRight (error "Failed to parse test HttpsUrl") $ httpsUrlFromText "https://localhost") + passwordHashingRateLimitEnv <- newRateLimitEnv undefined backendNotificationMetrics <- mkBackendNotificationMetrics workerRunningGauge <- mkWorkerRunningGauge domainsThread <- async $ runAppT Env {..} $ getRemoteDomains (fromJust rabbitmqAdminClient) diff --git a/services/background-worker/test/Test/Wire/Util.hs b/services/background-worker/test/Test/Wire/Util.hs index 9810b9c2659..1d410317fe8 100644 --- a/services/background-worker/test/Test/Wire/Util.hs +++ b/services/background-worker/test/Test/Wire/Util.hs @@ -31,6 +31,7 @@ import Wire.BackgroundWorker.Env hiding (federatorInternal) import Wire.BackgroundWorker.Env qualified as E import Wire.BackgroundWorker.Options import Wire.PostgresMigrationOpts +import Wire.RateLimit.Interpreter (newRateLimitEnv) testEnv :: IO Env testEnv = do @@ -68,6 +69,23 @@ testEnv = do brigEndpoint = undefined sparEndpoint = Endpoint "localhost" 0 galleyEndpoint = undefined + settings = + Settings + { maxTeamSize = 1000, + maxFanoutSize = Nothing, + exposeInvitationURLsTeamAllowlist = Nothing, + maxConvSize = 1000, + intraListing = True, + conversationCodeURI = Nothing, + multiIngress = Nothing, + federationProtocols = Nothing, + guestLinkTTLSeconds = Nothing, + passwordHashingOptions = undefined, + passwordHashingRateLimit = undefined, + checkGroupInfo = Nothing + } + convCodeURI = Left (fromRight (error "Failed to parse test HttpsUrl") $ httpsUrlFromText "https://localhost") + passwordHashingRateLimitEnv <- newRateLimitEnv undefined pure Env {..} runTestAppT :: AppT IO a -> Int -> IO a diff --git a/services/galley/default.nix b/services/galley/default.nix index 1f6c79d4095..b31f76478af 100644 --- a/services/galley/default.nix +++ b/services/galley/default.nix @@ -20,7 +20,6 @@ , cassandra-util , cassava , cereal -, comonad , conduit , containers , cookie @@ -38,7 +37,6 @@ , galley-types , gitignoreSource , hasql-pool -, hex , hs-opentelemetry-instrumentation-wai , hs-opentelemetry-sdk , HsOpenSSL @@ -92,7 +90,6 @@ , tasty-ant-xml , tasty-cannon , tasty-hunit -, tasty-quickcheck , temporary , text , time @@ -107,8 +104,6 @@ , uri-bytestring , utf8-string , uuid -, uuid-types -, vector , wai , wai-extra , wai-middleware-gunzip @@ -140,7 +135,6 @@ mkDerivation { bytestring-conversion cassandra-util cassava - comonad containers crypton crypton-x509 @@ -148,10 +142,8 @@ mkDerivation { errors exceptions extended - extra galley-types hasql-pool - hex hs-opentelemetry-instrumentation-wai hs-opentelemetry-sdk HsOpenSSL @@ -178,11 +170,9 @@ mkDerivation { servant servant-server singletons - sop-core split ssl-util stm - tagged text time tinylog @@ -193,7 +183,6 @@ mkDerivation { uri-bytestring utf8-string uuid - vector wai wai-extra wai-middleware-gunzip @@ -286,24 +275,7 @@ mkDerivation { wire-subsystems yaml ]; - testHaskellDepends = [ - base - containers - extra - galley-types - imports - lens - polysemy - polysemy-wire-zoo - tasty - tasty-hunit - tasty-quickcheck - types-common - uuid-types - wire-api - wire-api-federation - wire-subsystems - ]; + testHaskellDepends = [ base imports tasty ]; description = "Conversations"; license = lib.licenses.agpl3Only; } diff --git a/services/galley/galley.cabal b/services/galley/galley.cabal index 4e73fa3667c..324d1fbcb82 100644 --- a/services/galley/galley.cabal +++ b/services/galley/galley.cabal @@ -73,46 +73,12 @@ library -- cabal-fmt: expand src exposed-modules: - Galley.API.Action - Galley.API.Action.Kick - Galley.API.Action.Leave - Galley.API.Action.Notify - Galley.API.Action.Reset - Galley.API.Clients - Galley.API.Create Galley.API.CustomBackend Galley.API.Federation - Galley.API.Federation.Handlers Galley.API.Internal Galley.API.LegalHold - Galley.API.LegalHold.Conflicts - Galley.API.LegalHold.Get Galley.API.LegalHold.Team - Galley.API.Mapping Galley.API.Meetings - Galley.API.Message - Galley.API.MLS - Galley.API.MLS.CheckClients - Galley.API.MLS.Commit.Core - Galley.API.MLS.Commit.ExternalCommit - Galley.API.MLS.Commit.InternalCommit - Galley.API.MLS.Conversation - Galley.API.MLS.Enabled - Galley.API.MLS.GroupInfo - Galley.API.MLS.GroupInfoCheck - Galley.API.MLS.IncomingMessage - Galley.API.MLS.Keys - Galley.API.MLS.Message - Galley.API.MLS.Migration - Galley.API.MLS.One2One - Galley.API.MLS.OutOfSync - Galley.API.MLS.Propagate - Galley.API.MLS.Proposal - Galley.API.MLS.Removal - Galley.API.MLS.Reset - Galley.API.MLS.SubConversation - Galley.API.MLS.Util - Galley.API.MLS.Welcome Galley.API.Public.Bot Galley.API.Public.Conversation Galley.API.Public.CustomBackend @@ -126,13 +92,10 @@ library Galley.API.Public.TeamConversation Galley.API.Public.TeamMember Galley.API.Public.TeamNotification - Galley.API.Query Galley.API.Teams Galley.API.Teams.Export Galley.API.Teams.Features - Galley.API.Teams.Features.Get Galley.API.Teams.Notifications - Galley.API.Update Galley.App Galley.Cassandra Galley.Effects.Queue @@ -245,7 +208,6 @@ library , bytestring-conversion >=0.2 , cassandra-util >=0.16.2 , cassava >=0.5.2 - , comonad , containers >=0.5 , crypton , crypton-x509 @@ -253,10 +215,8 @@ library , errors >=2.0 , exceptions >=0.4 , extended - , extra >=1.3 , galley-types >=0.65.0 , hasql-pool - , hex , hs-opentelemetry-instrumentation-wai , hs-opentelemetry-sdk , HsOpenSSL >=0.11 @@ -283,11 +243,9 @@ library , servant , servant-server , singletons - , sop-core , split >=0.2 , ssl-util >=0.1 , stm >=2.4 - , tagged , text >=0.11 , time >=1.4 , tinylog >=0.10 @@ -298,7 +256,6 @@ library , uri-bytestring >=0.2 , utf8-string , uuid >=1.3 - , vector , wai >=3.0 , wai-extra >=3.0 , wai-middleware-gunzip >=0.0.2 @@ -535,27 +492,10 @@ test-suite galley-tests other-modules: Paths_galley Run - Test.Galley.API.Message - Test.Galley.API.One2One - Test.Galley.Mapping ghc-options: -threaded -with-rtsopts=-N -Wno-x-partial hs-source-dirs: test/unit build-depends: , base - , containers - , extra >=1.3 - , galley - , galley-types , imports - , lens - , polysemy - , polysemy-wire-zoo , tasty - , tasty-hunit - , tasty-quickcheck - , types-common - , uuid-types - , wire-api - , wire-api-federation - , wire-subsystems diff --git a/services/galley/src/Galley/API/Create.hs b/services/galley/src/Galley/API/Create.hs deleted file mode 100644 index 638e2b56cfe..00000000000 --- a/services/galley/src/Galley/API/Create.hs +++ /dev/null @@ -1,140 +0,0 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE DuplicateRecordFields #-} - --- This file is part of the Wire Server implementation. --- --- Copyright (C) 2022 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 Galley.API.Create where - -import Data.Id -import Data.Qualified -import Data.Set qualified as Set -import Galley.API.Mapping -import Galley.Types.Error -import Imports -import Polysemy -import Polysemy.Error -import Polysemy.TinyLog qualified as P -import Wire.API.Conversation (CreateGroupConversation (..), CreateGroupOwnConversation (..), NewConv, NewOne2OneConv) -import Wire.API.Conversation qualified as Public -import Wire.API.Error.Galley (UnreachableBackendsLegacy (..)) -import Wire.API.Event.Conversation (Connect) -import Wire.API.FederationStatus (RemoteDomains (..)) -import Wire.API.Routes.Public.Galley.Conversation -import Wire.API.Routes.Public.Util (ResponseForExistedCreated (..)) -import Wire.API.User (baseProtocolToProtocol) -import Wire.ConversationSubsystem qualified as ConversationSubsystem -import Wire.FederationSubsystem (FederationSubsystem, checkFederationStatus, enforceFederationProtocol) - ----------------------------------------------------------------------------- --- API Handlers - -createGroupConversationUpToV3 :: - ( Member ConversationSubsystem.ConversationSubsystem r, - Member (Error UnreachableBackendsLegacy) r, - Member (Error InternalError) r, - Member P.TinyLog r - ) => - Local UserId -> - Maybe ConnId -> - NewConv -> - Sem r (ConversationResponse Public.OwnConversation) -createGroupConversationUpToV3 lusr conn newConv = mapError UnreachableBackendsLegacy $ do - dbConv <- ConversationSubsystem.createGroupConversation lusr conn newConv - Created <$> conversationViewV9 lusr dbConv - -createGroupOwnConversation :: - ( Member ConversationSubsystem.ConversationSubsystem r, - Member (Error InternalError) r, - Member FederationSubsystem r, - Member P.TinyLog r - ) => - Local UserId -> - Maybe ConnId -> - NewConv -> - Sem r CreateGroupConversationResponseV9 -createGroupOwnConversation lusr conn newConv = do - let remoteDomains = void <$> snd (partitionQualified lusr $ newConv.newConvQualifiedUsers) - enforceFederationProtocol (baseProtocolToProtocol newConv.newConvProtocol) remoteDomains - checkFederationStatus (RemoteDomains $ Set.fromList remoteDomains) - dbConv <- ConversationSubsystem.createGroupConversation lusr conn newConv - GroupConversationCreatedV9 <$> (CreateGroupOwnConversation <$> conversationViewV9 lusr dbConv <*> pure mempty) - -createGroupConversation :: - ( Member ConversationSubsystem.ConversationSubsystem r, - Member FederationSubsystem r - ) => - Local UserId -> - Maybe ConnId -> - NewConv -> - Sem r CreateGroupConversation -createGroupConversation lusr conn newConv = do - let remoteDomains = void <$> snd (partitionQualified lusr $ newConv.newConvQualifiedUsers) - enforceFederationProtocol (baseProtocolToProtocol newConv.newConvProtocol) remoteDomains - checkFederationStatus (RemoteDomains $ Set.fromList remoteDomains) - dbConv <- ConversationSubsystem.createGroupConversation lusr conn newConv - pure $ - CreateGroupConversation - { conversation = conversationView (qualifyAs lusr ()) (Just lusr) dbConv, - failedToAdd = mempty - } - -createProteusSelfConversation :: - ( Member ConversationSubsystem.ConversationSubsystem r, - Member (Error InternalError) r, - Member P.TinyLog r - ) => - Local UserId -> - Sem r (ConversationResponse Public.OwnConversation) -createProteusSelfConversation lusr = do - (c, created) <- ConversationSubsystem.createProteusSelfConversation lusr - if created - then Created <$> conversationViewV9 lusr c - else Existed <$> conversationViewV9 lusr c - -createOne2OneConversation :: - ( Member ConversationSubsystem.ConversationSubsystem r, - Member (Error InternalError) r, - Member P.TinyLog r - ) => - Local UserId -> - ConnId -> - NewOne2OneConv -> - Sem r (ConversationResponse Public.OwnConversation) -createOne2OneConversation lusr zcon j = do - (c, created) <- ConversationSubsystem.createOne2OneConversation lusr zcon j - if created - then Created <$> conversationViewV9 lusr c - else Existed <$> conversationViewV9 lusr c - ----------------------------------------------------------------------------- --- Helpers - -createConnectConversation :: - ( Member ConversationSubsystem.ConversationSubsystem r, - Member (Error InternalError) r, - Member P.TinyLog r - ) => - Local UserId -> - Maybe ConnId -> - Connect -> - Sem r (ConversationResponse Public.OwnConversation) -createConnectConversation lusr conn j = do - (c, created) <- ConversationSubsystem.createConnectConversation lusr conn j - if created - then Created <$> conversationViewV9 lusr c - else Existed <$> conversationViewV9 lusr c diff --git a/services/galley/src/Galley/API/Federation.hs b/services/galley/src/Galley/API/Federation.hs index 560000626df..1b5ea3a958e 100644 --- a/services/galley/src/Galley/API/Federation.hs +++ b/services/galley/src/Galley/API/Federation.hs @@ -17,7 +17,6 @@ module Galley.API.Federation where -import Galley.API.Federation.Handlers import Galley.App import Polysemy import Servant (ServerT) @@ -26,6 +25,7 @@ import Wire.API.Federation.API import Wire.API.Federation.Endpoint import Wire.API.Federation.Version import Wire.API.Routes.Named +import Wire.ConversationSubsystem type FederationAPI = "federation" :> FedApi 'Galley @@ -33,26 +33,26 @@ type FederationAPI = "federation" :> FedApi 'Galley federationSitemap :: ServerT FederationAPI (Sem GalleyEffects) federationSitemap = - Named @"on-conversation-created" onConversationCreated - :<|> Named @"get-conversations@v1" getConversationsV1 - :<|> Named @"get-conversations" getConversations - :<|> Named @"leave-conversation" leaveConversation - :<|> Named @"send-message" sendMessage - :<|> Named @"update-conversation" updateConversation - :<|> Named @"mls-welcome" mlsSendWelcome - :<|> Named @"send-mls-message" sendMLSMessage - :<|> Named @"send-mls-commit-bundle" sendMLSCommitBundle - :<|> Named @"query-group-info" queryGroupInfo - :<|> Named @"update-typing-indicator" updateTypingIndicator - :<|> Named @"on-typing-indicator-updated" onTypingIndicatorUpdated - :<|> Named @"get-sub-conversation" getSubConversationForRemoteUser - :<|> Named @"delete-sub-conversation" deleteSubConversationForRemoteUser - :<|> Named @"leave-sub-conversation" leaveSubConversation - :<|> Named @"get-one2one-conversation@v1" getOne2OneConversationV1 - :<|> Named @"get-one2one-conversation" getOne2OneConversation - :<|> Named @"on-client-removed" onClientRemoved - :<|> Named @"on-message-sent" onMessageSent - :<|> Named @"on-mls-message-sent" onMLSMessageSent - :<|> Named @(Versioned 'V0 "on-conversation-updated") onConversationUpdatedV0 - :<|> Named @"on-conversation-updated" onConversationUpdated - :<|> Named @"on-user-deleted-conversations" onUserDeleted + Named @"on-conversation-created" federationOnConversationCreated + :<|> Named @"get-conversations@v1" federationGetConversationsV1 + :<|> Named @"get-conversations" federationGetConversations + :<|> Named @"leave-conversation" federationLeaveConversation + :<|> Named @"send-message" federationSendMessage + :<|> Named @"update-conversation" federationUpdateConversation + :<|> Named @"mls-welcome" federationMlsSendWelcome + :<|> Named @"send-mls-message" federationSendMLSMessage + :<|> Named @"send-mls-commit-bundle" federationSendMLSCommitBundle + :<|> Named @"query-group-info" federationQueryGroupInfo + :<|> Named @"update-typing-indicator" federationUpdateTypingIndicator + :<|> Named @"on-typing-indicator-updated" federationOnTypingIndicatorUpdated + :<|> Named @"get-sub-conversation" federationGetSubConversationForRemoteUser + :<|> Named @"delete-sub-conversation" federationDeleteSubConversationForRemoteUser + :<|> Named @"leave-sub-conversation" federationLeaveSubConversation + :<|> Named @"get-one2one-conversation@v1" federationGetOne2OneConversationV1 + :<|> Named @"get-one2one-conversation" federationGetOne2OneConversation + :<|> Named @"on-client-removed" federationOnClientRemoved + :<|> Named @"on-message-sent" federationOnMessageSent + :<|> Named @"on-mls-message-sent" federationOnMLSMessageSent + :<|> Named @(Versioned 'V0 "on-conversation-updated") federationOnConversationUpdatedV0 + :<|> Named @"on-conversation-updated" federationOnConversationUpdated + :<|> Named @"on-user-deleted-conversations" federationOnUserDeleted diff --git a/services/galley/src/Galley/API/Internal.hs b/services/galley/src/Galley/API/Internal.hs index 92e620c45c5..b7e418181ab 100644 --- a/services/galley/src/Galley/API/Internal.hs +++ b/services/galley/src/Galley/API/Internal.hs @@ -37,19 +37,11 @@ import Data.Qualified import Data.Range import Data.Singletons import Data.Time -import Galley.API.Action -import Galley.API.Clients qualified as Clients -import Galley.API.Create qualified as Create import Galley.API.LegalHold (unsetTeamLegalholdWhitelistedH) -import Galley.API.LegalHold.Conflicts -import Galley.API.MLS.Removal import Galley.API.Public.Servant -import Galley.API.Query qualified as Query import Galley.API.Teams import Galley.API.Teams qualified as Teams import Galley.API.Teams.Features -import Galley.API.Teams.Features.Get -import Galley.API.Update qualified as Update import Galley.App import Galley.Monad import Galley.Options hiding (brig) @@ -74,7 +66,6 @@ import Wire.API.Event.LeaveReason import Wire.API.Federation.API import Wire.API.Federation.API.Galley import Wire.API.Federation.Error -import Wire.API.MLS.Keys (MLSKeysByPurpose, MLSPrivateKeys) import Wire.API.Push.V2 qualified as PushV2 import Wire.API.Routes.API import Wire.API.Routes.Internal.Brig.EJPD @@ -83,30 +74,26 @@ import Wire.API.Routes.Internal.Galley.TeamsIntra import Wire.API.Routes.MultiTablePaging (mtpHasMore, mtpPagingState, mtpResults) import Wire.API.Routes.MultiTablePaging qualified as MTP import Wire.API.Team.Feature -import Wire.API.Team.FeatureFlags (FanoutLimit) +import Wire.API.Team.FeatureFlags (FeatureFlags) import Wire.API.User (UserIds (cUsers)) import Wire.API.User.Client import Wire.BackendNotificationQueueAccess import Wire.BrigAPIAccess (BrigAPIAccess) -import Wire.ConversationStore +import Wire.ConversationStore hiding (getConversations) import Wire.ConversationStore qualified as ConversationStore import Wire.ConversationStore.MLS.Types -import Wire.ConversationSubsystem -import Wire.ConversationSubsystem.One2One -import Wire.ConversationSubsystem.Util +import Wire.ConversationSubsystem as Conv +import Wire.ConversationSubsystem.LegalholdConflicts (LegalholdConflicts, LegalholdConflictsOldClients) import Wire.CustomBackendStore -import Wire.ExternalAccess (ExternalAccess) -import Wire.FeaturesConfigSubsystem (FeaturesConfigSubsystem) +import Wire.FeaturesConfigSubsystem (FeaturesConfigSubsystem, getAllTeamFeaturesForServer, getFeatureForTeam) import Wire.FederationSubsystem (getFederationStatus) import Wire.LegalHoldStore as LegalHoldStore import Wire.ListItems import Wire.NotificationSubsystem -import Wire.ProposalStore (ProposalStore) import Wire.Sem.Now (Now) import Wire.Sem.Now qualified as Now import Wire.Sem.Paging import Wire.Sem.Paging.Cassandra -import Wire.Sem.Random (Random) import Wire.ServiceStore import Wire.StoredConversation import Wire.StoredConversation qualified as Data @@ -123,13 +110,13 @@ internalAPI = hoistAPI @InternalAPIBase Imports.id $ mkNamedAPI @"status" (pure ()) <@> mkNamedAPI @"delete-user" rmUser - <@> mkNamedAPI @"connect" Create.createConnectConversation + <@> mkNamedAPI @"connect" createConnectConversation <@> mkNamedAPI @"get-conversation-clients" iGetMLSClientListForConv <@> mkNamedAPI @"guard-legalhold-policy-conflicts" guardLegalholdPolicyConflictsH <@> legalholdWhitelistedTeamsAPI <@> iTeamsAPI <@> miscAPI - <@> mkNamedAPI @"upsert-one2one" iUpsertOne2OneConversation + <@> mkNamedAPI @"upsert-one2one" internalUpsertOne2OneConversation <@> featureAPI <@> federationAPI <@> conversationAPI @@ -145,23 +132,19 @@ getConversationConfigH = input iEJPDAPI :: API IEJPDAPI GalleyEffects iEJPDAPI = mkNamedAPI @"get-conversations-by-user" ejpdGetConvInfo --- | An unpaginated, internal http interface to `Query.conversationIdsPageFrom`. Used for +-- | An unpaginated, internal http interface to `conversationIdsPageFrom`. Used for -- EJPD reports. Called locally with very little data for each conv, so we don't expect -- pagination to ever be needed. ejpdGetConvInfo :: forall r. ( Member ConversationStore r, - Member ConversationSubsystem r, - Member (Error InternalError) r, - Member (Input (Local ())) r, - Member (Input (Maybe (MLSKeysByPurpose MLSPrivateKeys))) r, - Member P.TinyLog r + Member ConversationSubsystem r ) => UserId -> Sem r [EJPDConvInfo] ejpdGetConvInfo uid = do luid <- qualifyLocal uid - firstPage <- Query.conversationIdsPageFrom luid initialPageRequest + firstPage <- conversationIdsPageFrom luid initialPageRequest getPages luid firstPage where initialPageRequest = mkPageRequest (MTP.MultiTablePagingState MTP.PagingLocals Nothing) @@ -184,7 +167,7 @@ ejpdGetConvInfo uid = do renderedPage <- mapMaybe mk <$> ConversationStore.getConversations (fst $ partitionQualified luid convids) if MTP.mtpHasMore page then do - newPage <- Query.conversationIdsPageFrom luid (mkPageRequest . MTP.mtpPagingState $ page) + newPage <- conversationIdsPageFrom luid (mkPageRequest . MTP.mtpPagingState $ page) morePages <- getPages luid newPage pure $ renderedPage <> morePages else pure renderedPage @@ -195,14 +178,14 @@ federationAPI = conversationAPI :: API IConversationAPI GalleyEffects conversationAPI = - mkNamedAPI @"conversation-get-member" Query.internalGetMember - <@> mkNamedAPI @"conversation-accept-v2" Update.acceptConv - <@> mkNamedAPI @"conversation-block" Update.blockConv - <@> mkNamedAPI @"conversation-unblock" Update.unblockConv - <@> mkNamedAPI @"conversation-meta" Query.getConversationMeta - <@> mkNamedAPI @"conversation-mls-one-to-one" Query.getMLSOne2OneConversationInternal - <@> mkNamedAPI @"conversation-mls-one-to-one-established" Query.isMLSOne2OneEstablished - <@> mkNamedAPI @"get-conversation-by-id" Query.getLocalConversationInternal + mkNamedAPI @"conversation-get-member" internalGetMember + <@> mkNamedAPI @"conversation-accept-v2" acceptConv + <@> mkNamedAPI @"conversation-block" blockConv + <@> mkNamedAPI @"conversation-unblock" unblockConv + <@> mkNamedAPI @"conversation-meta" getConversationMeta + <@> mkNamedAPI @"conversation-mls-one-to-one" getMLSOne2OneConversationInternal + <@> mkNamedAPI @"conversation-mls-one-to-one-established" isMLSOne2OneEstablished + <@> mkNamedAPI @"get-conversation-by-id" getLocalConversationInternal <@> mkNamedAPI @"is-conversation-out-of-sync" ConversationStore.isConversationOutOfSync legalholdWhitelistedTeamsAPI :: API ILegalholdWhitelistedTeamsAPI GalleyEffects @@ -244,20 +227,20 @@ iTeamsAPI = mkAPI $ \tid -> hoistAPIHandler Imports.id (base tid) <@> mkNamedAPI @"finalize-delete-team" (\lusr mconn -> TeamSubsystem.internalFinalizeDeleteTeam lusr mconn tid $> NoContent) <@> hoistAPISegment ( mkNamedAPI @"get-search-visibility-internal" (Teams.getSearchVisibilityInternal tid) - <@> mkNamedAPI @"set-search-visibility-internal" (Teams.setSearchVisibilityInternal (featureEnabledForTeam @SearchVisibilityAvailableConfig) tid) + <@> mkNamedAPI @"set-search-visibility-internal" (Teams.setSearchVisibilityInternal (featureEnabledForTeam (Proxy @SearchVisibilityAvailableConfig)) tid) ) miscAPI :: API IMiscAPI GalleyEffects miscAPI = mkNamedAPI @"get-team-members" Teams.getBindingTeamMembers <@> mkNamedAPI @"get-team-id" lookupBindingTeam - <@> mkNamedAPI @"test-get-clients" Clients.getClients + <@> mkNamedAPI @"test-get-clients" Conv.getClients <@> mkNamedAPI @"test-add-client" createClient - <@> mkNamedAPI @"test-delete-client" Clients.rmClient + <@> mkNamedAPI @"test-delete-client" rmClient <@> mkNamedAPI @"add-service" createService <@> mkNamedAPI @"delete-service" deleteService - <@> mkNamedAPI @"i-add-bot" Update.addBot - <@> mkNamedAPI @"delete-bot" Update.rmBot + <@> mkNamedAPI @"i-add-bot" addBot + <@> mkNamedAPI @"delete-bot" rmBot <@> mkNamedAPI @"put-custom-backend" setCustomBackend <@> mkNamedAPI @"delete-custom-backend" deleteCustomBackend @@ -341,7 +324,7 @@ featureAPI = <@> mkNamedAPI @"get-configured-feature-flags" getConfiguredFeatureFlags cellsAPI :: API ICellsAPI GalleyEffects -cellsAPI = mkNamedAPI @"set-cells-state" Update.updateCellsState +cellsAPI = mkNamedAPI @"set-cells-state" updateCellsState getConfiguredFeatureFlags :: forall r. @@ -360,20 +343,12 @@ rmUser :: Member ConversationStore r, Member (Error DynError) r, Member (Error FederationError) r, - Member (Error InternalError) r, - Member ExternalAccess r, Member NotificationSubsystem r, Member ConversationSubsystem r, - Member (Input (Maybe (MLSKeysByPurpose MLSPrivateKeys))) r, Member Now r, Member (ListItems p2 TeamId) r, - Member ProposalStore r, Member P.TinyLog r, - Member Random r, Member TeamStore r, - Member (Input FanoutLimit) r, - Member TeamSubsystem r, - Member (Input ConversationSubsystemConfig) r, Member FeaturesConfigSubsystem r ) => Local UserId -> @@ -383,7 +358,7 @@ rmUser lusr conn = do let nRange1000 = toRange (Proxy @1000) :: Range 1 1000 Int32 tids <- listTeams (tUnqualified lusr) Nothing maxBound leaveTeams tids - allConvIds <- Query.conversationIdsPageFrom lusr (GetPaginatedConversationIds Nothing nRange1000) + allConvIds <- conversationIdsPageFrom lusr (GetPaginatedConversationIds Nothing nRange1000) goConvPages nRange1000 allConvIds deleteClients (tUnqualified lusr) @@ -396,7 +371,7 @@ rmUser lusr conn = do when (mtpHasMore page) $ do let nextState = mtpPagingState page nextQuery = GetPaginatedConversationIds (Just nextState) range - newCids <- Query.conversationIdsPageFrom lusr nextQuery + newCids <- conversationIdsPageFrom lusr nextQuery goConvPages range newCids leaveTeams page = for_ (pageItems page) $ \tid -> do @@ -522,7 +497,7 @@ safeForever funName action = guardLegalholdPolicyConflictsH :: ( Member BrigAPIAccess r, - Member (Input Opts) r, + Member (Input FeatureFlags) r, Member P.TinyLog r, Member (ErrorS 'MissingLegalholdConsent) r, Member (ErrorS 'MissingLegalholdConsentOldClients) r, diff --git a/services/galley/src/Galley/API/LegalHold.hs b/services/galley/src/Galley/API/LegalHold.hs index c3e6abcfbe4..b241eac5613 100644 --- a/services/galley/src/Galley/API/LegalHold.hs +++ b/services/galley/src/Galley/API/LegalHold.hs @@ -21,7 +21,6 @@ module Galley.API.LegalHold removeSettingsInternalPaging, removeSettings, removeSettings', - getUserStatus, grantConsent, requestDevice, approveDevice, @@ -40,10 +39,7 @@ import Data.Misc import Data.Proxy (Proxy (Proxy)) import Data.Qualified import Data.Range (toRange) -import Galley.API.LegalHold.Get import Galley.API.LegalHold.Team -import Galley.API.Query (iterateConversations) -import Galley.API.Update (removeMemberFromLocalConv) import Galley.External.LegalHoldService qualified as LHService import Galley.Types.Error import Imports @@ -54,12 +50,10 @@ import Polysemy.Input import Polysemy.TinyLog qualified as P import System.Logger.Class qualified as Log import Wire.API.Conversation (ConvType (..), ConversationMetadata (..)) -import Wire.API.Conversation.Config (ConversationSubsystemConfig) import Wire.API.Conversation.Protocol import Wire.API.Conversation.Role import Wire.API.Error import Wire.API.Error.Galley -import Wire.API.Federation.Error import Wire.API.Provider.Service import Wire.API.Routes.Internal.Brig.Connection import Wire.API.Routes.Public.Galley.LegalHold @@ -72,21 +66,13 @@ import Wire.API.Team.LegalHold.Internal import Wire.API.Team.Member import Wire.API.User hiding (userId) import Wire.API.User.Client.Prekey -import Wire.BackendNotificationQueueAccess import Wire.BrigAPIAccess -import Wire.ConversationStore (ConversationStore) import Wire.ConversationSubsystem -import Wire.ConversationSubsystem.Util -import Wire.ExternalAccess (ExternalAccess) import Wire.FeaturesConfigSubsystem import Wire.FireAndForget import Wire.LegalHoldStore qualified as LegalHoldData -import Wire.NotificationSubsystem -import Wire.ProposalStore (ProposalStore) -import Wire.Sem.Now (Now) import Wire.Sem.Paging import Wire.Sem.Paging.Cassandra -import Wire.Sem.Random (Random) import Wire.StoredConversation import Wire.StoredConversation qualified as Data import Wire.TeamMemberStore @@ -97,15 +83,14 @@ import Wire.Util createSettings :: forall r. - ( Member (ErrorS 'NotATeamMember) r, - Member (ErrorS OperationDenied) r, - Member (ErrorS 'LegalHoldNotEnabled) r, + ( Member (ErrorS 'LegalHoldNotEnabled) r, Member (ErrorS 'LegalHoldServiceInvalidKey) r, Member (ErrorS 'LegalHoldServiceBadResponse) r, Member LegalHoldData.LegalHoldStore r, Member P.TinyLog r, Member (Input (FeatureDefaults LegalholdConfig)) r, Member TeamSubsystem r, + Member ConversationSubsystem r, Member FeaturesConfigSubsystem r ) => Local UserId -> @@ -153,36 +138,23 @@ getSettings lzusr tid = do removeSettingsInternalPaging :: forall r. - ( Member BackendNotificationQueueAccess r, - Member BrigAPIAccess r, - Member ConversationStore r, - Member (Error AuthenticationError) r, - Member (Error FederationError) r, + ( Member BrigAPIAccess r, Member (Error InternalError) r, Member (ErrorS ('ActionDenied 'RemoveConversationMember)) r, Member (ErrorS 'LegalHoldCouldNotBlockConnections) r, Member (ErrorS 'LegalHoldDisableUnimplemented) r, Member (ErrorS 'LegalHoldNotEnabled) r, Member (ErrorS 'LegalHoldServiceNotRegistered) r, - Member (ErrorS 'NotATeamMember) r, - Member (ErrorS OperationDenied) r, Member (ErrorS 'UserLegalHoldIllegalOperation) r, - Member ExternalAccess r, Member FireAndForget r, - Member NotificationSubsystem r, Member ConversationSubsystem r, - Member (Input (Local ())) r, - Member Now r, Member LegalHoldData.LegalHoldStore r, - Member ProposalStore r, Member P.TinyLog r, - Member Random r, Member (TeamMemberStore InternalPaging) r, Member TeamStore r, Member (Embed IO) r, Member (Input (FeatureDefaults LegalholdConfig)) r, Member TeamSubsystem r, - Member (Input ConversationSubsystemConfig) r, Member FeaturesConfigSubsystem r ) => Local UserId -> @@ -197,35 +169,22 @@ removeSettings :: Bounded (PagingBounds p TeamMember), Member (TeamMemberStore p) r, Member TeamStore r, - Member BackendNotificationQueueAccess r, Member BrigAPIAccess r, - Member ConversationStore r, - Member (Error AuthenticationError) r, - Member (Error FederationError) r, Member (Error InternalError) r, Member (ErrorS ('ActionDenied 'RemoveConversationMember)) r, Member (ErrorS 'LegalHoldCouldNotBlockConnections) r, Member (ErrorS 'LegalHoldDisableUnimplemented) r, Member (ErrorS 'LegalHoldNotEnabled) r, Member (ErrorS 'LegalHoldServiceNotRegistered) r, - Member (ErrorS 'NotATeamMember) r, - Member (ErrorS OperationDenied) r, Member (ErrorS 'UserLegalHoldIllegalOperation) r, - Member ExternalAccess r, Member FireAndForget r, - Member NotificationSubsystem r, Member ConversationSubsystem r, - Member (Input (Local ())) r, - Member Now r, - Member LegalHoldData.LegalHoldStore r, - Member ProposalStore r, Member P.TinyLog r, - Member Random r, Member (Embed IO) r, Member (Input (FeatureDefaults LegalholdConfig)) r, Member TeamSubsystem r, - Member (Input ConversationSubsystemConfig) r, - Member FeaturesConfigSubsystem r + Member FeaturesConfigSubsystem r, + Member LegalHoldData.LegalHoldStore r ) => UserId -> TeamId -> @@ -257,30 +216,19 @@ removeSettings' :: forall p r. ( Paging p, Bounded (PagingBounds p TeamMember), - Member BackendNotificationQueueAccess r, Member BrigAPIAccess r, - Member ConversationStore r, - Member (Error FederationError) r, Member (Error InternalError) r, Member (ErrorS ('ActionDenied 'RemoveConversationMember)) r, Member (ErrorS 'LegalHoldServiceNotRegistered) r, Member (ErrorS 'UserLegalHoldIllegalOperation) r, Member (ErrorS 'LegalHoldCouldNotBlockConnections) r, - Member ExternalAccess r, Member FireAndForget r, - Member NotificationSubsystem r, Member ConversationSubsystem r, - Member Now r, - Member (Input (Local ())) r, Member LegalHoldData.LegalHoldStore r, Member (TeamMemberStore p) r, Member TeamStore r, - Member ProposalStore r, - Member Random r, Member P.TinyLog r, - Member (Embed IO) r, - Member TeamSubsystem r, - Member (Input ConversationSubsystemConfig) r + Member (Embed IO) r ) => TeamId -> Sem r () @@ -308,26 +256,17 @@ removeSettings' tid = -- @withdrawExplicitConsentH@ (lots of corner cases we'd have to implement for that to pan -- out). grantConsent :: - ( Member BackendNotificationQueueAccess r, - Member BrigAPIAccess r, - Member ConversationStore r, - Member (Error FederationError) r, + ( Member BrigAPIAccess r, Member (Error InternalError) r, Member (ErrorS ('ActionDenied 'RemoveConversationMember)) r, Member (ErrorS 'LegalHoldCouldNotBlockConnections) r, Member (ErrorS 'TeamMemberNotFound) r, Member (ErrorS 'UserLegalHoldIllegalOperation) r, - Member ExternalAccess r, - Member NotificationSubsystem r, Member ConversationSubsystem r, - Member Now r, Member LegalHoldData.LegalHoldStore r, - Member ProposalStore r, Member P.TinyLog r, - Member Random r, Member TeamStore r, - Member TeamSubsystem r, - Member (Input ConversationSubsystemConfig) r + Member TeamSubsystem r ) => Local UserId -> TeamId -> @@ -346,10 +285,7 @@ grantConsent lusr tid = do -- | Request to provision a device on the legal hold service for a user requestDevice :: forall r. - ( Member BackendNotificationQueueAccess r, - Member BrigAPIAccess r, - Member ConversationStore r, - Member (Error FederationError) r, + ( Member BrigAPIAccess r, Member (Error InternalError) r, Member (ErrorS ('ActionDenied 'RemoveConversationMember)) r, Member (ErrorS 'LegalHoldCouldNotBlockConnections) r, @@ -357,26 +293,17 @@ requestDevice :: Member (ErrorS 'LegalHoldServiceBadResponse) r, Member (ErrorS 'LegalHoldServiceNotRegistered) r, Member (ErrorS 'MLSLegalholdIncompatible) r, - Member (ErrorS 'NotATeamMember) r, Member (ErrorS 'NoUserLegalHoldConsent) r, - Member (ErrorS OperationDenied) r, Member (ErrorS 'TeamMemberNotFound) r, Member (ErrorS 'UserLegalHoldAlreadyEnabled) r, Member (ErrorS 'UserLegalHoldIllegalOperation) r, - Member ExternalAccess r, - Member NotificationSubsystem r, Member ConversationSubsystem r, - Member (Input (Local ())) r, - Member Now r, Member LegalHoldData.LegalHoldStore r, - Member ProposalStore r, Member P.TinyLog r, - Member Random r, Member TeamStore r, Member (Embed IO) r, Member (Input (FeatureDefaults LegalholdConfig)) r, Member TeamSubsystem r, - Member (Input ConversationSubsystemConfig) r, Member FeaturesConfigSubsystem r ) => Local UserId -> @@ -406,7 +333,7 @@ requestDevice lzusr tid uid = do where disallowIfMLSUser :: Local UserId -> Sem r () disallowIfMLSUser luid = do - void $ iterateConversations luid (toRange (Proxy @500)) $ \convs -> do + iterateConversations luid (toRange (Proxy @500)) $ \convs -> do when (any (\c -> c.metadata.cnvmType /= SelfConv && c.protocol /= ProtocolProteus) convs) $ do throwS @'MLSLegalholdIncompatible @@ -440,11 +367,8 @@ requestDevice lzusr tid uid = do -- since they are replaced if needed when registering new LH devices. approveDevice :: forall r. - ( Member BackendNotificationQueueAccess r, - Member BrigAPIAccess r, - Member ConversationStore r, + ( Member BrigAPIAccess r, Member (Error AuthenticationError) r, - Member (Error FederationError) r, Member (Error InternalError) r, Member (ErrorS 'AccessDenied) r, Member (ErrorS ('ActionDenied 'RemoveConversationMember)) r, @@ -452,24 +376,16 @@ approveDevice :: Member (ErrorS 'LegalHoldNotEnabled) r, Member (ErrorS 'LegalHoldServiceNotRegistered) r, Member (ErrorS 'NoLegalHoldDeviceAllocated) r, - Member (ErrorS 'NotATeamMember) r, Member (ErrorS 'UserLegalHoldAlreadyEnabled) r, Member (ErrorS 'UserLegalHoldIllegalOperation) r, Member (ErrorS 'UserLegalHoldNotPending) r, - Member ExternalAccess r, - Member NotificationSubsystem r, Member ConversationSubsystem r, - Member (Input (Local ())) r, - Member Now r, Member LegalHoldData.LegalHoldStore r, - Member ProposalStore r, Member P.TinyLog r, - Member Random r, Member TeamStore r, Member (Embed IO) r, Member (Input (FeatureDefaults LegalholdConfig)) r, Member TeamSubsystem r, - Member (Input ConversationSubsystemConfig) r, Member FeaturesConfigSubsystem r ) => Local UserId -> @@ -521,31 +437,18 @@ approveDevice lzusr connId tid uid (Public.ApproveLegalHoldForUserRequest mPassw disableForUser :: forall r. - ( Member BackendNotificationQueueAccess r, - Member BrigAPIAccess r, - Member ConversationStore r, - Member (Error AuthenticationError) r, - Member (Error FederationError) r, + ( Member BrigAPIAccess r, Member (Error InternalError) r, Member (ErrorS ('ActionDenied 'RemoveConversationMember)) r, Member (ErrorS 'LegalHoldCouldNotBlockConnections) r, Member (ErrorS 'LegalHoldServiceNotRegistered) r, - Member (ErrorS 'NotATeamMember) r, - Member (ErrorS OperationDenied) r, Member (ErrorS 'UserLegalHoldIllegalOperation) r, - Member ExternalAccess r, - Member NotificationSubsystem r, Member ConversationSubsystem r, - Member (Input (Local ())) r, - Member Now r, Member LegalHoldData.LegalHoldStore r, - Member ProposalStore r, Member P.TinyLog r, - Member Random r, Member TeamStore r, Member (Embed IO) r, - Member TeamSubsystem r, - Member (Input ConversationSubsystemConfig) r + Member TeamSubsystem r ) => Local UserId -> TeamId -> @@ -589,25 +492,15 @@ disableForUser lzusr tid uid (Public.DisableLegalHoldForUserRequest mPassword) = -- enabled, or disabled, make sure the affected connections are screened for policy conflict -- (anybody with no-consent), and put those connections in the appropriate blocked state. changeLegalholdStatusAndHandlePolicyConflicts :: - ( Member BackendNotificationQueueAccess r, - Member BrigAPIAccess r, - Member ConversationStore r, - Member (Error FederationError) r, + ( Member BrigAPIAccess r, Member (Error InternalError) r, Member (ErrorS ('ActionDenied 'RemoveConversationMember)) r, Member (ErrorS 'LegalHoldCouldNotBlockConnections) r, Member (ErrorS 'UserLegalHoldIllegalOperation) r, - Member ExternalAccess r, - Member NotificationSubsystem r, Member ConversationSubsystem r, - Member Now r, Member LegalHoldData.LegalHoldStore r, Member TeamStore r, - Member ProposalStore r, - Member Random r, - Member P.TinyLog r, - Member TeamSubsystem r, - Member (Input ConversationSubsystemConfig) r + Member P.TinyLog r ) => TeamId -> Local UserId -> @@ -655,7 +548,7 @@ blockNonConsentingConnections :: Member TeamStore r, Member P.TinyLog r, Member (ErrorS 'LegalHoldCouldNotBlockConnections) r, - Member TeamSubsystem r + Member ConversationSubsystem r ) => UserId -> Sem r () @@ -708,61 +601,48 @@ unsetTeamLegalholdWhitelistedH tid = do -- contains the hypothetical new LH status of `uid`'s so it can be consulted instead of the -- one from the database. handleGroupConvPolicyConflicts :: - ( Member BackendNotificationQueueAccess r, - Member ConversationStore r, - Member (Error FederationError) r, - Member (Error InternalError) r, + ( Member (Error InternalError) r, Member (ErrorS ('ActionDenied 'RemoveConversationMember)) r, - Member ExternalAccess r, - Member NotificationSubsystem r, - Member ConversationSubsystem r, - Member Now r, - Member ProposalStore r, - Member P.TinyLog r, - Member Random r, - Member TeamStore r, - Member TeamSubsystem r, - Member (Input ConversationSubsystemConfig) r + Member ConversationSubsystem r ) => Local UserId -> UserLegalHoldStatus -> Sem r () handleGroupConvPolicyConflicts luid hypotheticalLHStatus = do - void $ - iterateConversations luid (toRange (Proxy @500)) $ \convs -> do - for_ (filter ((== RegularConv) . Data.convType) convs) $ \conv -> do - let FutureWork _convRemoteMembers' = FutureWork @'LegalholdPlusFederationNotImplemented Data.remoteMembers + iterateConversations luid (toRange (Proxy @500)) $ \convs -> do + for_ (filter ((== RegularConv) . Data.convType) convs) $ \conv -> do + let FutureWork _convRemoteMembers' = FutureWork @'LegalholdPlusFederationNotImplemented Data.remoteMembers - membersAndLHStatus :: [(LocalMember, UserLegalHoldStatus)] <- do - let mems = conv.localMembers - uidsLHStatus <- getLHStatusForUsers ((.id_) <$> mems) - pure $ - zipWith - ( \mem (mid, status) -> - assert (mem.id_ == mid) $ - if mem.id_ == tUnqualified luid - then (mem, hypotheticalLHStatus) - else (mem, status) - ) - mems - uidsLHStatus + membersAndLHStatus :: [(LocalMember, UserLegalHoldStatus)] <- do + let mems = conv.localMembers + uidsLHStatus <- getLHStatusForUsers ((.id_) <$> mems) + pure $ + zipWith + ( \mem (mid, status) -> + assert (mem.id_ == mid) $ + if mem.id_ == tUnqualified luid + then (mem, hypotheticalLHStatus) + else (mem, status) + ) + mems + uidsLHStatus - let lcnv = qualifyAs luid conv.id_ - -- we know that this is a group conversation, so invalid operation - -- and conversation not found errors cannot actually be thrown - mapToRuntimeError @'InvalidOperation - (InternalErrorWithDescription "expected group conversation while handling policy conflicts") - . mapToRuntimeError @'ConvNotFound - (InternalErrorWithDescription "conversation disappeared while iterating on a list of conversations") - . mapErrorS @('ActionDenied 'LeaveConversation) @('ActionDenied 'RemoveConversationMember) - $ if any - ((== ConsentGiven) . consentGiven . snd) - (filter ((== roleNameWireAdmin) . (.convRoleName) . fst) membersAndLHStatus) - then do - for_ (filter ((== ConsentNotGiven) . consentGiven . snd) membersAndLHStatus) $ \(memberNoConsent, _) -> do - let lusr = qualifyAs luid memberNoConsent.id_ - removeMemberFromLocalConv lcnv lusr Nothing (tUntagged lusr) - else do - for_ (filter (userLHEnabled . snd) membersAndLHStatus) $ \(legalholder, _) -> do - let lusr = qualifyAs luid legalholder.id_ - removeMemberFromLocalConv lcnv lusr Nothing (tUntagged lusr) + let lcnv = qualifyAs luid conv.id_ + -- we know that this is a group conversation, so invalid operation + -- and conversation not found errors cannot actually be thrown + mapToRuntimeError @'InvalidOperation + (InternalErrorWithDescription "expected group conversation while handling policy conflicts") + . mapToRuntimeError @'ConvNotFound + (InternalErrorWithDescription "conversation disappeared while iterating on a list of conversations") + . mapErrorS @('ActionDenied 'LeaveConversation) @('ActionDenied 'RemoveConversationMember) + $ if any + ((== ConsentGiven) . consentGiven . snd) + (filter ((== roleNameWireAdmin) . (.convRoleName) . fst) membersAndLHStatus) + then do + for_ (filter ((== ConsentNotGiven) . consentGiven . snd) membersAndLHStatus) $ \(memberNoConsent, _) -> do + let lusr = qualifyAs luid memberNoConsent.id_ + removeMemberFromLocalConv lcnv lusr Nothing (tUntagged lusr) + else do + for_ (filter (userLHEnabled . snd) membersAndLHStatus) $ \(legalholder, _) -> do + let lusr = qualifyAs luid legalholder.id_ + removeMemberFromLocalConv lcnv lusr Nothing (tUntagged lusr) diff --git a/services/galley/src/Galley/API/Public/Bot.hs b/services/galley/src/Galley/API/Public/Bot.hs index ecb4f18eb38..12bed8ed456 100644 --- a/services/galley/src/Galley/API/Public/Bot.hs +++ b/services/galley/src/Galley/API/Public/Bot.hs @@ -18,42 +18,25 @@ module Galley.API.Public.Bot where import Data.Id -import Data.Qualified -import Galley.API.Query qualified as Query -import Galley.API.Teams.Features qualified as Features -import Galley.API.Update import Galley.App import Polysemy -import Polysemy.Input -import Wire.API.Error -import Wire.API.Error.Galley import Wire.API.Event.Team qualified as Public () import Wire.API.Provider.Bot import Wire.API.Routes.API import Wire.API.Routes.Public.Galley.Bot -import Wire.ConversationStore (ConversationStore) -import Wire.FeaturesConfigSubsystem (FeaturesConfigSubsystem) -import Wire.TeamStore (TeamStore) -import Wire.TeamSubsystem (TeamSubsystem) +import Wire.ConversationSubsystem botAPI :: API BotAPI GalleyEffects botAPI = mkNamedAPI @"post-bot-message-unqualified" postBotMessageUnqualified - <@> mkNamedAPI @"get-bot-conversation" getBotConversation + <@> mkNamedAPI @"get-bot-conversation" getBotConversationEndpoint -getBotConversation :: +getBotConversationEndpoint :: forall r. - ( Member ConversationStore r, - Member (Input (Local ())) r, - Member (ErrorS 'AccessDenied) r, - Member (ErrorS 'ConvNotFound) r, - Member TeamStore r, - Member TeamSubsystem r, - Member FeaturesConfigSubsystem r - ) => + (Member ConversationSubsystem r) => BotId -> ConvId -> Sem r BotConvView -getBotConversation bid cnv = do - Features.guardSecondFactorDisabled (botUserId bid) cnv - Query.getBotConversation bid cnv +getBotConversationEndpoint bid cnv = do + guardSecondFactorDisabled (botUserId bid) cnv + getBotConversation bid cnv diff --git a/services/galley/src/Galley/API/Public/Conversation.hs b/services/galley/src/Galley/API/Public/Conversation.hs index cbab0c5663a..a54e22548af 100644 --- a/services/galley/src/Galley/API/Public/Conversation.hs +++ b/services/galley/src/Galley/API/Public/Conversation.hs @@ -17,16 +17,12 @@ module Galley.API.Public.Conversation where -import Galley.API.Create -import Galley.API.MLS.GroupInfo -import Galley.API.MLS.SubConversation -import Galley.API.Query -import Galley.API.Update import Galley.App import Imports import Wire.API.Routes.API import Wire.API.Routes.Public.Galley.Conversation import Wire.ConversationStore.MLS.Types +import Wire.ConversationSubsystem conversationAPI :: API ConversationAPI GalleyEffects conversationAPI = @@ -41,7 +37,7 @@ conversationAPI = <@> mkNamedAPI @"list-conversation-ids-unqualified" conversationIdsPageFromUnqualified <@> mkNamedAPI @"list-conversation-ids-v2" (conversationIdsPageFromV2 DoNotListGlobalSelf) <@> mkNamedAPI @"list-conversation-ids" conversationIdsPageFrom - <@> mkNamedAPI @"get-conversations" getConversations + <@> mkNamedAPI @"get-conversations" getPaginatedConversations <@> mkNamedAPI @"list-conversations@v1" listConversations <@> mkNamedAPI @"list-conversations@v2" listConversations <@> mkNamedAPI @"list-conversations@v5" listConversations diff --git a/services/galley/src/Galley/API/Public/Feature.hs b/services/galley/src/Galley/API/Public/Feature.hs index 0c55a19471b..0443b851295 100644 --- a/services/galley/src/Galley/API/Public/Feature.hs +++ b/services/galley/src/Galley/API/Public/Feature.hs @@ -20,16 +20,17 @@ module Galley.API.Public.Feature where +import Data.Proxy (Proxy (..)) import Galley.API.Teams import Galley.API.Teams.Features -import Galley.API.Teams.Features.Get import Galley.App import Imports import Wire.API.Routes.API import Wire.API.Routes.Public.Galley.Feature import Wire.API.Routes.Version import Wire.API.Team.Feature -import Wire.FeaturesConfigSubsystem (getAllTeamFeaturesForTeamMember) +import Wire.ConversationSubsystem +import Wire.FeaturesConfigSubsystem (getAllTeamFeaturesForTeamMember, getFeature) featureAPIGetPut :: forall cfg r. (_) => API (FeatureAPIGetPut cfg) r featureAPIGetPut = @@ -42,7 +43,7 @@ featureAPI = <@> featureAPIGetPut <@> featureAPIGetPut <@> mkNamedAPI @"get-search-visibility" getSearchVisibility - <@> mkNamedAPI @"set-search-visibility" (setSearchVisibility (featureEnabledForTeam @SearchVisibilityAvailableConfig)) + <@> mkNamedAPI @"set-search-visibility" (setSearchVisibility (featureEnabledForTeam (Proxy @SearchVisibilityAvailableConfig))) <@> mkNamedAPI @'("get", RequireExternalEmailVerificationConfig) getFeature <@> mkNamedAPI @'("get", DigitalSignaturesConfig) getFeature <@> featureAPIGetPut diff --git a/services/galley/src/Galley/API/Public/LegalHold.hs b/services/galley/src/Galley/API/Public/LegalHold.hs index 04afca327fb..ed0afa99c31 100644 --- a/services/galley/src/Galley/API/Public/LegalHold.hs +++ b/services/galley/src/Galley/API/Public/LegalHold.hs @@ -21,6 +21,7 @@ import Galley.API.LegalHold import Galley.App import Wire.API.Routes.API import Wire.API.Routes.Public.Galley.LegalHold +import Wire.ConversationSubsystem legalHoldAPI :: API LegalHoldAPI GalleyEffects legalHoldAPI = diff --git a/services/galley/src/Galley/API/Public/MLS.hs b/services/galley/src/Galley/API/Public/MLS.hs index 687d9e48276..dafde053b89 100644 --- a/services/galley/src/Galley/API/Public/MLS.hs +++ b/services/galley/src/Galley/API/Public/MLS.hs @@ -17,12 +17,11 @@ module Galley.API.Public.MLS where -import Galley.API.MLS -import Galley.API.MLS.Reset import Galley.App import Imports import Wire.API.Routes.API import Wire.API.Routes.Public.Galley.MLS +import Wire.ConversationSubsystem mlsAPI :: API MLSAPI GalleyEffects mlsAPI = diff --git a/services/galley/src/Galley/API/Public/Messaging.hs b/services/galley/src/Galley/API/Public/Messaging.hs index 806484ae908..e96a78dd19c 100644 --- a/services/galley/src/Galley/API/Public/Messaging.hs +++ b/services/galley/src/Galley/API/Public/Messaging.hs @@ -17,10 +17,10 @@ module Galley.API.Public.Messaging where -import Galley.API.Update import Galley.App import Wire.API.Routes.API import Wire.API.Routes.Public.Galley.Messaging +import Wire.ConversationSubsystem messagingAPI :: API MessagingAPI GalleyEffects messagingAPI = diff --git a/services/galley/src/Galley/API/Public/Team.hs b/services/galley/src/Galley/API/Public/Team.hs index 7b8575c0e13..db27f6a176a 100644 --- a/services/galley/src/Galley/API/Public/Team.hs +++ b/services/galley/src/Galley/API/Public/Team.hs @@ -17,11 +17,11 @@ module Galley.API.Public.Team where -import Galley.API.Query import Galley.API.Teams import Galley.App import Wire.API.Routes.API import Wire.API.Routes.Public.Galley.Team +import Wire.ConversationSubsystem teamAPI :: API TeamAPI GalleyEffects teamAPI = diff --git a/services/galley/src/Galley/API/Teams.hs b/services/galley/src/Galley/API/Teams.hs index 844509e13e2..051104b8a90 100644 --- a/services/galley/src/Galley/API/Teams.hs +++ b/services/galley/src/Galley/API/Teams.hs @@ -73,13 +73,9 @@ import Data.Proxy import Data.Qualified import Data.Range as Range import Data.Set qualified as Set -import Data.Singletons import Data.Time.Clock (UTCTime) -import Galley.API.Action import Galley.API.LegalHold.Team -import Galley.API.Teams.Features.Get import Galley.API.Teams.Notifications qualified as APITeamQueue -import Galley.API.Update qualified as API import Galley.App import Galley.Effects.Queue qualified as E import Galley.Options @@ -92,13 +88,13 @@ import Polysemy.TinyLog qualified as P import System.Logger qualified as Log import Wire.API.Conversation (ConvType (..), ConversationRemoveMembers (..)) import Wire.API.Conversation qualified +import Wire.API.Conversation.Action (SConversationActionTag (SConversationRemoveMembersTag)) import Wire.API.Conversation.Role (wireConvRoles) import Wire.API.Conversation.Role qualified as Public import Wire.API.Error import Wire.API.Error.Galley import Wire.API.Event.LeaveReason import Wire.API.Event.Team -import Wire.API.Federation.Error import Wire.API.Push.V2 (RecipientClients (RecipientClientsAll)) import Wire.API.Routes.Internal.Galley.TeamsIntra import Wire.API.Routes.MultiTablePaging (MultiTablePage (..), MultiTablePagingState (..)) @@ -122,17 +118,14 @@ import Wire.API.Team.Size import Wire.API.User qualified as U import Wire.BrigAPIAccess qualified as Brig import Wire.BrigAPIAccess qualified as E -import Wire.CodeStore import Wire.ConversationStore (ConversationStore) import Wire.ConversationStore qualified as E import Wire.ConversationSubsystem -import Wire.ConversationSubsystem.Util import Wire.FeaturesConfigSubsystem import Wire.LegalHoldStore (LegalHoldStore) import Wire.ListItems import Wire.ListItems qualified as E import Wire.NotificationSubsystem -import Wire.ProposalStore (ProposalStore) import Wire.Sem.Now import Wire.Sem.Now qualified as Now import Wire.Sem.Paging.Cassandra @@ -151,7 +144,11 @@ import Wire.Util getTeamH :: forall r. - (Member (ErrorS 'TeamNotFound) r, Member (E.Queue DeleteItem) r, Member TeamStore r, Member TeamSubsystem r) => + ( Member (ErrorS 'TeamNotFound) r, + Member (E.Queue DeleteItem) r, + Member TeamStore r, + Member TeamSubsystem r + ) => UserId -> TeamId -> Sem r Public.Team @@ -261,11 +258,11 @@ createBindingTeam tid zusr body = do updateTeamStatus :: ( Member E.BrigAPIAccess r, - Member (ErrorS 'InvalidTeamStatusUpdate) r, Member (ErrorS 'TeamNotFound) r, Member Now r, Member TeamStore r, - Member TeamJournal r + Member TeamJournal r, + Member (ErrorS InvalidTeamStatusUpdate) r ) => TeamId -> TeamStatusUpdate -> @@ -300,8 +297,7 @@ updateTeamStatus tid (TeamStatusUpdate newStatus cur) = do (_, _) -> throwS @'InvalidTeamStatusUpdate updateTeamH :: - ( Member (ErrorS 'NotATeamMember) r, - Member (ErrorS ('MissingPermission ('Just 'SetTeamData))) r, + ( Member ConversationSubsystem r, Member NotificationSubsystem r, Member Now r, Member TeamStore r, @@ -332,11 +328,8 @@ updateTeamH zusr zcon tid updateData = do deleteTeam :: forall r. - ( Member E.BrigAPIAccess r, - Member (Error AuthenticationError) r, + ( Member ConversationSubsystem r, Member (ErrorS 'DeleteQueueFull) r, - Member (ErrorS 'NotATeamMember) r, - Member (ErrorS OperationDenied) r, Member (ErrorS 'TeamNotFound) r, Member (E.Queue DeleteItem) r, Member TeamStore r, @@ -514,12 +507,10 @@ uncheckedGetTeamMember tid uid = addTeamMember :: forall r. ( Member E.BrigAPIAccess r, + Member ConversationSubsystem r, Member NotificationSubsystem r, Member (ErrorS 'InvalidPermissions) r, Member (ErrorS 'NoAddToBinding) r, - Member (ErrorS 'NotATeamMember) r, - Member (ErrorS 'NotConnected) r, - Member (ErrorS OperationDenied) r, Member (ErrorS 'TeamNotFound) r, Member (ErrorS 'TooManyTeamMembers) r, Member (ErrorS 'TooManyTeamAdmins) r, @@ -649,13 +640,12 @@ uncheckedUpdateTeamMember mlzusr mZcon tid newMem = do updateTeamMember :: forall r. ( Member E.BrigAPIAccess r, + Member ConversationSubsystem r, Member (ErrorS 'AccessDenied) r, Member (ErrorS 'InvalidPermissions) r, Member (ErrorS 'TeamNotFound) r, Member (ErrorS 'TeamMemberNotFound) r, Member (ErrorS 'TooManyTeamAdmins) r, - Member (ErrorS 'NotATeamMember) r, - Member (ErrorS OperationDenied) r, Member NotificationSubsystem r, Member Now r, Member P.TinyLog r, @@ -704,22 +694,19 @@ updateTeamMember lzusr zcon tid newMem = do deleteTeamMember :: ( Member E.BrigAPIAccess r, Member ConversationStore r, - Member (Error AuthenticationError) r, Member (Error InvalidInput) r, Member (ErrorS 'AccessDenied) r, Member (ErrorS 'TeamMemberNotFound) r, Member (ErrorS 'TeamNotFound) r, Member (ErrorS 'NotATeamMember) r, - Member (ErrorS OperationDenied) r, - Member Now r, - Member NotificationSubsystem r, Member ConversationSubsystem r, + Member TeamSubsystem r, + Member NotificationSubsystem r, Member FeaturesConfigSubsystem r, Member TeamStore r, Member P.TinyLog r, - Member (Input FanoutLimit) r, Member TeamJournal r, - Member TeamSubsystem r + Member Now r ) => Local UserId -> ConnId -> @@ -732,22 +719,19 @@ deleteTeamMember lusr zcon tid remove body = deleteTeamMember' lusr zcon tid rem deleteNonBindingTeamMember :: ( Member E.BrigAPIAccess r, Member ConversationStore r, - Member (Error AuthenticationError) r, Member (Error InvalidInput) r, Member (ErrorS 'AccessDenied) r, Member (ErrorS 'TeamMemberNotFound) r, Member (ErrorS 'TeamNotFound) r, Member (ErrorS 'NotATeamMember) r, - Member (ErrorS OperationDenied) r, - Member Now r, - Member NotificationSubsystem r, Member ConversationSubsystem r, + Member TeamSubsystem r, + Member NotificationSubsystem r, Member FeaturesConfigSubsystem r, Member TeamStore r, Member P.TinyLog r, - Member (Input FanoutLimit) r, Member TeamJournal r, - Member TeamSubsystem r + Member Now r ) => Local UserId -> ConnId -> @@ -760,22 +744,19 @@ deleteNonBindingTeamMember lusr zcon tid remove = deleteTeamMember' lusr zcon ti deleteTeamMember' :: ( Member E.BrigAPIAccess r, Member ConversationStore r, - Member (Error AuthenticationError) r, Member (Error InvalidInput) r, Member (ErrorS 'AccessDenied) r, Member (ErrorS 'TeamMemberNotFound) r, Member (ErrorS 'TeamNotFound) r, Member (ErrorS 'NotATeamMember) r, - Member (ErrorS OperationDenied) r, - Member Now r, - Member NotificationSubsystem r, Member ConversationSubsystem r, + Member NotificationSubsystem r, Member FeaturesConfigSubsystem r, Member TeamStore r, Member P.TinyLog r, - Member (Input FanoutLimit) r, Member TeamJournal r, - Member TeamSubsystem r + Member TeamSubsystem r, + Member Now r ) => Local UserId -> ConnId -> @@ -914,7 +895,7 @@ removeFromConvsAndPushConvLeaveEvent lusr zcon tid remove = do (Set.fromList bots) void $ sendConversationActionNotifications - (sing @'ConversationRemoveMembersTag) + SConversationRemoveMembersTag (tUntagged lusr) True zcon @@ -964,17 +945,7 @@ getTeamConversation zusr tid cid = do pure $ newTeamConversation teamConv deleteTeamConversation :: - ( Member CodeStore r, - Member ConversationStore r, - Member (Error FederationError) r, - Member (ErrorS 'ConvNotFound) r, - Member (ErrorS 'InvalidOperation) r, - Member (ErrorS 'NotATeamMember) r, - Member (ErrorS ('ActionDenied 'Public.DeleteConversation)) r, - Member ProposalStore r, - Member ConversationSubsystem r, - Member TeamSubsystem r - ) => + (Member ConversationSubsystem r) => Local UserId -> ConnId -> TeamId -> @@ -982,12 +953,11 @@ deleteTeamConversation :: Sem r () deleteTeamConversation lusr zcon _tid cid = do let lconv = qualifyAs lusr cid - void $ API.deleteLocalConversation lusr zcon lconv + void $ deleteLocalConversation lusr zcon lconv getSearchVisibility :: - ( Member (ErrorS 'NotATeamMember) r, - Member (ErrorS OperationDenied) r, - Member TeamStore r, + ( Member TeamStore r, + Member ConversationSubsystem r, Member TeamSubsystem r ) => Local UserId -> @@ -1000,10 +970,9 @@ getSearchVisibility luid tid = do setSearchVisibility :: forall r. - ( Member (ErrorS 'NotATeamMember) r, - Member (ErrorS OperationDenied) r, + ( Member TeamStore r, Member (ErrorS 'TeamSearchVisibilityNotEnabled) r, - Member TeamStore r, + Member ConversationSubsystem r, Member TeamSubsystem r ) => (TeamId -> Sem r Bool) -> @@ -1174,8 +1143,7 @@ getBindingTeamMembers :: ( Member (ErrorS 'TeamNotFound) r, Member (ErrorS 'NonBindingTeam) r, Member TeamStore r, - Member (Input FanoutLimit) r, - Member TeamSubsystem r + Member ConversationSubsystem r ) => UserId -> Sem r TeamMemberList @@ -1240,7 +1208,7 @@ userIsTeamOwner :: ( Member (ErrorS 'TeamMemberNotFound) r, Member (ErrorS 'AccessDenied) r, Member (ErrorS 'NotATeamMember) r, - Member (Input (Local ())) r, + Member ConversationSubsystem r, Member TeamSubsystem r ) => TeamId -> @@ -1273,8 +1241,6 @@ checkAdminLimit adminCount = updateTeamCollaborator :: forall r. ( Member ConversationStore r, - Member (ErrorS OperationDenied) r, - Member (ErrorS NotATeamMember) r, Member P.TinyLog r, Member TeamCollaboratorsSubsystem r, Member ConversationSubsystem r, @@ -1299,8 +1265,6 @@ updateTeamCollaborator lusr tid rusr perms = do removeTeamCollaborator :: forall r. ( Member ConversationStore r, - Member (ErrorS OperationDenied) r, - Member (ErrorS NotATeamMember) r, Member NotificationSubsystem r, Member ConversationSubsystem r, Member Now r, @@ -1308,7 +1272,6 @@ removeTeamCollaborator :: Member FeaturesConfigSubsystem r, Member TeamStore r, Member TeamCollaboratorsSubsystem r, - Member (Input FanoutLimit) r, Member TeamSubsystem r ) => Local UserId -> diff --git a/services/galley/src/Galley/API/Teams/Features.hs b/services/galley/src/Galley/API/Teams/Features.hs index 0e702ace164..f37af6d36ec 100644 --- a/services/galley/src/Galley/API/Teams/Features.hs +++ b/services/galley/src/Galley/API/Teams/Features.hs @@ -22,13 +22,9 @@ module Galley.API.Teams.Features ( setFeature, setFeatureInternal, patchFeatureInternal, - getAllTeamFeaturesForTeam, - getAllTeamFeaturesForUser, updateLockStatus, GetFeatureConfig (..), SetFeatureConfig (..), - guardSecondFactorDisabled, - featureEnabledForTeam, guardMlsE2EIdConfig, ) where @@ -43,7 +39,6 @@ import Data.Kind import Data.Qualified (Local) import Galley.API.LegalHold qualified as LegalHold import Galley.API.LegalHold.Team qualified as LegalHold -import Galley.API.Teams.Features.Get import Galley.App import Galley.Options import Galley.Types.Error (InternalError) @@ -68,10 +63,9 @@ import Wire.BrigAPIAccess (BrigAPIAccess, updateSearchVisibilityInbound) import Wire.CodeStore import Wire.ConversationStore (ConversationStore, MLSCommitLockStore) import Wire.ConversationSubsystem -import Wire.ConversationSubsystem.Util (assertTeamExists, getTeamMembersForFanout, permissionCheck) import Wire.ExternalAccess (ExternalAccess) -import Wire.FeaturesConfigSubsystem (FeaturesConfigSubsystem, getDbFeatureRawInternal) -import Wire.FeaturesConfigSubsystem.Types (GetFeatureConfigEffects) +import Wire.FeaturesConfigSubsystem (FeaturesConfigSubsystem, getDbFeatureRawInternal, getFeatureForTeam) +import Wire.FeaturesConfigSubsystem.Types import Wire.FeaturesConfigSubsystem.Utils (resolveServerFeature) import Wire.FederationAPIAccess (FederationAPIAccess) import Wire.FederationSubsystem (FederationSubsystem) @@ -98,13 +92,10 @@ patchFeatureInternal :: ( SetFeatureConfig cfg, ComputeFeatureConstraints cfg r, SetFeatureForTeamConstraints cfg r, - Member (ErrorS 'TeamNotFound) r, - Member TeamStore r, Member TeamFeatureStore r, Member P.TinyLog r, + Member ConversationSubsystem r, Member NotificationSubsystem r, - Member (Input FanoutLimit) r, - Member TeamSubsystem r, GetFeatureConfigEffects r ) => TeamId -> @@ -135,13 +126,11 @@ setFeature :: ( SetFeatureConfig cfg, ComputeFeatureConstraints cfg r, SetFeatureForTeamConstraints cfg r, - Member (ErrorS 'NotATeamMember) r, - Member (ErrorS OperationDenied) r, Member (Error TeamFeatureError) r, Member TeamFeatureStore r, Member P.TinyLog r, + Member ConversationSubsystem r, Member NotificationSubsystem r, - Member (Input FanoutLimit) r, Member TeamSubsystem r ) => UserId -> @@ -158,14 +147,11 @@ setFeatureInternal :: ( SetFeatureConfig cfg, ComputeFeatureConstraints cfg r, SetFeatureForTeamConstraints cfg r, - Member (ErrorS 'TeamNotFound) r, Member (Error TeamFeatureError) r, - Member TeamStore r, Member TeamFeatureStore r, Member P.TinyLog r, - Member NotificationSubsystem r, - Member (Input FanoutLimit) r, - Member TeamSubsystem r + Member ConversationSubsystem r, + Member NotificationSubsystem r ) => TeamId -> Feature cfg -> @@ -183,8 +169,7 @@ setFeatureUnchecked :: Member TeamFeatureStore r, Member (P.Logger (Log.Msg -> Log.Msg)) r, Member NotificationSubsystem r, - Member (Input FanoutLimit) r, - Member TeamSubsystem r + Member ConversationSubsystem r ) => TeamId -> Feature cfg -> @@ -198,8 +183,7 @@ updateLockStatus :: forall cfg r. ( IsFeatureConfig cfg, Member TeamFeatureStore r, - Member TeamStore r, - Member (ErrorS 'TeamNotFound) r + Member ConversationSubsystem r ) => TeamId -> LockStatus -> @@ -226,9 +210,8 @@ pushFeatureEvent :: forall cfg r. ( IsFeatureConfig cfg, Member NotificationSubsystem r, - Member P.TinyLog r, - Member (Input FanoutLimit) r, - Member TeamSubsystem r + Member ConversationSubsystem r, + Member P.TinyLog r ) => TeamId -> Event -> @@ -262,10 +245,9 @@ setFeatureForTeam :: SetFeatureForTeamConstraints cfg r, ComputeFeatureConstraints cfg r, Member P.TinyLog r, + Member ConversationSubsystem r, Member NotificationSubsystem r, - Member TeamFeatureStore r, - Member (Input FanoutLimit) r, - Member TeamSubsystem r + Member TeamFeatureStore r ) => TeamId -> LockableFeature cfg -> diff --git a/services/galley/src/Galley/App.hs b/services/galley/src/Galley/App.hs index bfa6bbcf79f..e0d3ae7876d 100644 --- a/services/galley/src/Galley/App.hs +++ b/services/galley/src/Galley/App.hs @@ -52,7 +52,6 @@ import Data.Misc import Data.Qualified import Data.Range import Data.Text qualified as Text -import Galley.API.MLS.GroupInfoCheck (GroupInfoCheckEnabled (GroupInfoCheckEnabled)) import Galley.Effects.Queue qualified as GE import Galley.Env import Galley.External.LegalHoldService.Internal qualified as LHInternal @@ -82,6 +81,7 @@ import Polysemy.Resource import Polysemy.TinyLog (TinyLog, logErrors) import Polysemy.TinyLog qualified as P import Servant qualified +import Servant.Server (Tagged (unTagged)) import Ssl.Util import System.Logger qualified as Log import System.Logger.Class (Logger) @@ -89,11 +89,13 @@ import System.Logger.Extended qualified as Logger import UnliftIO.Exception qualified as UnliftIO import Wire.API.Conversation.Config (ConversationSubsystemConfig (..)) import Wire.API.Conversation.Protocol +import Wire.API.Conversation.Role qualified as Role import Wire.API.Error -import Wire.API.Error.Galley (GalleyError (..), NonFederatingBackends, OperationDenied, UnreachableBackends) +import Wire.API.Error.Galley (AuthenticationError, GalleyError (..), GroupInfoDiagnostics, MLSOutOfSyncError, MLSProposalFailure, MLSProtocolError, NonFederatingBackends, OperationDenied, UnreachableBackends, UnreachableBackendsLegacy) import Wire.API.Federation.Client import Wire.API.Federation.Error import Wire.API.MLS.Keys (MLSKeysByPurpose, MLSPrivateKeys) +import Wire.API.Routes.API (ServerEffect (interpretServerEffect)) import Wire.API.Team.Collaborator import Wire.API.Team.Feature import Wire.API.Team.FeatureFlags @@ -109,8 +111,8 @@ import Wire.CodeStore.Postgres import Wire.ConversationStore (ConversationStore, MLSCommitLockStore) import Wire.ConversationStore.Cassandra import Wire.ConversationStore.Postgres -import Wire.ConversationSubsystem (ConversationSubsystem) -import Wire.ConversationSubsystem.Interpreter (interpretConversationSubsystem) +import Wire.ConversationSubsystem +import Wire.ConversationSubsystem.Interpreter (GroupInfoCheckEnabled (..), GuestLinkTTLSeconds, IntraListing (IntraListing), interpretConversationSubsystem) import Wire.CustomBackendStore import Wire.CustomBackendStore.Cassandra import Wire.Error @@ -233,6 +235,8 @@ type GalleyEffects = Input FanoutLimit, Input (FeatureDefaults LegalholdConfig), Input (Local ()), + Input IntraListing, + Input (Maybe GuestLinkTTLSeconds), Input (Maybe (MLSKeysByPurpose MLSPrivateKeys)), Input (Maybe GroupInfoCheckEnabled), Input Opts, @@ -240,8 +244,57 @@ type GalleyEffects = Now, GE.Queue DeleteItem, Error Meeting.MeetingError, + Error AuthenticationError, + Error MLSProtocolError, + Error MLSProposalFailure, Error DynError, Error RateLimitExceeded, + Error UnreachableBackendsLegacy, + Error GroupInfoDiagnostics, + Error MLSOutOfSyncError, + ErrorS 'MLSMigrationCriteriaNotSatisfied, + ErrorS 'ConvInvalidProtocolTransition, + ErrorS 'InvalidTargetAccess, + ErrorS 'MLSReadReceiptsNotAllowed, + ErrorS 'InvalidTarget, + ErrorS 'CreateConversationCodeConflict, + ErrorS 'TooManyMembers, + ErrorS 'MLSFederatedOne2OneNotSupported, + ErrorS 'GuestLinksDisabled, + ErrorS 'InvalidConversationPassword, + ErrorS 'CodeNotFound, + ErrorS 'MLSMissingGroupInfo, + ErrorS 'AccessDenied, + ErrorS 'TeamMemberNotFound, + ErrorS 'MLSSubConvUnsupportedConvType, + ErrorS 'MLSFederatedResetNotSupported, + ErrorS 'BroadcastLimitExceeded, + ErrorS 'MLSGroupConversationMismatch, + ErrorS 'ConvMemberNotFound, + ErrorS 'GroupIdVersionNotSupported, + ErrorS 'MLSUnsupportedProposal, + ErrorS 'MLSInvalidLeafNodeIndex, + ErrorS 'MLSClientMismatch, + ErrorS 'MLSInvalidLeafNodeSignature, + ErrorS 'MLSSubConvClientNotInParent, + ErrorS 'MLSClientSenderUserMismatch, + ErrorS 'MLSSelfRemovalNotAllowed, + ErrorS 'MLSCommitMissingReferences, + ErrorS 'MLSProposalNotFound, + ErrorS 'MLSStaleMessage, + ErrorS 'MLSUnsupportedMessage, + ErrorS 'MLSIdentityMismatch, + ErrorS MLSLegalholdIncompatible, + ErrorS ('ActionDenied Role.ModifyAddPermission), + ErrorS ('ActionDenied Role.ModifyConversationAccess), + ErrorS ('ActionDenied Role.ModifyConversationReceiptMode), + ErrorS ('ActionDenied Role.ModifyConversationMessageTimer), + ErrorS ('ActionDenied Role.ModifyConversationName), + ErrorS ('ActionDenied Role.ModifyOtherConversationMember), + ErrorS ('ActionDenied Role.AddConversationMember), + ErrorS ('ActionDenied Role.DeleteConversation), + ErrorS ('ActionDenied Role.RemoveConversationMember), + ErrorS ('ActionDenied Role.LeaveConversation), ErrorS OperationDenied, ErrorS 'HistoryNotSupported, ErrorS 'NotATeamMember, @@ -292,7 +345,7 @@ type GalleyEffects = validateOptions :: Opts -> IO (Either HttpsUrl (Map Text HttpsUrl)) validateOptions o = do let settings' = view settings o - optFanoutLimit = fromIntegral . fromRange $ currentFanoutLimit o + optFanoutLimit = fromIntegral . fromRange $ currentFanoutLimit settings'._maxTeamSize settings'._maxFanoutSize when (settings'._maxConvSize > fromIntegral optFanoutLimit) $ error "setMaxConvSize cannot be > setTruncationLimit" when (settings' ^. maxTeamSize < optFanoutLimit) $ @@ -502,8 +555,57 @@ evalGalley e = . mapError toResponse -- ErrorS 'NotATeamMember . mapError toResponse -- ErrorS 'HistoryNotSupported . mapError toResponse -- ErrorS OperationDenied + . mapError toResponse -- ErrorS ('ActionDenied Role.LeaveConversation) + . mapError toResponse -- ErrorS ('ActionDenied Role.RemoveConversationMember) + . mapError toResponse -- ErrorS ('ActionDenied Role.DeleteConversation) + . mapError toResponse -- ErrorS ('ActionDenied Role.AddConversationMember) + . mapError toResponse -- ErrorS ('ActionDenied Role.ModifyOtherConversationMember) + . mapError toResponse -- ErrorS ('ActionDenied Role.ModifyConversationName) + . mapError toResponse -- ErrorS ('ActionDenied Role.ModifyConversationMessageTimer) + . mapError toResponse -- ErrorS ('ActionDenied Role.ModifyConversationReceiptMode) + . mapError toResponse -- ErrorS ('ActionDenied Role.ModifyConversationAccess) + . mapError toResponse -- ErrorS ('ActionDenied Role.ModifyAddPermission) + . mapError toResponse -- ErrorS MLSLegalholdIncompatible, + . mapError toResponse -- ErrorS 'MLSIdentityMismatch, + . mapError toResponse -- ErrorS 'MLSUnsupportedMessage, + . mapError toResponse -- ErrorS 'MLSStaleMessage, + . mapError toResponse -- ErrorS 'MLSProposalNotFound, + . mapError toResponse -- ErrorS 'MLSCommitMissingReferences, + . mapError toResponse -- ErrorS 'MLSSelfRemovalNotAllowed, + . mapError toResponse -- ErrorS 'MLSClientSenderUserMismatch, + . mapError toResponse -- ErrorS 'MLSSubConvClientNotInParent, + . mapError toResponse -- ErrorS 'MLSInvalidLeafNodeSignature, + . mapError toResponse -- ErrorS 'MLSClientMismatch, + . mapError toResponse -- ErrorS 'MLSInvalidLeafNodeIndex, + . mapError toResponse -- ErrorS 'MLSUnsupportedProposal, + . mapError toResponse -- ErrorS 'GroupIdVersionNotSupported, + . mapError toResponse -- ErrorS 'ConvMemberNotFound, + . mapError toResponse -- ErrorS 'MLSGroupConversationMismatch, + . mapError toResponse -- ErrorS 'BroadcastLimitExceeded, + . mapError toResponse -- ErrorS 'MLSFederatedResetNotSupported, + . mapError toResponse -- ErrorS 'MLSSubConvUnsupportedConvType, + . mapError toResponse -- ErrorS 'TeamMemberNotFound, + . mapError toResponse -- ErrorS 'AccessDenied, + . mapError toResponse -- ErrorS 'MLSMissingGroupInfo, + . mapError toResponse -- ErrorS 'CodeNotFound, + . mapError toResponse -- ErrorS 'InvalidConversationPassword, + . mapError toResponse -- ErrorS 'GuestLinksDisabled, + . mapError toResponse -- ErrorS 'MLSFederatedOne2OneNotSupported, + . mapError toResponse -- ErrorS 'TooManyMembers, + . mapError toResponse -- ErrorS 'CreateConversationCodeConflict, + . mapError toResponse -- ErrorS 'InvalidTarget, + . mapError toResponse -- ErrorS 'MLSReadReceiptsNotAllowed, + . mapError toResponse -- ErrorS 'InvalidTargetAccess, + . mapError toResponse -- ErrorS 'ConvInvalidProtocolTransition, + . mapError toResponse -- ErrorS 'MLSMigrationCriteriaNotSatisfied, + . mapError toResponse -- Error MLSOutOfSyncError, + . mapError toResponse -- Error GroupInfoDiagnostics, + . mapError toResponse -- Error UnreachableBackendsLegacy, . mapError rateLimitExceededToHttpError . mapError toResponse -- DynError + . interpretServerEffect -- Error MLSProposalFailure, + . mapError (\msg -> (dynError @(MapError 'MLSProtocolErrorTag)) {eMessage = unTagged msg}) -- Error MLSProtocolError, + . interpretServerEffect -- Error AuthenticationError . mapError meetingError . interpretQueue (e ^. deleteQueue) . nowToIO @@ -511,9 +613,11 @@ evalGalley e = . runInputConst (e ^. options) . runInputConst (GroupInfoCheckEnabled <$> e._options._settings._checkGroupInfo) . runInputConst e._mlsKeys + . runInputConst e._options._settings._guestLinkTTLSeconds + . runInputConst (IntraListing e._options._settings._intraListing) . runInputConst localUnit . interpretTeamFeatureSpecialContext e - . runInputConst (currentFanoutLimit (e ^. options)) + . runInputConst (currentFanoutLimitOpts (e ^. options)) . runInputSem (inputs @Opts $ view (O.settings . O.featureFlags)) . runInputSem (inputs @Opts $ ExposeInvitationURLsAllowlist . fromMaybe [] . view (O.settings . O.exposeInvitationURLsTeamAllowlist)) . interpretInternalTeamListToCassandra diff --git a/services/galley/src/Galley/Env.hs b/services/galley/src/Galley/Env.hs index a7736f793fb..7bd0643ab3f 100644 --- a/services/galley/src/Galley/Env.hs +++ b/services/galley/src/Galley/Env.hs @@ -18,15 +18,37 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Galley.Env where +module Galley.Env + ( Env (..), + DeleteItem (..), + reqId, + options, + applog, + manager, + http2Manager, + federator, + brig, + cstate, + hasqlPool, + deleteQueue, + extEnv, + aEnv, + mlsKeys, + rabbitmqChannel, + convCodeURI, + passwordHashingRateLimitEnv, + reqIdMsg, + notificationSubsystemConfig, + currentFanoutLimitOpts, + ) +where import Cassandra import Control.Lens hiding ((.=)) import Data.Id import Data.Misc (HttpsUrl) -import Data.Range import Data.Time.Clock.DiffTime (millisecondsToDiffTime) -import Galley.Options +import Galley.Options hiding (brig, federator) import Galley.Options qualified as O import Galley.Queue qualified as Q import HTTP2.Client.Manager (Http2Manager) @@ -38,6 +60,7 @@ import System.Logger import Util.Options import Wire.API.MLS.Keys import Wire.API.Team.FeatureFlags (FanoutLimit) +import Wire.API.Team.FeatureFlags qualified as FeatureFlags import Wire.AWS qualified as Aws import Wire.ExternalAccess.External import Wire.NotificationSubsystem.Interpreter @@ -72,21 +95,19 @@ reqIdMsg :: RequestId -> Msg -> Msg reqIdMsg = ("request" .=) . unRequestId {-# INLINE reqIdMsg #-} -currentFanoutLimit :: Opts -> FanoutLimit -currentFanoutLimit o = do - let optFanoutLimit = fromIntegral . fromRange $ fromMaybe defaultFanoutLimit (o ^. (O.settings . maxFanoutSize)) - let maxSize = fromIntegral (o ^. (O.settings . maxTeamSize)) - unsafeRange (min maxSize optFanoutLimit) - notificationSubsystemConfig :: Env -> NotificationSubsystemConfig notificationSubsystemConfig env = - NotificationSubsystemConfig - { chunkSize = defaultChunkSize, - fanoutLimit = currentFanoutLimit env._options, - slowPushDelay = - maybe - defaultSlowPushDelay - (millisecondsToDiffTime . toInteger) - (env ^. options . O.settings . deleteConvThrottleMillis), - requestId = env ^. reqId - } + let settings' = env._options._settings + in NotificationSubsystemConfig + { chunkSize = defaultChunkSize, + fanoutLimit = FeatureFlags.currentFanoutLimit settings'._maxTeamSize settings'._maxFanoutSize, + slowPushDelay = + maybe + defaultSlowPushDelay + (millisecondsToDiffTime . toInteger) + (env ^. options . O.settings . deleteConvThrottleMillis), + requestId = env ^. reqId + } + +currentFanoutLimitOpts :: Opts -> FanoutLimit +currentFanoutLimitOpts opts = FeatureFlags.currentFanoutLimit opts._settings._maxTeamSize opts._settings._maxFanoutSize diff --git a/services/galley/src/Galley/Options.hs b/services/galley/src/Galley/Options.hs index 4af6462a1a3..7f040b74d6c 100644 --- a/services/galley/src/Galley/Options.hs +++ b/services/galley/src/Galley/Options.hs @@ -56,21 +56,18 @@ module Galley.Options logNetStrings, logFormat, guestLinkTTLSeconds, - defGuestLinkTTLSeconds, passwordHashingOptions, passwordHashingRateLimit, checkGroupInfo, meetings, validityPeriod, postgresMigration, - GuestLinkTTLSeconds (..), PostgresMigrationOpts (..), StorageLocation (..), ) where import Control.Lens hiding (Level, (.=)) -import Data.Aeson import Data.Aeson.TH (deriveFromJSON) import Data.Domain (Domain) import Data.Id (TeamId) @@ -87,21 +84,10 @@ import Wire.API.Conversation.Protocol import Wire.API.Routes.Version import Wire.API.Team.FeatureFlags import Wire.API.Team.Member +import Wire.ConversationSubsystem.Interpreter (GuestLinkTTLSeconds) import Wire.PostgresMigrationOpts import Wire.RateLimit.Interpreter (RateLimitConfig) -newtype GuestLinkTTLSeconds = GuestLinkTTLSeconds - { unGuestLinkTTLSeconds :: Int - } - deriving (Show, Generic) - -instance FromJSON GuestLinkTTLSeconds where - parseJSON x = do - n <- parseJSON x - if n > 0 && n <= 31536000 - then pure $ GuestLinkTTLSeconds n - else fail "GuestLinkTTLSeconds must be in (0, 31536000]" - data Settings = Settings { -- | Number of connections for the HTTP client pool _httpPoolSize :: !Int, @@ -184,10 +170,6 @@ makeLenses ''MeetingsConfig defConcurrentDeletionEvents :: Int defConcurrentDeletionEvents = 128 --- | Default guest link TTL in days. 365 days if not set. -defGuestLinkTTLSeconds :: GuestLinkTTLSeconds -defGuestLinkTTLSeconds = GuestLinkTTLSeconds $ 60 * 60 * 24 * 365 -- 1 year - data JournalOpts = JournalOpts { -- | SQS queue name to send team events _queueName :: !Text, diff --git a/services/galley/test/integration/API.hs b/services/galley/test/integration/API.hs index e38960b67ba..e54b0487dd9 100644 --- a/services/galley/test/integration/API.hs +++ b/services/galley/test/integration/API.hs @@ -66,7 +66,6 @@ import Data.Text.Ascii qualified as Ascii import Data.Time.Clock (getCurrentTime) import Federator.Discovery (DiscoveryFailure (..)) import Federator.MockServer hiding (status) -import Galley.API.Mapping import Galley.Options (federator, rabbitmq) import Imports hiding (id) import Imports qualified as I @@ -105,6 +104,7 @@ import Wire.API.Team.Member qualified as Teams import Wire.API.User import Wire.API.User.Client import Wire.API.UserMap (UserMap (..)) +import Wire.ConversationSubsystem.Mapping import Wire.StoredConversation hiding (convName) tests :: IO TestSetup -> TestTree diff --git a/services/galley/test/integration/API/Teams.hs b/services/galley/test/integration/API/Teams.hs index 2986fa37c62..0e2536237bd 100644 --- a/services/galley/test/integration/API/Teams.hs +++ b/services/galley/test/integration/API/Teams.hs @@ -1280,7 +1280,7 @@ testBillingInLargeTeam = do refreshIndex opts <- view tsGConf galley <- viewGalley - let fanoutLimit = fromRange $ Galley.currentFanoutLimit opts + let fanoutLimit = fromRange $ Galley.currentFanoutLimitOpts opts allOwnersBeforeFanoutLimit <- foldM ( \billingMembers n -> do diff --git a/services/galley/test/integration/API/Teams/LegalHold.hs b/services/galley/test/integration/API/Teams/LegalHold.hs index f24abb8ada7..db9e3db7d0c 100644 --- a/services/galley/test/integration/API/Teams/LegalHold.hs +++ b/services/galley/test/integration/API/Teams/LegalHold.hs @@ -173,7 +173,7 @@ testRemoveLegalHoldFromTeam = do testAddTeamUserTooLargeWithLegalholdWhitelisted :: (HasCallStack) => TestM () testAddTeamUserTooLargeWithLegalholdWhitelisted = withTeam $ \owner tid -> do o <- view tsGConf - let fanoutLimit = fromIntegral @_ @Integer . fromRange $ Galley.currentFanoutLimit o + let fanoutLimit = fromIntegral @_ @Integer . fromRange $ Galley.currentFanoutLimitOpts o forM_ [2 .. (fanoutLimit + 5)] $ \_n -> do addUserToTeam' owner tid !!! do const 201 === statusCode diff --git a/services/galley/test/integration/API/Teams/LegalHold/DisabledByDefault.hs b/services/galley/test/integration/API/Teams/LegalHold/DisabledByDefault.hs index 222c2af34bf..734af584d74 100644 --- a/services/galley/test/integration/API/Teams/LegalHold/DisabledByDefault.hs +++ b/services/galley/test/integration/API/Teams/LegalHold/DisabledByDefault.hs @@ -200,7 +200,7 @@ testRemoveLegalHoldFromTeam = do testEnablePerTeamTooLarge :: TestM () testEnablePerTeamTooLarge = do o <- view tsGConf - let fanoutLimit = fromIntegral . fromRange $ Galley.currentFanoutLimit o + let fanoutLimit = fromIntegral . fromRange $ Galley.currentFanoutLimitOpts o -- TODO: it is impossible in this test to create teams bigger than the fanout limit. -- Change the +1 to anything else and look at the logs (tid, _owner, _others) <- createBindingTeamWithMembers (fanoutLimit + 5) @@ -215,7 +215,7 @@ testEnablePerTeamTooLarge = do testAddTeamUserTooLargeWithLegalhold :: TestM () testAddTeamUserTooLargeWithLegalhold = do o <- view tsGConf - let fanoutLimit = fromIntegral . fromRange $ Galley.currentFanoutLimit o + let fanoutLimit = fromIntegral . fromRange $ Galley.currentFanoutLimitOpts o (tid, owner, _others) <- createBindingTeamWithMembers fanoutLimit feat :: Public.Feature Public.LegalholdConfig <- responseJsonUnsafe <$> (getEnabled tid Date: Fri, 20 Mar 2026 19:29:36 +0100 Subject: [PATCH 02/19] fix: charts? --- charts/wire-server/values.yaml | 27 +++++++++++++++++++ .../wire-federation-v0/values.yaml.gotmpl | 1 + hack/helm_vars/wire-server/values.yaml.gotmpl | 13 +++++++++ 3 files changed, 41 insertions(+) diff --git a/charts/wire-server/values.yaml b/charts/wire-server/values.yaml index 5b6f0c99b0e..0cea842d6c5 100644 --- a/charts/wire-server/values.yaml +++ b/charts/wire-server/values.yaml @@ -1040,6 +1040,33 @@ background-worker: conversationCodes: cassandra teamFeatures: cassandra + settings: + maxTeamSize: 10000 + maxFanoutSize: 500 + exposeInvitationURLsTeamAllowlist: [] + maxConvSize: 500 + intraListing: true + conversationCodeURI: null + multiIngress: null + federationProtocols: null + guestLinkTTLSeconds: 31536000 + passwordHashingOptions: + algorithm: scrypt # or argon2id + passwordHashingRateLimit: + ipAddrLimit: + burst: 5 + inverseRate: 300000000 # 5 mins, makes it 12 reqs/hour + userLimit: + burst: 5 + inverseRate: 60000000 # 1 min, makes it 60 req/hour + internalLimit: + burst: 10 + inverseRate: 0 # No rate limiting for internal use + ipv4CidrBlock: 32 # Only block individual IP addresses + ipv6CidrBlock: 64 # Block /64 range at a time. + ipAddressExceptions: [] + maxRateLimitedKeys: 100000 # Estimated memory usage: 4 MB + secrets: {} # pgPassword: diff --git a/hack/helm_vars/wire-federation-v0/values.yaml.gotmpl b/hack/helm_vars/wire-federation-v0/values.yaml.gotmpl index 95eee4d1a56..ce38c40a09b 100644 --- a/hack/helm_vars/wire-federation-v0/values.yaml.gotmpl +++ b/hack/helm_vars/wire-federation-v0/values.yaml.gotmpl @@ -324,6 +324,7 @@ background-worker: ipv6CidrBlock: 64 # Block /64 range at a time. ipAddressExceptions: - 127.0.0.1/8 + maxRateLimitedKeys: 100000 checkGroupInfo: false secrets: rabbitmq: diff --git a/hack/helm_vars/wire-server/values.yaml.gotmpl b/hack/helm_vars/wire-server/values.yaml.gotmpl index f6869d62cfb..5818028f563 100644 --- a/hack/helm_vars/wire-server/values.yaml.gotmpl +++ b/hack/helm_vars/wire-server/values.yaml.gotmpl @@ -321,8 +321,20 @@ galley: parallelism: 4 memory: 32 # This needs to be at least 8 * parallelism. passwordHashingRateLimit: + ipAddrLimit: + burst: 5 + inverseRate: 300000000 # 5 mins, makes it 12 reqs/hour + userLimit: + burst: 5 + inverseRate: 60000000 # 1 min, makes it 60 req/hour + internalLimit: + burst: 10 + inverseRate: 0 # No rate limiting for internal use + ipv4CidrBlock: 32 # Only block individual IP addresses + ipv6CidrBlock: 64 # Block /64 range at a time. ipAddressExceptions: - 127.0.0.1/8 + maxRateLimitedKeys: 100000 featureFlags: sso: disabled-by-default # this needs to be the default; tests can enable it when needed. @@ -709,6 +721,7 @@ background-worker: ipv6CidrBlock: 64 # Block /64 range at a time. ipAddressExceptions: - 127.0.0.1/8 + maxRateLimitedKeys: 100000 checkGroupInfo: false secrets: pgPassword: "posty-the-gres" From 56cd25cf09215f331e9ba635bcd667a108cba2fe Mon Sep 17 00:00:00 2001 From: Gautier DI FOLCO Date: Tue, 24 Mar 2026 17:34:40 +0100 Subject: [PATCH 03/19] fix: regression --- .../src/Wire/ConversationSubsystem/Federation.hs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Federation.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Federation.hs index c454ee1a4db..ef70d8c824d 100644 --- a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Federation.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Federation.hs @@ -562,7 +562,8 @@ type MLSBundleStaticErrors = '[ ErrorS 'MLSWelcomeMismatch, ErrorS 'MLSIdentityMismatch, ErrorS 'GroupIdVersionNotSupported, - ErrorS 'MLSInvalidLeafNodeSignature + ErrorS 'MLSInvalidLeafNodeSignature, + ErrorS 'MLSGroupConversationMismatch ] handleMLSMessageErrors :: @@ -605,7 +606,6 @@ sendMLSCommitBundle :: Member ExternalAccess r, Member (Error FederationError) r, Member (Error InternalError) r, - Member (ErrorS 'MLSGroupConversationMismatch) r, Member (ErrorS 'MLSClientMismatch) r, Member (ErrorS 'MLSInvalidLeafNodeIndex) r, Member (ErrorS 'MLSUnsupportedProposal) r, @@ -672,7 +672,6 @@ sendMLSMessage :: Member (Error FederationError) r, Member (Error InternalError) r, Member (FederationAPIAccess FederatorClient) r, - Member (ErrorS 'MLSGroupConversationMismatch) r, Member (ErrorS 'MLSClientMismatch) r, Member (ErrorS 'MLSInvalidLeafNodeIndex) r, Member (ErrorS 'MLSUnsupportedProposal) r, From 5b1a455ef4b81822c2cd91cc0a2d1836a6c4f157 Mon Sep 17 00:00:00 2001 From: Gautier DI FOLCO Date: Thu, 26 Mar 2026 15:58:18 +0100 Subject: [PATCH 04/19] fix(leif): legacy background-worker config --- .../wire-federation-v0/values.yaml.gotmpl | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/hack/helm_vars/wire-federation-v0/values.yaml.gotmpl b/hack/helm_vars/wire-federation-v0/values.yaml.gotmpl index ce38c40a09b..52abc30d9b6 100644 --- a/hack/helm_vars/wire-federation-v0/values.yaml.gotmpl +++ b/hack/helm_vars/wire-federation-v0/values.yaml.gotmpl @@ -297,35 +297,6 @@ background-worker: backendNotificationPusher: pushBackoffMinWait: 1000 # 1ms pushBackoffMaxWait: 500000 # 0.5s - settings: - maxTeamSize: 32 - maxFanoutSize: 18 - exposeInvitationURLsTeamAllowlist: [] - maxConvSize: 16 - intraListing: true - conversationCodeURI: https://kube-staging-nginz-https.zinfra.io/conversation-join/ - guestLinkTTLSeconds: 31536000 - passwordHashingOptions: - algorithm: argon2id - iterations: 1 - parallelism: 4 - memory: 32 # This needs to be at least 8 * parallelism. - passwordHashingRateLimit: - ipAddrLimit: - burst: 5 - inverseRate: 300000000 # 5 mins, makes it 12 reqs/hour - userLimit: - burst: 5 - inverseRate: 60000000 # 1 min, makes it 60 req/hour - internalLimit: - burst: 10 - inverseRate: 0 # No rate limiting for internal use - ipv4CidrBlock: 32 # Only block individual IP addresses - ipv6CidrBlock: 64 # Block /64 range at a time. - ipAddressExceptions: - - 127.0.0.1/8 - maxRateLimitedKeys: 100000 - checkGroupInfo: false secrets: rabbitmq: username: {{ .Values.rabbitmqUsername }} From bbe66e8bdf64c010db1d7c1f4ae27e9e7d777814 Mon Sep 17 00:00:00 2001 From: Gautier DI FOLCO Date: Thu, 26 Mar 2026 16:12:44 +0100 Subject: [PATCH 05/19] fix(leif): galley handler naming --- services/galley/src/Galley/API/Public/Bot.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/galley/src/Galley/API/Public/Bot.hs b/services/galley/src/Galley/API/Public/Bot.hs index 12bed8ed456..22cccf3da33 100644 --- a/services/galley/src/Galley/API/Public/Bot.hs +++ b/services/galley/src/Galley/API/Public/Bot.hs @@ -29,14 +29,14 @@ import Wire.ConversationSubsystem botAPI :: API BotAPI GalleyEffects botAPI = mkNamedAPI @"post-bot-message-unqualified" postBotMessageUnqualified - <@> mkNamedAPI @"get-bot-conversation" getBotConversationEndpoint + <@> mkNamedAPI @"get-bot-conversation" getBotConversationH -getBotConversationEndpoint :: +getBotConversationH :: forall r. (Member ConversationSubsystem r) => BotId -> ConvId -> Sem r BotConvView -getBotConversationEndpoint bid cnv = do +getBotConversationH bid cnv = do guardSecondFactorDisabled (botUserId bid) cnv getBotConversation bid cnv From 6cf737dbb94f92e11e7903602629fa8c3fe4874a Mon Sep 17 00:00:00 2001 From: Gautier DI FOLCO Date: Thu, 26 Mar 2026 16:20:23 +0100 Subject: [PATCH 06/19] fix(leif): galley unit test-suite --- services/galley/default.nix | 1 - services/galley/galley.cabal | 15 --------------- services/galley/test/unit/Run.hs | 31 ------------------------------- 3 files changed, 47 deletions(-) delete mode 100644 services/galley/test/unit/Run.hs diff --git a/services/galley/default.nix b/services/galley/default.nix index b31f76478af..2c96733df81 100644 --- a/services/galley/default.nix +++ b/services/galley/default.nix @@ -275,7 +275,6 @@ mkDerivation { wire-subsystems yaml ]; - testHaskellDepends = [ base imports tasty ]; description = "Conversations"; license = lib.licenses.agpl3Only; } diff --git a/services/galley/galley.cabal b/services/galley/galley.cabal index 324d1fbcb82..949cf3ec06f 100644 --- a/services/galley/galley.cabal +++ b/services/galley/galley.cabal @@ -484,18 +484,3 @@ executable galley-schema if flag(static) ld-options: -static - -test-suite galley-tests - import: common-all - type: exitcode-stdio-1.0 - main-is: ../unit.hs - other-modules: - Paths_galley - Run - - ghc-options: -threaded -with-rtsopts=-N -Wno-x-partial - hs-source-dirs: test/unit - build-depends: - , base - , imports - , tasty diff --git a/services/galley/test/unit/Run.hs b/services/galley/test/unit/Run.hs deleted file mode 100644 index 53dbfe01898..00000000000 --- a/services/galley/test/unit/Run.hs +++ /dev/null @@ -1,31 +0,0 @@ --- This file is part of the Wire Server implementation. --- --- Copyright (C) 2022 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 Run - ( main, - ) -where - -import Imports -import Test.Tasty - -main :: IO () -main = - defaultMain $ - testGroup - "Tests" - [] From ee4c1fa0178a3ac7032c28b52142913cecb21a0a Mon Sep 17 00:00:00 2001 From: Gautier DI FOLCO Date: Tue, 31 Mar 2026 19:53:19 +0200 Subject: [PATCH 07/19] fix(leif): split moved actions --- .../wire-subsystems/src/Wire/BrigAPIAccess.hs | 18 ++ .../src/Wire/ClientSubsystem/Interpreter.hs | 4 + .../src/Wire/ConversationSubsystem.hs | 95 +--------- .../src/Wire/ConversationSubsystem/Action.hs | 5 +- .../ConversationSubsystem/CreateInternal.hs | 2 +- .../Wire/ConversationSubsystem/Features.hs | 168 ----------------- .../Wire/ConversationSubsystem/Interpreter.hs | 46 +---- .../Wire/ConversationSubsystem/Legalhold.hs | 78 -------- .../MLS/GroupInfoCheck.hs | 3 +- .../Wire/ConversationSubsystem/MLS/Message.hs | 3 +- .../src/Wire/ConversationSubsystem/Message.hs | 3 +- .../src/Wire/ConversationSubsystem/Query.hs | 2 +- .../src/Wire/ConversationSubsystem/Update.hs | 2 +- .../src/Wire/ConversationSubsystem/Util.hs | 156 +--------------- .../src/Wire/FeaturesConfigSubsystem.hs | 25 ++- .../FeaturesConfigSubsystem/Interpreter.hs | 155 ++++++++++++++++ .../wire-subsystems/src/Wire/TeamSubsystem.hs | 107 +++++++++++ .../src/Wire/TeamSubsystem/GalleyAPI.hs | 49 ++++- .../src/Wire/TeamSubsystem/Interpreter.hs | 175 ++++++++++++++++-- .../src/Wire/UserClientIndexStore.hs | 105 +++++++++++ .../Wire/MeetingsSubsystem/InterpreterSpec.hs | 13 ++ .../test/unit/Wire/MiniBackend.hs | 69 ++++++- .../SAMLEmailSubsystem/InterpreterSpec.hs | 9 + .../Wire/ScimSubsystem/InterpreterSpec.hs | 4 +- .../InterpreterSpec.hs | 45 ++++- .../UserGroupSubsystem/InterpreterSpec.hs | 59 ++++-- libs/wire-subsystems/wire-subsystems.cabal | 2 - .../brig/src/Brig/CanonicalInterpreter.hs | 11 ++ services/galley/src/Galley/API/Internal.hs | 8 +- services/galley/src/Galley/API/LegalHold.hs | 40 ++-- .../galley/src/Galley/API/LegalHold/Team.hs | 18 ++ services/galley/src/Galley/API/Public/Bot.hs | 5 +- .../galley/src/Galley/API/Public/Feature.hs | 3 +- .../galley/src/Galley/API/Public/LegalHold.hs | 2 +- services/galley/src/Galley/API/Teams.hs | 67 ++++--- .../galley/src/Galley/API/Teams/Features.hs | 32 ++-- 36 files changed, 944 insertions(+), 644 deletions(-) delete mode 100644 libs/wire-subsystems/src/Wire/ConversationSubsystem/Features.hs delete mode 100644 libs/wire-subsystems/src/Wire/ConversationSubsystem/Legalhold.hs diff --git a/libs/wire-subsystems/src/Wire/BrigAPIAccess.hs b/libs/wire-subsystems/src/Wire/BrigAPIAccess.hs index c3d089c81e5..00609d79de8 100644 --- a/libs/wire-subsystems/src/Wire/BrigAPIAccess.hs +++ b/libs/wire-subsystems/src/Wire/BrigAPIAccess.hs @@ -71,6 +71,9 @@ module Wire.BrigAPIAccess deleteGroupInternal, deleteApp, DeleteGroupManagedError (..), + + -- * Assertions + ensureConnectedToLocals, ) where @@ -88,6 +91,7 @@ import Polysemy import Polysemy.Error import Web.Scim.Filter qualified as Scim import Wire.API.Connection +import Wire.API.Error import Wire.API.Error.Galley import Wire.API.MLS.CipherSuite import Wire.API.Routes.Internal.Brig @@ -192,3 +196,17 @@ getConnectionsUnqualifiedBidi uids1 uids2 mrel1 mrel2 = do res1 <- getConnectionsUnqualified uids1 (Just uids2) mrel1 res2 <- getConnectionsUnqualified uids2 (Just uids1) mrel2 pure (res1, res2) + +ensureConnectedToLocals :: + ( Member (ErrorS 'NotConnected) r, + Member BrigAPIAccess r + ) => + UserId -> + [UserId] -> + Sem r () +ensureConnectedToLocals _ [] = pure () +ensureConnectedToLocals u uids = do + (connsFrom, connsTo) <- + getConnectionsUnqualifiedBidi [u] uids (Just Accepted) (Just Accepted) + unless (length connsFrom == length uids && length connsTo == length uids) $ + throwS @'NotConnected diff --git a/libs/wire-subsystems/src/Wire/ClientSubsystem/Interpreter.hs b/libs/wire-subsystems/src/Wire/ClientSubsystem/Interpreter.hs index ede50a8afbd..5261501afba 100644 --- a/libs/wire-subsystems/src/Wire/ClientSubsystem/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/ClientSubsystem/Interpreter.hs @@ -1,3 +1,5 @@ +{-# OPTIONS_GHC -Wno-ambiguous-fields #-} + module Wire.ClientSubsystem.Interpreter ( runClientSubsystem, ClientError (..), @@ -19,6 +21,8 @@ import Data.Set ((\\)) import Data.Set qualified as Set import Data.Time.Clock import Imports hiding ((\\)) +import Data.Time.Clock (UTCTime) +import Imports import Polysemy import Polysemy.Error import Polysemy.Input diff --git a/libs/wire-subsystems/src/Wire/ConversationSubsystem.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem.hs index 0a41e1fda3b..c110e8be58c 100644 --- a/libs/wire-subsystems/src/Wire/ConversationSubsystem.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem.hs @@ -20,12 +20,9 @@ module Wire.ConversationSubsystem ( module Wire.ConversationSubsystem, Util.BotsAndMembers (..), - Util.ConsentGiven (..), Util.canDeleteMember, - Util.consentGiven, Util.isMember, Util.userLHEnabled, - Features.toTeamStatus, MLSRemoval.RemoveUserIncludeMain (..), LegalholdConflicts.guardLegalholdPolicyConflicts, ) @@ -35,13 +32,10 @@ import Data.Code qualified as Code import Data.CommaSeparatedList (CommaSeparatedList) import Data.Domain import Data.Id -import Data.LegalHold (UserLegalHoldStatus) -import Data.Misc (IpAddr, PlainTextPassword6) -import Data.Proxy (Proxy (..)) +import Data.Misc (IpAddr) import Data.Qualified import Data.Range -import Data.Singletons (Demote, Sing, SingKind) -import Galley.Types.Clients (Clients) +import Data.Singletons (Sing) import Imports import Polysemy import Wire.API.Bot (AddBot, RemoveBot) @@ -75,23 +69,15 @@ import Wire.API.Routes.Public.Galley.Messaging (MessageNotSent, PostOtrResponse) import Wire.API.Routes.Public.Util (UpdateResult) import Wire.API.Routes.Version import Wire.API.ServantProto (RawProto (..)) -import Wire.API.Team.Feature (AllTeamFeatures, GuestLinksConfig, LockableFeature) -import Wire.API.Team.LegalHold (UserLegalHoldStatusResponse) -import Wire.API.Team.Member (IsPerm, TeamMemberList) -import Wire.API.User (VerificationAction) +import Wire.API.Team.Feature (GuestLinksConfig, LockableFeature) import Wire.ConversationStore.MLS.Types (ListGlobalSelfConvs) -import Wire.ConversationSubsystem.Features qualified as Features import Wire.ConversationSubsystem.LegalholdConflicts qualified as LegalholdConflicts import Wire.ConversationSubsystem.MLS.IncomingMessage (IncomingBundle, IncomingMessage) import Wire.ConversationSubsystem.MLS.Removal qualified as MLSRemoval import Wire.ConversationSubsystem.Util qualified as Util -import Wire.FeaturesConfigSubsystem.Types (GetFeatureConfig) import Wire.NotificationSubsystem (LocalConversationUpdate) import Wire.StoredConversation (BotMember, LocalMember, StoredConversation) -data PermissionCheckArgs teamAssociation where - PermissionCheckArgs :: forall k (p :: k) teamAssociation. (SingKind k, IsPerm teamAssociation (Demote k)) => Sing p -> Maybe teamAssociation -> PermissionCheckArgs teamAssociation - data ConversationSubsystem m a where NotifyConversationAction :: Sing tag -> @@ -146,7 +132,6 @@ data ConversationSubsystem m a where Range 1 1000 Int32 -> Maybe ConversationPagingState -> ConversationSubsystem r ConvIdsPage - InternalGetClientIds :: [UserId] -> ConversationSubsystem m Clients InternalGetLocalMember :: ConvId -> UserId -> @@ -169,13 +154,11 @@ data ConversationSubsystem m a where GetLocalConversationInternal :: ConvId -> ConversationSubsystem m Conversation - RmClient :: - UserId -> + RemoveClient :: + Local StoredConversation -> + Qualified UserId -> ClientId -> ConversationSubsystem m () - GetClients :: - UserId -> - ConversationSubsystem m [ClientId] AddBot :: Local UserId -> ConnId -> @@ -186,10 +169,6 @@ data ConversationSubsystem m a where Maybe ConnId -> RemoveBot -> ConversationSubsystem m (UpdateResult Event) - GetFeatureInternal :: - (GetFeatureConfig cfg) => - TeamId -> - ConversationSubsystem m (LockableFeature cfg) UpdateCellsState :: ConvId -> CellsState -> @@ -371,20 +350,6 @@ data ConversationSubsystem m a where GetMLSPublicKeys :: Maybe MLSPublicKeyFormat -> ConversationSubsystem m (MLSKeysByPurpose (MLSKeys SomeKey)) - FeatureEnabledForTeam :: - forall cfg m. - (GetFeatureConfig cfg) => - Proxy cfg -> - TeamId -> - ConversationSubsystem m Bool - GetAllTeamFeaturesForUser :: - UserId -> - ConversationSubsystem m AllTeamFeatures - GetSingleFeatureForUser :: - forall cfg m. - (GetFeatureConfig cfg) => - UserId -> - ConversationSubsystem m (LockableFeature cfg) ResetMLSConversation :: Local UserId -> MLSReset -> @@ -394,15 +359,6 @@ data ConversationSubsystem m a where Qualified ConvId -> SubConvId -> ConversationSubsystem m PublicSubConversation - GetUserStatus :: - Local UserId -> - TeamId -> - UserId -> - ConversationSubsystem m UserLegalHoldStatusResponse - GuardSecondFactorDisabled :: - UserId -> - ConvId -> - ConversationSubsystem m () GetBotConversation :: BotId -> ConvId -> @@ -426,6 +382,9 @@ data ConversationSubsystem m a where Local UserId -> Qualified ConvId -> ConversationSubsystem m Public.Conversation + InternalGetConversation :: + ConvId -> + ConversationSubsystem m (Maybe StoredConversation) GetConversationRoles :: Local UserId -> ConvId -> @@ -710,42 +669,9 @@ data ConversationSubsystem m a where ConversationAction (tag :: ConversationActionTag) -> ExtraConversationData -> ConversationSubsystem m LocalConversationUpdate - PermissionCheck :: - (IsPerm teamAssociation perm) => - perm -> Maybe teamAssociation -> ConversationSubsystem m teamAssociation - PermissionCheckSAbs :: - PermissionCheckArgs teamAssociation -> - ConversationSubsystem m teamAssociation - EnsureReAuthorised :: - UserId -> - Maybe PlainTextPassword6 -> - Maybe Code.Value -> - Maybe VerificationAction -> - ConversationSubsystem m () QualifyLocal :: a -> ConversationSubsystem m (Local a) - AssertOnTeam :: - UserId -> - TeamId -> - ConversationSubsystem m () - CheckConsent :: - Map UserId TeamId -> - UserId -> - ConversationSubsystem m Util.ConsentGiven - GetLHStatusForUsers :: - [UserId] -> - ConversationSubsystem m [(UserId, UserLegalHoldStatus)] - EnsureConnectedToLocals :: - UserId -> - [UserId] -> - ConversationSubsystem m () - GetTeamMembersForFanout :: - TeamId -> - ConversationSubsystem m TeamMemberList - AssertTeamExists :: - TeamId -> - ConversationSubsystem m () InternalUpsertOne2OneConversation :: UpsertOne2OneConversationRequest -> ConversationSubsystem m () @@ -765,6 +691,3 @@ data ConversationSubsystem m a where ConversationSubsystem m () makeSem ''ConversationSubsystem - -permissionCheckS :: forall k (p :: k) teamAssociation r. (Member ConversationSubsystem r, SingKind k, IsPerm teamAssociation (Demote k)) => Sing p -> Maybe teamAssociation -> Sem r teamAssociation -permissionCheckS p mTeam = send (PermissionCheckSAbs (PermissionCheckArgs p mTeam)) diff --git a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Action.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Action.hs index 8fb060eb0be..1d689bbebe5 100644 --- a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Action.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Action.hs @@ -118,7 +118,6 @@ import Wire.ConversationSubsystem.Action.Kick import Wire.ConversationSubsystem.Action.Leave import Wire.ConversationSubsystem.Action.Notify import Wire.ConversationSubsystem.Action.Reset -import Wire.ConversationSubsystem.Features import Wire.ConversationSubsystem.MLS.Conversation import Wire.ConversationSubsystem.MLS.Migration import Wire.ConversationSubsystem.MLS.Removal @@ -139,7 +138,7 @@ import Wire.StoredConversation import Wire.StoredConversation qualified as Data import Wire.TeamCollaboratorsSubsystem import Wire.TeamStore -import Wire.TeamSubsystem (TeamSubsystem) +import Wire.TeamSubsystem (ConsentGiven (..), TeamSubsystem, consentGiven) import Wire.TeamSubsystem qualified as TeamSubsystem import Wire.UserList import Wire.Util @@ -839,7 +838,7 @@ performConversationJoin qusr lconv (ConversationJoin invited role joinType) = do throwS @'MissingLegalholdConsent convUsersLHStatus <- do - uidsStatus <- getLHStatusForUsers ((.id_) <$> convUsers) + uidsStatus <- TeamSubsystem.getLHStatusForUsers ((.id_) <$> convUsers) pure $ zipWith (\mem (_, status) -> (mem, status)) convUsers uidsStatus if any diff --git a/libs/wire-subsystems/src/Wire/ConversationSubsystem/CreateInternal.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/CreateInternal.hs index f2aab2d82e3..e28425c9061 100644 --- a/libs/wire-subsystems/src/Wire/ConversationSubsystem/CreateInternal.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/CreateInternal.hs @@ -80,7 +80,7 @@ import Wire.StoredConversation qualified as Data import Wire.TeamCollaboratorsSubsystem import Wire.TeamStore (TeamStore) import Wire.TeamStore qualified as TeamStore -import Wire.TeamSubsystem (TeamSubsystem) +import Wire.TeamSubsystem (TeamSubsystem, permissionCheck) import Wire.TeamSubsystem qualified as TeamSubsystem import Wire.UserList (UserList (UserList), toUserList, ulAddLocal, ulAll, ulFromLocals, ulLocals, ulRemotes) diff --git a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Features.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Features.hs deleted file mode 100644 index 02bb7168d63..00000000000 --- a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Features.hs +++ /dev/null @@ -1,168 +0,0 @@ -{-# OPTIONS_GHC -Wno-ambiguous-fields #-} - --- This file is part of the Wire Server implementation. --- --- Copyright (C) 2022 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.ConversationSubsystem.Features - ( getFeature, - getFeatureInternal, - getAllTeamFeaturesForServer, - getAllTeamFeaturesForTeam, - getAllTeamFeaturesForUser, - getSingleFeatureForUser, - GetFeatureConfig (..), - getFeatureForTeam, - guardSecondFactorDisabled, - DoAuth (..), - featureEnabledForTeam, - toTeamStatus, - ) -where - -import Control.Error (hush) -import Data.Id -import Data.SOP -import Data.Tagged -import Imports -import Polysemy -import Polysemy.Error -import Wire.API.Conversation (cnvmTeam) -import Wire.API.Error -import Wire.API.Error.Galley -import Wire.API.Routes.Internal.Galley.TeamFeatureNoConfigMulti qualified as Multi -import Wire.API.Team.Feature -import Wire.ConversationStore as ConversationStore -import Wire.ConversationSubsystem.Util -import Wire.FeaturesConfigSubsystem -import Wire.FeaturesConfigSubsystem.Types -import Wire.TeamStore qualified as TeamStore -import Wire.TeamSubsystem (TeamSubsystem) -import Wire.TeamSubsystem qualified as TeamSubsystem - --- FUTUREWORK: everything in this module should be moved to the FeatureConfigSubsystem -data DoAuth = DoAuth UserId | DontDoAuth - -getFeatureInternal :: - ( GetFeatureConfig cfg, - Member (ErrorS 'TeamNotFound) r, - Member TeamStore.TeamStore r, - Member FeaturesConfigSubsystem r - ) => - TeamId -> - Sem r (LockableFeature cfg) -getFeatureInternal tid = do - assertTeamExists tid - getFeatureForTeam tid - -toTeamStatus :: TeamId -> LockableFeature cfg -> Multi.TeamStatus cfg -toTeamStatus tid feat = Multi.TeamStatus tid feat.status - -getTeamAndCheckMembership :: - ( Member TeamStore.TeamStore r, - Member (ErrorS 'NotATeamMember) r, - Member (ErrorS 'TeamNotFound) r, - Member TeamSubsystem r - ) => - UserId -> - Sem r (Maybe TeamId) -getTeamAndCheckMembership uid = do - mTid <- TeamStore.getOneUserTeam uid - for_ mTid $ \tid -> do - zusrMembership <- TeamSubsystem.internalGetTeamMember uid tid - void $ maybe (throwS @'NotATeamMember) pure zusrMembership - assertTeamExists tid - pure mTid - -getAllTeamFeatures :: - forall r. - (Member FeaturesConfigSubsystem r) => - TeamId -> - Sem r AllTeamFeatures -getAllTeamFeatures tid = getAllTeamFeaturesForTeam tid - -getAllTeamFeaturesForUser :: - forall r. - ( Member (ErrorS 'NotATeamMember) r, - Member (ErrorS 'TeamNotFound) r, - Member TeamStore.TeamStore r, - Member TeamSubsystem r, - Member FeaturesConfigSubsystem r, - GetFeatureConfigEffects r - ) => - UserId -> - Sem r AllTeamFeatures -getAllTeamFeaturesForUser uid = do - mTid <- getTeamAndCheckMembership uid - case mTid of - Nothing -> hsequence' $ hcpure (Proxy @(GetAllTeamFeaturesForUserConstraints r)) $ Comp $ getFeatureForUser uid - Just tid -> getAllTeamFeatures tid - -getSingleFeatureForUser :: - forall cfg r. - ( GetFeatureConfig cfg, - Member (ErrorS 'NotATeamMember) r, - Member (ErrorS 'TeamNotFound) r, - Member TeamStore.TeamStore r, - Member TeamSubsystem r, - Member FeaturesConfigSubsystem r - ) => - UserId -> - Sem r (LockableFeature cfg) -getSingleFeatureForUser uid = do - mTid <- getTeamAndCheckMembership uid - getFeatureForTeamUser @_ @cfg uid mTid - --- | If second factor auth is enabled, make sure that end-points that don't support it, but --- should, are blocked completely. (This is a workaround until we have 2FA for those --- end-points as well.) --- --- This function exists to resolve a cyclic dependency. -guardSecondFactorDisabled :: - forall r. - ( Member (ErrorS 'AccessDenied) r, - Member TeamStore.TeamStore r, - Member ConversationStore r, - Member FeaturesConfigSubsystem r - ) => - UserId -> - ConvId -> - Sem r () -guardSecondFactorDisabled uid cid = do - mTid <- fmap hush . runError @() $ do - convData <- ConversationStore.getConversationMetadata cid >>= note () - tid <- note () convData.cnvmTeam - mapError (unTagged @'TeamNotFound @()) $ assertTeamExists tid - pure tid - - tf <- getFeatureForTeamUser @_ @SndFactorPasswordChallengeConfig uid mTid - case tf.status of - FeatureStatusDisabled -> pure () - FeatureStatusEnabled -> throwS @'AccessDenied - -featureEnabledForTeam :: - forall cfg r. - ( GetFeatureConfig cfg, - Member (ErrorS 'TeamNotFound) r, - Member TeamStore.TeamStore r, - Member FeaturesConfigSubsystem r - ) => - TeamId -> - Sem r Bool -featureEnabledForTeam tid = - (==) FeatureStatusEnabled - . (.status) - <$> getFeatureInternal @cfg tid diff --git a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Interpreter.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Interpreter.hs index a031f31d721..ec52fe7f82d 100644 --- a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Interpreter.hs @@ -25,9 +25,7 @@ module Wire.ConversationSubsystem.Interpreter ) where -import Data.Proxy (Proxy (..)) import Data.Qualified -import Data.Singletons (fromSing) import Galley.Types.Error (InternalError, InvalidInput (..)) import Imports import Polysemy @@ -52,14 +50,10 @@ import Wire.ConversationStore (ConversationStore) import Wire.ConversationStore qualified as ConvStore import Wire.ConversationSubsystem import Wire.ConversationSubsystem.Action.Notify qualified as ActionNotify -import Wire.ConversationSubsystem.Clients qualified as Clients import Wire.ConversationSubsystem.Create qualified as Create import Wire.ConversationSubsystem.CreateInternal qualified as CreateInternal -import Wire.ConversationSubsystem.Features qualified as Features import Wire.ConversationSubsystem.Federation qualified as Federation import Wire.ConversationSubsystem.Fetch qualified as Fetch -import Wire.ConversationSubsystem.Internal qualified as Internal -import Wire.ConversationSubsystem.Legalhold qualified as Legalhold import Wire.ConversationSubsystem.MLS qualified as MLS import Wire.ConversationSubsystem.MLS.Enabled qualified as MLSEnabled import Wire.ConversationSubsystem.MLS.GroupInfo qualified as MLSGroupInfo @@ -221,8 +215,6 @@ interpretConversationSubsystem = interpretH $ \case liftT $ ConvStore.getConversations convIds GetConversationIds lusr maxIds pagingState -> liftT $ Fetch.getConversationIdsImpl lusr maxIds pagingState - InternalGetClientIds uids -> - liftT $ Internal.internalGetClientIds uids InternalGetLocalMember cid uid -> liftT $ ConvStore.getLocalMember cid uid PostMLSCommitBundle loc qusr c ctype qConvOrSub conn oosCheck bundle -> @@ -304,10 +296,6 @@ interpretConversationSubsystem = interpretH $ \case liftT $ MLSReset.resetMLSConversation lusr reset GetSubConversation lusr cnv sub -> liftT $ MLSSubConversation.getSubConversation lusr cnv sub - GetUserStatus lusr tid uid -> - liftT $ Legalhold.getUserStatus lusr tid uid - GuardSecondFactorDisabled uid cid -> - liftT $ Features.guardSecondFactorDisabled uid cid GetBotConversation bid cnv -> liftT $ Query.getBotConversation bid cnv GetUnqualifiedOwnConversation lusr cnv -> @@ -316,6 +304,8 @@ interpretConversationSubsystem = interpretH $ \case liftT $ Query.getOwnConversation lusr qcnv GetConversation lusr qcnv -> liftT $ Query.getConversation lusr qcnv + InternalGetConversation cnv -> + liftT $ ConvStore.getConversation cnv GetConversationRoles lusr cnv -> liftT $ Query.getConversationRoles lusr cnv GetGroupInfo lusr qcnv -> @@ -418,32 +408,8 @@ interpretConversationSubsystem = interpretH $ \case liftT $ Query.getConversations lusr mids mstart msize SearchChannels lusr tid searchString sortOrder pageSize lastName lastId discoverable -> liftT $ Query.searchChannels lusr tid searchString sortOrder pageSize lastName lastId discoverable - FeatureEnabledForTeam (Proxy :: Proxy cfg) tid -> - liftT $ Features.featureEnabledForTeam @cfg tid - GetAllTeamFeaturesForUser uid -> - liftT $ Features.getAllTeamFeaturesForUser uid - GetSingleFeatureForUser uid -> - liftT $ Features.getSingleFeatureForUser uid - PermissionCheck p mTeam -> - liftT $ Util.permissionCheck p mTeam - PermissionCheckSAbs (PermissionCheckArgs p mTeam) -> - liftT $ Util.permissionCheck (fromSing p) mTeam - EnsureReAuthorised u secret mbAction mbCode -> - liftT $ Util.ensureReAuthorised u secret mbAction mbCode QualifyLocal a -> liftT $ Util.qualifyLocal a - AssertOnTeam uid tid -> - liftT $ Util.assertOnTeam uid tid - CheckConsent teamsOfUsers other -> - liftT $ Util.checkConsent teamsOfUsers other - GetLHStatusForUsers uids -> - liftT $ Util.getLHStatusForUsers uids - EnsureConnectedToLocals u uids -> - liftT $ Util.ensureConnectedToLocals u uids - GetTeamMembersForFanout tid -> - liftT $ Util.getTeamMembersForFanout tid - AssertTeamExists tid -> - liftT $ Util.assertTeamExists tid InternalGetMember qcnv usr -> liftT $ Query.internalGetMember qcnv usr GetConversationMeta cnv -> @@ -454,16 +420,12 @@ interpretConversationSubsystem = interpretH $ \case liftT $ Query.isMLSOne2OneEstablished lself qother GetLocalConversationInternal cid -> liftT $ Query.getLocalConversationInternal cid - RmClient usr cid -> - liftT $ Clients.rmClient usr cid - GetClients usr -> - liftT $ Clients.getClients usr + RemoveClient lc qusr c -> + liftT $ MLSRemoval.removeClient lc qusr c AddBot lusr zcon b -> liftT $ Update.addBot lusr zcon b RmBot lusr zcon b -> liftT $ Update.rmBot lusr zcon b - GetFeatureInternal tid -> - liftT $ Features.getFeatureInternal tid UpdateCellsState cnv state -> liftT $ Update.updateCellsState cnv state RemoveUser lc includeMain qusr -> diff --git a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Legalhold.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Legalhold.hs deleted file mode 100644 index 1cdc1122013..00000000000 --- a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Legalhold.hs +++ /dev/null @@ -1,78 +0,0 @@ --- This file is part of the Wire Server implementation. --- --- Copyright (C) 2022 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.ConversationSubsystem.Legalhold (getUserStatus) where - -import Control.Lens (view) -import Data.ByteString.Conversion (toByteString') -import Data.Id -import Data.LegalHold (UserLegalHoldStatus (..)) -import Data.Qualified -import Galley.Types.Error -import Imports -import Polysemy -import Polysemy.Error -import Polysemy.TinyLog qualified as P -import System.Logger.Class qualified as Log -import Wire.API.Error -import Wire.API.Error.Galley -import Wire.API.Team.LegalHold -import Wire.API.Team.LegalHold qualified as Public -import Wire.API.Team.Member -import Wire.API.User.Client.Prekey -import Wire.LegalHoldStore qualified as LegalHoldData -import Wire.TeamSubsystem (TeamSubsystem) -import Wire.TeamSubsystem qualified as TeamSubsystem - --- | Learn whether a user has LH enabled and fetch pre-keys. --- Note that this is accessible to ANY authenticated user, even ones outside the team -getUserStatus :: - forall r. - ( Member (Error InternalError) r, - Member (ErrorS 'TeamMemberNotFound) r, - Member LegalHoldData.LegalHoldStore r, - Member P.TinyLog r, - Member TeamSubsystem r - ) => - Local UserId -> - TeamId -> - UserId -> - Sem r Public.UserLegalHoldStatusResponse -getUserStatus _lzusr tid uid = do - teamMember <- noteS @'TeamMemberNotFound =<< TeamSubsystem.internalGetTeamMember uid tid - let status = view legalHoldStatus teamMember - (mlk, lcid) <- case status of - UserLegalHoldNoConsent -> pure (Nothing, Nothing) - UserLegalHoldDisabled -> pure (Nothing, Nothing) - UserLegalHoldPending -> makeResponseDetails - UserLegalHoldEnabled -> makeResponseDetails - pure $ UserLegalHoldStatusResponse status mlk lcid - where - makeResponseDetails :: Sem r (Maybe LastPrekey, Maybe ClientId) - makeResponseDetails = do - mLastKey <- fmap snd <$> LegalHoldData.selectPendingPrekeys uid - lastKey <- case mLastKey of - Nothing -> do - P.err - . Log.msg - $ "expected to find a prekey for user: " - <> toByteString' uid - <> " but none was found" - throw NoPrekeyForUser - Just lstKey -> pure lstKey - let clientId = clientIdFromPrekey . unpackLastPrekey $ lastKey - pure (Just lastKey, Just clientId) diff --git a/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/GroupInfoCheck.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/GroupInfoCheck.hs index e0c05e2b540..d3bdabf3e3b 100644 --- a/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/GroupInfoCheck.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/GroupInfoCheck.hs @@ -42,8 +42,7 @@ import Wire.API.MLS.Serialisation import Wire.API.Team.Feature import Wire.ConversationStore import Wire.ConversationStore.MLS.Types -import Wire.ConversationSubsystem.Features -import Wire.FeaturesConfigSubsystem (FeaturesConfigSubsystem) +import Wire.FeaturesConfigSubsystem (FeaturesConfigSubsystem, getFeatureForTeam) data GroupInfoMismatch = GroupInfoMismatch {clients :: [(Int, ClientIdentity)]} diff --git a/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Message.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Message.hs index 719149f6980..1059711effd 100644 --- a/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Message.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Message.hs @@ -68,7 +68,6 @@ import Wire.BrigAPIAccess (BrigAPIAccess) import Wire.ConversationStore import Wire.ConversationStore.MLS.Types import Wire.ConversationSubsystem.Action -import Wire.ConversationSubsystem.Legalhold (getUserStatus) import Wire.ConversationSubsystem.MLS.Commit.Core (getCommitData) import Wire.ConversationSubsystem.MLS.Commit.ExternalCommit import Wire.ConversationSubsystem.MLS.Commit.InternalCommit @@ -92,7 +91,7 @@ import Wire.Sem.Now qualified as Now import Wire.Sem.Random (Random) import Wire.StoredConversation import Wire.TeamStore qualified as TeamStore -import Wire.TeamSubsystem (TeamSubsystem) +import Wire.TeamSubsystem (TeamSubsystem, getUserStatus) -- FUTUREWORK -- - Check that the capabilities of a leaf node in an add proposal contains all diff --git a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Message.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Message.hs index f3f1efd9172..214bdcf6ed8 100644 --- a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Message.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Message.hs @@ -87,7 +87,7 @@ import Wire.Sem.Now (Now) import Wire.Sem.Now qualified as Now import Wire.StoredConversation import Wire.TeamStore -import Wire.TeamSubsystem (TeamSubsystem) +import Wire.TeamSubsystem (TeamSubsystem, getTeamMembersForFanout) import Wire.TeamSubsystem qualified as TeamSubsystem import Wire.UserClientIndexStore @@ -360,7 +360,6 @@ postBroadcast lusr con msg = runError $ do maybeFetchAllMembersInTeam :: ( Member (ErrorS 'BroadcastLimitExceeded) r, - Member (Input FanoutLimit) r, Member TeamSubsystem r ) => TeamId -> diff --git a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Query.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Query.hs index 20609b0f1e3..d64ec116e6f 100644 --- a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Query.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Query.hs @@ -118,7 +118,7 @@ import Wire.StoredConversation import Wire.StoredConversation qualified as Data import Wire.TeamCollaboratorsSubsystem import Wire.TeamStore -import Wire.TeamSubsystem (TeamSubsystem) +import Wire.TeamSubsystem (TeamSubsystem, permissionCheck) import Wire.TeamSubsystem qualified as TeamSubsystem import Wire.UserList import Wire.Util diff --git a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Update.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Update.hs index 45b12100ba3..dac71009aa6 100644 --- a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Update.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Update.hs @@ -154,7 +154,7 @@ import Wire.Sem.Random (Random) import Wire.StoredConversation import Wire.TeamCollaboratorsSubsystem import Wire.TeamStore -import Wire.TeamSubsystem (TeamSubsystem) +import Wire.TeamSubsystem (TeamSubsystem, permissionCheck) import Wire.TeamSubsystem qualified as TeamSubsystem import Wire.UserClientIndexStore qualified as E import Wire.UserGroupStore (UserGroupStore, getUserGroupsForConv) diff --git a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Util.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Util.hs index 5de750c6c8c..5194f670e5b 100644 --- a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Util.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Util.hs @@ -24,7 +24,6 @@ import Control.Lens (view, (^.)) import Control.Monad.Extra (allM, anyM) import Control.Monad.Trans.Maybe import Data.Bifunctor -import Data.Code qualified as Code import Data.Default import Data.Domain (Domain) import Data.Id as Id @@ -34,7 +33,7 @@ import Data.List.Extra (chunksOf, nubOrd) import Data.List.NonEmpty (NonEmpty) import Data.List.NonEmpty qualified as NE import Data.Map qualified as Map -import Data.Misc (PlainTextPassword6, PlainTextPassword8) +import Data.Misc (PlainTextPassword8) import Data.Qualified import Data.Set qualified as Set import Data.Singletons @@ -71,10 +70,8 @@ import Wire.API.Team.Collaborator qualified as CollaboratorPermission (Collabora import Wire.API.Team.FeatureFlags import Wire.API.Team.Member import Wire.API.Team.Member qualified as Mem -import Wire.API.Team.Member.Error import Wire.API.Team.Role import Wire.API.User hiding (userId) -import Wire.API.User.Auth.ReAuth import Wire.API.VersionInfo import Wire.BackendNotificationQueueAccess import Wire.BrigAPIAccess @@ -94,7 +91,7 @@ import Wire.Sem.Now qualified as Now import Wire.StoredConversation as Data import Wire.TeamCollaboratorsSubsystem import Wire.TeamStore -import Wire.TeamSubsystem (TeamSubsystem) +import Wire.TeamSubsystem (ConsentGiven (..), TeamSubsystem, consentGiven, getLHStatus) import Wire.TeamSubsystem qualified as TeamSubsystem import Wire.UserList @@ -195,20 +192,6 @@ ensureConnected self others = do ensureConnectedToLocals (tUnqualified self) (ulLocals others) ensureConnectedToRemotes self (ulRemotes others) -ensureConnectedToLocals :: - ( Member (ErrorS 'NotConnected) r, - Member BrigAPIAccess r - ) => - UserId -> - [UserId] -> - Sem r () -ensureConnectedToLocals _ [] = pure () -ensureConnectedToLocals u uids = do - (connsFrom, connsTo) <- - getConnectionsUnqualifiedBidi [u] uids (Just Accepted) (Just Accepted) - unless (length connsFrom == length uids && length connsTo == length uids) $ - throwS @'NotConnected - ensureConnectedToRemotes :: ( Member BrigAPIAccess r, Member (ErrorS 'NotConnected) r @@ -222,18 +205,6 @@ ensureConnectedToRemotes u remotes = do when (length acceptedConns /= length remotes) $ throwS @'NotConnected -ensureReAuthorised :: - ( Member BrigAPIAccess r, - Member (Error AuthenticationError) r - ) => - UserId -> - Maybe PlainTextPassword6 -> - Maybe Code.Value -> - Maybe VerificationAction -> - Sem r () -ensureReAuthorised u secret mbAction mbCode = - reauthUser u (ReAuthUser secret mbAction mbCode) >>= fromEither - ensureManageChannelsPermission :: (Member (ErrorS 'ConvNotFound) r) => StoredConversation -> TeamMember -> Sem r () ensureManageChannelsPermission conv tm = do unless (hasManageChannelsPermission conv tm) $ throwS @'ConvNotFound @@ -323,72 +294,6 @@ checkGroupIdSupport loc conv joinAction = void $ runMaybeT $ do failOnFirstError :: (Member (ErrorS GroupIdVersionNotSupported) r) => [Either e x] -> Sem r () failOnFirstError = traverse_ $ either (\_ -> throwS @GroupIdVersionNotSupported) pure --- | Same as 'permissionCheck', but for a statically known permission. -permissionCheckS :: - forall teamAssociation perm (p :: perm) r. - ( SingKind perm, - IsPerm teamAssociation (Demote perm), - ( Member (ErrorS (PermError p)) r, - Member (ErrorS 'NotATeamMember) r - ) - ) => - Sing p -> - Maybe teamAssociation -> - Sem r teamAssociation -permissionCheckS p = - \case - Just m -> do - if m `hasPermission` fromSing p - then pure m - else throwS @(PermError p) - -- FUTUREWORK: factor `noteS` out of this function. - Nothing -> throwS @'NotATeamMember - --- | If a team member is not given throw 'notATeamMember'; if the given team --- member does not have the given permission, throw 'operationDenied'. --- Otherwise, return the team member. -permissionCheck :: - ( IsPerm teamAssociation perm, - ( Member (ErrorS OperationDenied) r, - Member (ErrorS 'NotATeamMember) r - ) - ) => - perm -> - Maybe teamAssociation -> - Sem r teamAssociation --- FUTUREWORK: factor `noteS` out of this function. -permissionCheck p = \case - Just m -> do - if m `hasPermission` p - then pure m - else throwS @OperationDenied - -- FUTUREWORK: factor `noteS` out of this function. - Nothing -> throwS @'NotATeamMember - -assertTeamExists :: - ( Member (ErrorS 'TeamNotFound) r, - Member TeamStore r - ) => - TeamId -> - Sem r () -assertTeamExists tid = do - teamExists <- isJust <$> getTeam tid - if teamExists - then pure () - else throwS @'TeamNotFound - -assertOnTeam :: - ( Member (ErrorS 'NotATeamMember) r, - Member TeamSubsystem r - ) => - UserId -> - TeamId -> - Sem r () -assertOnTeam uid tid = - TeamSubsystem.internalGetTeamMember uid tid >>= \case - Nothing -> throwS @'NotATeamMember - Just _ -> pure () - -- | Try to accept a 1-1 conversation, promoting connect conversations as appropriate. acceptOne2One :: ( Member ConversationStore r, @@ -997,38 +902,6 @@ userLHEnabled = \case UserLegalHoldDisabled -> False UserLegalHoldNoConsent -> False -data ConsentGiven = ConsentGiven | ConsentNotGiven - deriving (Eq, Ord, Show) - -consentGiven :: UserLegalHoldStatus -> ConsentGiven -consentGiven = \case - UserLegalHoldDisabled -> ConsentGiven - UserLegalHoldPending -> ConsentGiven - UserLegalHoldEnabled -> ConsentGiven - UserLegalHoldNoConsent -> ConsentNotGiven - -checkConsent :: - (Member TeamSubsystem r) => - Map UserId TeamId -> - UserId -> - Sem r ConsentGiven -checkConsent teamsOfUsers other = do - consentGiven <$> getLHStatus (Map.lookup other teamsOfUsers) other - --- Get legalhold status of user. Defaults to 'defUserLegalHoldStatus' if user --- doesn't belong to a team. -getLHStatus :: - (Member TeamSubsystem r) => - Maybe TeamId -> - UserId -> - Sem r UserLegalHoldStatus -getLHStatus teamOfUser other = do - case teamOfUser of - Nothing -> pure defUserLegalHoldStatus - Just team -> do - mMember <- TeamSubsystem.internalGetTeamMember other team - pure $ maybe defUserLegalHoldStatus (view legalHoldStatus) mMember - anyLegalholdActivated :: ( Member (Input ConversationSubsystemConfig) r, Member TeamStore r, @@ -1075,31 +948,6 @@ allLegalholdConsentGiven uids = do eitherTeamMemberAndLHAllowedOrDefLHStatus teamsPage uid = do fromMaybe (consentGiven defUserLegalHoldStatus == ConsentGiven) <$> (for (Map.lookup uid teamsPage) isTeamLegalholdWhitelisted) --- | Add to every uid the legalhold status -getLHStatusForUsers :: - (Member TeamStore r, Member TeamSubsystem r) => - [UserId] -> - Sem r [(UserId, UserLegalHoldStatus)] -getLHStatusForUsers uids = - mconcat - <$> for - (chunksOf 32 uids) - ( \uidsChunk -> do - teamsOfUsers <- getUsersTeams uidsChunk - for uidsChunk $ \uid -> do - (uid,) <$> getLHStatus (Map.lookup uid teamsOfUsers) uid - ) - -getTeamMembersForFanout :: - ( Member (Input FanoutLimit) r, - Member TeamSubsystem r - ) => - TeamId -> - Sem r TeamMemberList -getTeamMembersForFanout tid = do - lim <- input - TeamSubsystem.internalGetTeamMembersWithLimit tid (Just lim) - ensureMemberLimit :: ( Foldable f, ( Member (ErrorS 'TooManyMembers) r, diff --git a/libs/wire-subsystems/src/Wire/FeaturesConfigSubsystem.hs b/libs/wire-subsystems/src/Wire/FeaturesConfigSubsystem.hs index d8824e685c2..2b5da0c95fe 100644 --- a/libs/wire-subsystems/src/Wire/FeaturesConfigSubsystem.hs +++ b/libs/wire-subsystems/src/Wire/FeaturesConfigSubsystem.hs @@ -19,7 +19,8 @@ module Wire.FeaturesConfigSubsystem where -import Data.Id (TeamId, UserId) +import Data.Id (ConvId, TeamId, UserId) +import Data.Proxy (Proxy) import Data.Qualified (Local) import Imports import Polysemy @@ -35,5 +36,27 @@ data FeaturesConfigSubsystem m a where GetAllTeamFeaturesForTeamMember :: Local UserId -> TeamId -> FeaturesConfigSubsystem m AllTeamFeatures GetAllTeamFeaturesForTeam :: TeamId -> FeaturesConfigSubsystem m AllTeamFeatures GetAllTeamFeaturesForServer :: FeaturesConfigSubsystem m AllTeamFeatures + GuardSecondFactorDisabled :: + UserId -> + ConvId -> + FeaturesConfigSubsystem m () + FeatureEnabledForTeam :: + forall cfg m. + (GetFeatureConfig cfg) => + Proxy cfg -> + TeamId -> + FeaturesConfigSubsystem m Bool + GetAllTeamFeaturesForUser :: + UserId -> + FeaturesConfigSubsystem m AllTeamFeatures + GetSingleFeatureForUser :: + forall cfg m. + (GetFeatureConfig cfg) => + UserId -> + FeaturesConfigSubsystem m (LockableFeature cfg) + GetFeatureInternal :: + (GetFeatureConfig cfg) => + TeamId -> + FeaturesConfigSubsystem m (LockableFeature cfg) makeSem ''FeaturesConfigSubsystem diff --git a/libs/wire-subsystems/src/Wire/FeaturesConfigSubsystem/Interpreter.hs b/libs/wire-subsystems/src/Wire/FeaturesConfigSubsystem/Interpreter.hs index d3842f68bf5..70936a806e0 100644 --- a/libs/wire-subsystems/src/Wire/FeaturesConfigSubsystem/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/FeaturesConfigSubsystem/Interpreter.hs @@ -5,24 +5,31 @@ module Wire.FeaturesConfigSubsystem.Interpreter where +import Control.Error (hush) import Data.Aeson.Types qualified as A import Data.Id import Data.Qualified (tUnqualified) import Data.SOP +import Data.Tagged import Data.Text.Lazy qualified as LT import Imports import Polysemy import Polysemy.Error import Polysemy.Input +import Wire.API.Conversation (ConversationMetadata (..)) import Wire.API.Error import Wire.API.Error.Galley import Wire.API.Team.Feature import Wire.API.Team.FeatureFlags +import Wire.BrigAPIAccess (BrigAPIAccess) +import Wire.ConversationStore qualified as ConversationStore import Wire.FeaturesConfigSubsystem import Wire.FeaturesConfigSubsystem.Types import Wire.FeaturesConfigSubsystem.Utils +import Wire.LegalHoldStore (LegalHoldStore) import Wire.TeamFeatureStore import Wire.TeamFeatureStore.Error (TeamFeatureStoreError (..)) +import Wire.TeamStore qualified as TeamStore import Wire.TeamSubsystem (TeamSubsystem) import Wire.TeamSubsystem qualified as TeamSubsystem @@ -30,8 +37,11 @@ runFeaturesConfigSubsystem :: forall r a. ( Member TeamFeatureStore r, Member TeamSubsystem r, + Member TeamStore.TeamStore r, + Member ConversationStore.ConversationStore r, Member (Error TeamFeatureStoreError) r, Member (ErrorS 'NotATeamMember) r, + Member (ErrorS 'AccessDenied) r, GetFeatureConfigEffects r ) => Sem (FeaturesConfigSubsystem : r) a -> @@ -54,6 +64,16 @@ runFeaturesConfigSubsystem = interpret $ \case getAllTeamFeaturesImpl tid GetAllTeamFeaturesForServer -> getAllTeamFeaturesForServerImpl + GuardSecondFactorDisabled uid cid -> + guardSecondFactorDisabledImpl uid cid + FeatureEnabledForTeam (Proxy :: Proxy cfg) tid -> + featureEnabledForTeamImpl @cfg tid + GetAllTeamFeaturesForUser uid -> + getAllTeamFeaturesForUserImpl uid + GetSingleFeatureForUser uid -> + getSingleFeatureForUserImpl uid + GetFeatureInternal tid -> + getFeatureInternalImpl tid -- Internal helpers @@ -129,3 +149,138 @@ parseDbFeatureOrThrow feat = mapError (TeamFeatureStoreErrorInternalError . LT.pack) . fromEither $ A.parseEither (const (parseDbFeature feat)) () + +getFeatureInternalImpl :: + ( GetFeatureConfig cfg, + Member TeamFeatureStore r, + Member LegalHoldStore r, + Member TeamSubsystem r, + Member BrigAPIAccess r, + Member (Input FeatureFlags) r, + Member (Input (FeatureDefaults LegalholdConfig)) r, + Member (Input ExposeInvitationURLsAllowlist) r, + Member (Error TeamFeatureStoreError) r + ) => + TeamId -> + Sem r (LockableFeature cfg) +getFeatureInternalImpl tid = do + TeamSubsystem.assertTeamExists tid + getFeatureForTeamImpl tid + +getTeamAndCheckMembership :: + ( Member TeamStore.TeamStore r, + Member (ErrorS 'NotATeamMember) r, + Member TeamSubsystem r + ) => + UserId -> + Sem r (Maybe TeamId) +getTeamAndCheckMembership uid = do + mTid <- TeamStore.getOneUserTeam uid + for_ mTid $ \tid -> do + zusrMembership <- TeamSubsystem.internalGetTeamMember uid tid + void $ maybe (throwS @'NotATeamMember) pure zusrMembership + TeamSubsystem.assertTeamExists tid + pure mTid + +getAllTeamFeatures :: + forall r. + ( Member TeamFeatureStore r, + Member LegalHoldStore r, + Member BrigAPIAccess r, + Member (Input FeatureFlags) r, + Member (Input (FeatureDefaults LegalholdConfig)) r, + Member (Input ExposeInvitationURLsAllowlist) r, + Member (Error TeamFeatureStoreError) r + ) => + TeamId -> + Sem r AllTeamFeatures +getAllTeamFeatures tid = getAllTeamFeaturesImpl tid + +getAllTeamFeaturesForUserImpl :: + forall r. + ( Member (ErrorS 'NotATeamMember) r, + Member TeamStore.TeamStore r, + Member TeamSubsystem r, + Member TeamFeatureStore r, + Member (Error TeamFeatureStoreError) r, + GetFeatureConfigEffects r + ) => + UserId -> + Sem r AllTeamFeatures +getAllTeamFeaturesForUserImpl uid = do + mTid <- getTeamAndCheckMembership uid + case mTid of + Nothing -> hsequence' $ hcpure (Proxy @(GetAllTeamFeaturesForUserConstraints r)) $ Comp $ getFeatureForUser uid + Just tid -> getAllTeamFeatures tid + +getSingleFeatureForUserImpl :: + forall cfg r. + ( GetFeatureConfig cfg, + Member (ErrorS 'NotATeamMember) r, + Member (Error TeamFeatureStoreError) r, + Member TeamStore.TeamStore r, + Member TeamSubsystem r, + Member TeamFeatureStore r, + Member BrigAPIAccess r, + Member LegalHoldStore r, + Member (Input FeatureFlags) r, + Member (Input (FeatureDefaults LegalholdConfig)) r, + Member (Input ExposeInvitationURLsAllowlist) r + ) => + UserId -> + Sem r (LockableFeature cfg) +getSingleFeatureForUserImpl uid = do + mTid <- getTeamAndCheckMembership uid + getFeatureForTeamUserImpl @cfg uid mTid + +-- | If second factor auth is enabled, make sure that end-points that don't support it, but +-- should, are blocked completely. (This is a workaround until we have 2FA for those +-- end-points as well.) +-- +-- This function exists to resolve a cyclic dependency. +guardSecondFactorDisabledImpl :: + forall r. + ( Member (ErrorS 'AccessDenied) r, + Member (Error TeamFeatureStoreError) r, + Member ConversationStore.ConversationStore r, + Member TeamSubsystem r, + Member TeamFeatureStore r, + Member BrigAPIAccess r, + Member LegalHoldStore r, + Member (Input FeatureFlags) r, + Member (Input (FeatureDefaults LegalholdConfig)) r, + Member (Input ExposeInvitationURLsAllowlist) r + ) => + UserId -> + ConvId -> + Sem r () +guardSecondFactorDisabledImpl uid cid = do + mTid <- fmap hush . runError @() $ do + convData <- ConversationStore.getConversationMetadata cid >>= note () + tid <- note () convData.cnvmTeam + mapError (unTagged @'TeamNotFound @()) $ TeamSubsystem.assertTeamExists tid + pure tid + + tf <- getFeatureForTeamUserImpl @SndFactorPasswordChallengeConfig uid mTid + case tf.status of + FeatureStatusDisabled -> pure () + FeatureStatusEnabled -> throwS @'AccessDenied + +featureEnabledForTeamImpl :: + forall cfg r. + ( GetFeatureConfig cfg, + Member TeamSubsystem r, + Member TeamFeatureStore r, + Member LegalHoldStore r, + Member BrigAPIAccess r, + Member (Input FeatureFlags) r, + Member (Input (FeatureDefaults LegalholdConfig)) r, + Member (Input ExposeInvitationURLsAllowlist) r, + Member (Error TeamFeatureStoreError) r + ) => + TeamId -> + Sem r Bool +featureEnabledForTeamImpl tid = + (==) FeatureStatusEnabled + . (.status) + <$> getFeatureInternalImpl @cfg tid diff --git a/libs/wire-subsystems/src/Wire/TeamSubsystem.hs b/libs/wire-subsystems/src/Wire/TeamSubsystem.hs index 7c7213aee72..cd4fa9a7cac 100644 --- a/libs/wire-subsystems/src/Wire/TeamSubsystem.hs +++ b/libs/wire-subsystems/src/Wire/TeamSubsystem.hs @@ -20,13 +20,30 @@ module Wire.TeamSubsystem where import Data.Id +import Data.LegalHold +import Data.Map qualified as Map import Data.Qualified import Data.Range +import Data.Singletons (Demote, Sing, SingKind, fromSing) import Imports import Polysemy +import Wire.API.Error +import Wire.API.Error.Galley +import Wire.API.Team.LegalHold (UserLegalHoldStatusResponse) import Wire.API.Team.Member +import Wire.API.Team.Member.Error import Wire.API.Team.Member.Info (TeamMemberInfoList) +data PermissionCheckArgs teamAssociation where + PermissionCheckArgs :: + forall k (p :: k) teamAssociation. + ( SingKind k, + IsPerm teamAssociation (Demote k) + ) => + Sing p -> + Maybe teamAssociation -> + PermissionCheckArgs teamAssociation + data TeamSubsystem m a where InternalGetTeamMember :: UserId -> TeamId -> TeamSubsystem m (Maybe TeamMember) InternalGetTeamMembersWithLimit :: TeamId -> Maybe (Range 1 HardTruncationLimit Int32) -> TeamSubsystem m TeamMemberList @@ -35,5 +52,95 @@ data TeamSubsystem m a where InternalGetTeamAdmins :: TeamId -> TeamSubsystem m TeamMemberList InternalGetOneUserTeam :: UserId -> TeamSubsystem m (Maybe TeamId) InternalFinalizeDeleteTeam :: Local UserId -> Maybe ConnId -> TeamId -> TeamSubsystem m () + GetUserStatus :: + Local UserId -> + TeamId -> + UserId -> + TeamSubsystem m UserLegalHoldStatusResponse + GetTeamMembersForFanout :: + TeamId -> + TeamSubsystem m TeamMemberList + AssertTeamExists :: + TeamId -> + TeamSubsystem m () + GetLHStatusForUsers :: + [UserId] -> + TeamSubsystem m [(UserId, UserLegalHoldStatus)] + GetLHStatus :: + Maybe TeamId -> + UserId -> + TeamSubsystem m UserLegalHoldStatus makeSem ''TeamSubsystem + +assertOnTeam :: + ( Member (ErrorS 'NotATeamMember) r, + Member TeamSubsystem r + ) => + UserId -> + TeamId -> + Sem r () +assertOnTeam uid tid = + internalGetTeamMember uid tid >>= \case + Nothing -> throwS @'NotATeamMember + Just _ -> pure () + +-- | If a team member is not given throw 'notATeamMember'; if the given team +-- member does not have the given permission, throw 'operationDenied'. +-- Otherwise, return the team member. +permissionCheck :: + ( IsPerm teamAssociation perm, + ( Member (ErrorS OperationDenied) r, + Member (ErrorS 'NotATeamMember) r + ) + ) => + perm -> + Maybe teamAssociation -> + Sem r teamAssociation +-- FUTUREWORK: factor `noteS` out of this function. +permissionCheck p = \case + Just m -> do + if m `hasPermission` p + then pure m + else throwS @OperationDenied + -- FUTUREWORK: factor `noteS` out of this function. + Nothing -> throwS @'NotATeamMember + +-- | Same as 'permissionCheck', but for a statically known permission. +permissionCheckS :: + forall teamAssociation perm (p :: perm) r. + ( SingKind perm, + IsPerm teamAssociation (Demote perm), + ( Member (ErrorS (PermError p)) r, + Member (ErrorS 'NotATeamMember) r + ) + ) => + Sing p -> + Maybe teamAssociation -> + Sem r teamAssociation +permissionCheckS p = + \case + Just m -> do + if m `hasPermission` fromSing p + then pure m + else throwS @(PermError p) + -- FUTUREWORK: factor `noteS` out of this function. + Nothing -> throwS @'NotATeamMember + +data ConsentGiven = ConsentGiven | ConsentNotGiven + deriving (Eq, Ord, Show) + +consentGiven :: UserLegalHoldStatus -> ConsentGiven +consentGiven = \case + UserLegalHoldDisabled -> ConsentGiven + UserLegalHoldPending -> ConsentGiven + UserLegalHoldEnabled -> ConsentGiven + UserLegalHoldNoConsent -> ConsentNotGiven + +checkConsent :: + (Member TeamSubsystem r) => + Map UserId TeamId -> + UserId -> + Sem r ConsentGiven +checkConsent teamsOfUsers other = do + consentGiven <$> getLHStatus (Map.lookup other teamsOfUsers) other diff --git a/libs/wire-subsystems/src/Wire/TeamSubsystem/GalleyAPI.hs b/libs/wire-subsystems/src/Wire/TeamSubsystem/GalleyAPI.hs index 0f4e4342cc7..52cfb0da574 100644 --- a/libs/wire-subsystems/src/Wire/TeamSubsystem/GalleyAPI.hs +++ b/libs/wire-subsystems/src/Wire/TeamSubsystem/GalleyAPI.hs @@ -17,13 +17,22 @@ module Wire.TeamSubsystem.GalleyAPI where +import Data.LegalHold (UserLegalHoldStatus (..)) import Imports import Polysemy +import Wire.API.Error +import Wire.API.Error.Galley +import Wire.API.Team.Feature (FeatureStatus (FeatureStatusEnabled), LockableFeature (..)) import Wire.GalleyAPIAccess (GalleyAPIAccess) import Wire.GalleyAPIAccess qualified as GalleyAPIAccess import Wire.TeamSubsystem -interpretTeamSubsystemToGalleyAPI :: (Member GalleyAPIAccess r) => InterpreterFor TeamSubsystem r +interpretTeamSubsystemToGalleyAPI :: + ( Member GalleyAPIAccess r, + Member (ErrorS TeamMemberNotFound) r, + Member (ErrorS TeamNotFound) r + ) => + InterpreterFor TeamSubsystem r interpretTeamSubsystemToGalleyAPI = interpret $ \case InternalGetTeamMember userId teamId -> GalleyAPIAccess.getTeamMember userId teamId InternalGetTeamMembersWithLimit teamId maxResults -> GalleyAPIAccess.getTeamMembersWithLimit teamId maxResults @@ -32,3 +41,41 @@ interpretTeamSubsystemToGalleyAPI = interpret $ \case InternalGetTeamAdmins teamId -> GalleyAPIAccess.getTeamAdmins teamId InternalGetOneUserTeam userId -> GalleyAPIAccess.getTeamId userId InternalFinalizeDeleteTeam lusr mcon teamId -> GalleyAPIAccess.finalizeDeleteTeam lusr mcon teamId + GetUserStatus lusr tid uid -> do + GalleyAPIAccess.getTeamMember uid tid >>= \case + Nothing -> throwS @'TeamMemberNotFound + Just _ -> do + GalleyAPIAccess.getUserLegalholdStatus lusr tid >>= \case + Nothing -> throwS @'TeamNotFound + Just status -> pure status + GetTeamMembersForFanout tid -> + GalleyAPIAccess.getTeamMembersWithLimit tid Nothing + AssertTeamExists tid -> do + void $ GalleyAPIAccess.getTeam tid + GetLHStatusForUsers uids -> do + for uids $ \uid -> do + mteamId <- GalleyAPIAccess.getTeamId uid + status <- case mteamId of + Nothing -> pure UserLegalHoldDisabled + Just tid -> do + GalleyAPIAccess.getTeamMember uid tid >>= \case + Nothing -> pure UserLegalHoldDisabled + Just _ -> do + LockableFeature {status} <- GalleyAPIAccess.getTeamLegalHoldStatus tid + pure $ + if status == FeatureStatusEnabled + then UserLegalHoldEnabled + else UserLegalHoldDisabled + pure (uid, status) + GetLHStatus mtid uid -> do + case mtid of + Nothing -> pure UserLegalHoldDisabled + Just tid -> do + GalleyAPIAccess.getTeamMember uid tid >>= \case + Nothing -> pure UserLegalHoldDisabled + Just _ -> do + LockableFeature {status} <- GalleyAPIAccess.getTeamLegalHoldStatus tid + pure $ + if status == FeatureStatusEnabled + then UserLegalHoldEnabled + else UserLegalHoldDisabled diff --git a/libs/wire-subsystems/src/Wire/TeamSubsystem/Interpreter.hs b/libs/wire-subsystems/src/Wire/TeamSubsystem/Interpreter.hs index 749bfd978d3..f598ec35d74 100644 --- a/libs/wire-subsystems/src/Wire/TeamSubsystem/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/TeamSubsystem/Interpreter.hs @@ -18,22 +18,34 @@ module Wire.TeamSubsystem.Interpreter where import Control.Lens (view, (%~), (^.)) +import Data.ByteString.Conversion (toByteString') import Data.Default import Data.Id import Data.Json.Util -import Data.LegalHold (UserLegalHoldStatus (..)) +import Data.LegalHold (UserLegalHoldStatus (..), defUserLegalHoldStatus) import Data.List.Extra qualified as List import Data.List.NonEmpty (NonEmpty ((:|))) +import Data.Map qualified as Map import Data.Qualified import Data.Time +import Galley.Types.Error import Imports import Polysemy +import Polysemy.Error import Polysemy.Input +import Polysemy.TinyLog qualified as P +import System.Logger.Class qualified as Log +import Wire.API.Error +import Wire.API.Error.Galley import Wire.API.Event.Conversation qualified as Conv import Wire.API.Event.Team +import Wire.API.Team.FeatureFlags (FanoutLimit) import Wire.API.Team.HardTruncationLimit +import Wire.API.Team.LegalHold +import Wire.API.Team.LegalHold qualified as Public import Wire.API.Team.Member import Wire.API.Team.Member.Info (TeamMemberInfoList (TeamMemberInfoList)) +import Wire.API.User.Client.Prekey import Wire.BrigAPIAccess import Wire.BrigAPIAccess qualified as Brig import Wire.ConversationStore @@ -42,6 +54,7 @@ import Wire.ExternalAccess import Wire.ExternalAccess qualified as ExternalAccess import Wire.LegalHoldStore (LegalHoldStore) import Wire.LegalHoldStore qualified as LH +import Wire.LegalHoldStore qualified as LegalHoldData import Wire.NotificationSubsystem import Wire.Sem.Now import Wire.Sem.Now qualified as Now @@ -65,7 +78,12 @@ interpretTeamSubsystem :: Member Now r, Member SparAPIAccess r, Member ConversationStore r, - Member TeamJournal r + Member TeamJournal r, + Member (Input FanoutLimit) r, + Member (Error InternalError) r, + Member (ErrorS 'TeamNotFound) r, + Member (ErrorS 'TeamMemberNotFound) r, + Member P.TinyLog r ) => TeamSubsystemConfig -> InterpreterFor TeamSubsystem r @@ -79,23 +97,23 @@ interpretTeamSubsystemWithInputConfig :: Member ExternalAccess r, Member NotificationSubsystem r, Member (Input TeamSubsystemConfig) r, + Member (Input FanoutLimit) r, Member Now r, Member SparAPIAccess r, Member ConversationStore r, - Member TeamJournal r + Member TeamJournal r, + Member (Error InternalError) r, + Member (ErrorS 'TeamNotFound) r, + Member (ErrorS 'TeamMemberNotFound) r, + Member P.TinyLog r ) => InterpreterFor TeamSubsystem r interpretTeamSubsystemWithInputConfig = interpret $ \case - InternalGetTeamMember uid tid -> do - tms <- TeamStore.getTeamMember tid uid - for tms $ \tm -> do - hasImplicitConsent <- LH.isTeamLegalholdWhitelisted tid - pure $ if hasImplicitConsent then grantImplicitConsent tm else tm - InternalGetTeamMembersWithLimit tid maxResults -> do - tmList <- TeamStore.getTeamMembersWithLimit tid (fromMaybe hardTruncationLimitRange maxResults) - ms <- adjustMembersForImplicitConsent tid (tmList ^. teamMembers) - pure $ newTeamMemberList ms (tmList ^. teamMemberListType) + InternalGetTeamMember uid tid -> + internalGetTeamMemberImpl uid tid + InternalGetTeamMembersWithLimit tid maxResults -> + internalGetTeamMembersWithLimitImpl tid maxResults InternalSelectTeamMemberInfos tid uids -> TeamMemberInfoList <$> TeamStore.selectTeamMemberInfos tid uids InternalSelectTeamMembers tid uids -> do tms <- TeamStore.selectTeamMembers tid uids @@ -106,9 +124,20 @@ interpretTeamSubsystemWithInputConfig = >>= TeamStore.selectTeamMembers tid >>= adjustMembersForImplicitConsent tid pure $ newTeamMemberList admins ListComplete - InternalGetOneUserTeam uid -> TeamStore.getOneUserTeam uid + InternalGetOneUserTeam uid -> + TeamStore.getOneUserTeam uid InternalFinalizeDeleteTeam luid mcon tid -> internalFinalizeDeleteTeamImpl luid mcon tid + GetUserStatus lzusr tid uid -> + getUserStatusImpl lzusr tid uid + AssertTeamExists tid -> + assertTeamExistsImpl tid + GetTeamMembersForFanout tid -> + getTeamMembersForFanoutImpl tid + GetLHStatus teamOfUser other -> + getLHStatusImpl teamOfUser other + GetLHStatusForUsers uids -> + getLHStatusForUsersImpl uids adjustMembersForImplicitConsent :: (Member LegalHoldStore r) => TeamId -> [TeamMember] -> Sem r [TeamMember] adjustMembersForImplicitConsent tid ms = do @@ -123,6 +152,31 @@ grantImplicitConsent = UserLegalHoldPending -> UserLegalHoldPending UserLegalHoldEnabled -> UserLegalHoldEnabled +internalGetTeamMemberImpl :: + ( Member TeamStore r, + Member LegalHoldStore r + ) => + UserId -> + TeamId -> + Sem r (Maybe TeamMember) +internalGetTeamMemberImpl uid tid = do + tms <- TeamStore.getTeamMember tid uid + for tms $ \tm -> do + hasImplicitConsent <- LH.isTeamLegalholdWhitelisted tid + pure $ if hasImplicitConsent then grantImplicitConsent tm else tm + +internalGetTeamMembersWithLimitImpl :: + ( Member TeamStore r, + Member LegalHoldStore r + ) => + TeamId -> + Maybe FanoutLimit -> + Sem r TeamMemberList +internalGetTeamMembersWithLimitImpl tid maxResults = do + tmList <- TeamStore.getTeamMembersWithLimit tid (fromMaybe hardTruncationLimitRange maxResults) + ms <- adjustMembersForImplicitConsent tid (tmList ^. teamMembers) + pure $ newTeamMemberList ms (tmList ^. teamMemberListType) + -- This function is "unchecked" because it does not validate that the user has the `DeleteTeam` permission. internalFinalizeDeleteTeamImpl :: forall r. @@ -204,3 +258,98 @@ internalFinalizeDeleteTeamImpl lusr zcon tid = do let ee' = map (,e) bots let pp' = (p {conn = zcon}) : pp pure (pp', ee' ++ ee) + +-- | Learn whether a user has LH enabled and fetch pre-keys. +-- Note that this is accessible to ANY authenticated user, even ones outside the team +getUserStatusImpl :: + forall r. + ( Member (Error InternalError) r, + Member (ErrorS 'TeamMemberNotFound) r, + Member LegalHoldData.LegalHoldStore r, + Member TeamStore r, + Member P.TinyLog r + ) => + Local UserId -> + TeamId -> + UserId -> + Sem r Public.UserLegalHoldStatusResponse +getUserStatusImpl _lzusr tid uid = do + teamMember <- noteS @'TeamMemberNotFound =<< internalGetTeamMemberImpl uid tid + let status = view legalHoldStatus teamMember + (mlk, lcid) <- case status of + UserLegalHoldNoConsent -> pure (Nothing, Nothing) + UserLegalHoldDisabled -> pure (Nothing, Nothing) + UserLegalHoldPending -> makeResponseDetails + UserLegalHoldEnabled -> makeResponseDetails + pure $ UserLegalHoldStatusResponse status mlk lcid + where + makeResponseDetails :: Sem r (Maybe LastPrekey, Maybe ClientId) + makeResponseDetails = do + mLastKey <- fmap snd <$> LegalHoldData.selectPendingPrekeys uid + lastKey <- case mLastKey of + Nothing -> do + P.err + . Log.msg + $ "expected to find a prekey for user: " + <> toByteString' uid + <> " but none was found" + throw NoPrekeyForUser + Just lstKey -> pure lstKey + let clientId = clientIdFromPrekey . unpackLastPrekey $ lastKey + pure (Just lastKey, Just clientId) + +assertTeamExistsImpl :: + ( Member (ErrorS 'TeamNotFound) r, + Member TeamStore r + ) => + TeamId -> + Sem r () +assertTeamExistsImpl tid = do + teamExists <- isJust <$> TeamStore.getTeam tid + if teamExists + then pure () + else throwS @'TeamNotFound + +getTeamMembersForFanoutImpl :: + ( Member TeamStore r, + Member LegalHoldStore r, + Member (Input FanoutLimit) r + ) => + TeamId -> + Sem r TeamMemberList +getTeamMembersForFanoutImpl tid = do + lim <- input + internalGetTeamMembersWithLimitImpl tid (Just lim) + +-- Get legalhold status of user. Defaults to 'defUserLegalHoldStatus' if user +-- doesn't belong to a team. +getLHStatusImpl :: + ( Member TeamStore r, + Member LegalHoldStore r + ) => + Maybe TeamId -> + UserId -> + Sem r UserLegalHoldStatus +getLHStatusImpl teamOfUser other = do + case teamOfUser of + Nothing -> pure defUserLegalHoldStatus + Just team -> do + mMember <- internalGetTeamMemberImpl other team + pure $ maybe defUserLegalHoldStatus (view legalHoldStatus) mMember + +-- | Add to every uid the legalhold status +getLHStatusForUsersImpl :: + ( Member TeamStore r, + Member LegalHoldStore r + ) => + [UserId] -> + Sem r [(UserId, UserLegalHoldStatus)] +getLHStatusForUsersImpl uids = + mconcat + <$> for + (List.chunksOf 32 uids) + ( \uidsChunk -> do + teamsOfUsers <- TeamStore.getUsersTeams uidsChunk + for uidsChunk $ \uid -> do + (uid,) <$> getLHStatusImpl (Map.lookup uid teamsOfUsers) uid + ) diff --git a/libs/wire-subsystems/src/Wire/UserClientIndexStore.hs b/libs/wire-subsystems/src/Wire/UserClientIndexStore.hs index f0bde2b2248..6946642238f 100644 --- a/libs/wire-subsystems/src/Wire/UserClientIndexStore.hs +++ b/libs/wire-subsystems/src/Wire/UserClientIndexStore.hs @@ -30,12 +30,36 @@ module Wire.UserClientIndexStore -- * Delete client deleteClient, deleteClients, + + -- * Helpers + internalGetClientIds, + rmClient, + getClientsId, ) where +import Data.Domain (Domain) import Data.Id +import Data.Proxy (Proxy (..)) +import Data.Qualified +import Data.Range import Galley.Types.Clients +import Imports +import Network.AMQP qualified as Q import Polysemy +import Polysemy.Error +import Polysemy.Input +import Polysemy.TinyLog qualified as P +import System.Logger.Message +import Wire.API.Conversation hiding (Member) +import Wire.API.Conversation.Config (ConversationSubsystemConfig (..)) +import Wire.API.Federation.API +import Wire.API.Federation.API.Galley +import Wire.API.Federation.Error +import Wire.API.Routes.MultiTablePaging +import Wire.BackendNotificationQueueAccess +import Wire.BrigAPIAccess +import Wire.ConversationSubsystem qualified as ConversationSubsystem data UserClientIndexStore m a where GetClients :: [UserId] -> UserClientIndexStore m Clients @@ -44,3 +68,84 @@ data UserClientIndexStore m a where DeleteClients :: UserId -> UserClientIndexStore m () makeSem ''UserClientIndexStore + +internalGetClientIds :: + ( Member BrigAPIAccess r, + Member UserClientIndexStore r, + Member (Input ConversationSubsystemConfig) r + ) => + [UserId] -> + Sem r Clients +internalGetClientIds users = do + cfg <- input + let isInternal = cfg.listClientsUsingBrig + if isInternal + then fromUserClients <$> lookupClients users + else getClients users + +rmClient :: + forall r. + ( Member UserClientIndexStore r, + Member ConversationSubsystem.ConversationSubsystem r, + Member (Error FederationError) r, + Member BackendNotificationQueueAccess r, + Member (Input (Local ())) r, + Member P.TinyLog r + ) => + UserId -> + ClientId -> + Sem r () +rmClient usr cid = do + clients <- getClients [usr] + if (cid `elem` clientIds usr clients) + then do + lusr <- qualifyLocal usr + let nRange1000 = toRange (Proxy @1000) :: Range 1 1000 Int32 + firstConvIds <- ConversationSubsystem.conversationIdsPageFrom lusr (GetPaginatedConversationIds Nothing nRange1000) + goConvs nRange1000 firstConvIds lusr + deleteClient usr cid + else + P.debug + ( field "user" (idToText usr) + . field "client" (clientToText cid) + . msg (val "rmClientH: client already gone") + ) + where + goConvs :: Range 1 1000 Int32 -> ConvIdsPage -> Local UserId -> Sem r () + goConvs range page lusr = do + let (localConvs, remoteConvs) = partitionQualified lusr (mtpResults page) + for_ localConvs $ \convId -> do + mConv <- ConversationSubsystem.internalGetConversation convId + for_ mConv $ \conv -> do + lconv <- qualifyLocal conv + ConversationSubsystem.removeClient lconv (tUntagged lusr) cid + traverse_ removeRemoteMLSClients (rangedChunks remoteConvs) + when (mtpHasMore page) $ do + let nextState = mtpPagingState page + nextQuery = GetPaginatedConversationIds (Just nextState) range + newCids <- ConversationSubsystem.conversationIdsPageFrom lusr nextQuery + goConvs range newCids lusr + + removeRemoteMLSClients :: Range 1 1000 [Remote ConvId] -> Sem r () + removeRemoteMLSClients convIds = do + for_ (bucketRemote (fromRange convIds)) $ \remoteConvs -> + let rpc = + fedQueueClient + @'OnClientRemovedTag + (ClientRemovedRequest usr cid (tUnqualified remoteConvs)) + in enqueueNotification Q.Persistent remoteConvs rpc + +getClientsId :: + ( Member BrigAPIAccess r, + Member UserClientIndexStore r, + Member (Input ConversationSubsystemConfig) r + ) => + UserId -> + Sem r [ClientId] +getClientsId usr = clientIds usr <$> internalGetClientIds [usr] + +qualifyLocal :: (Member (Input (Local ())) r) => a -> Sem r (Local a) +qualifyLocal a = toLocalUnsafe <$> fmap getDomain input <*> pure a + where + getDomain :: Local () -> Domain + getDomain = tDomain diff --git a/libs/wire-subsystems/test/unit/Wire/MeetingsSubsystem/InterpreterSpec.hs b/libs/wire-subsystems/test/unit/Wire/MeetingsSubsystem/InterpreterSpec.hs index 5c45b433bc6..78ff882c602 100644 --- a/libs/wire-subsystems/test/unit/Wire/MeetingsSubsystem/InterpreterSpec.hs +++ b/libs/wire-subsystems/test/unit/Wire/MeetingsSubsystem/InterpreterSpec.hs @@ -25,6 +25,7 @@ import Data.Map qualified as Map import Data.Qualified import Data.Range (checked, unsafeRange) import Data.Set qualified as Set +import Data.Tagged (Tagged) import Data.Time.Calendar (fromGregorian) import Data.Time.Clock import Imports @@ -36,6 +37,8 @@ import Test.Hspec import Test.Hspec.QuickCheck (prop) import Test.QuickCheck (counterexample, ioProperty, (.&&.), (===), (==>)) import Text.Email.Parser (unsafeEmailAddress) +import Wire.API.Error (ErrorS) +import Wire.API.Error.Galley (GalleyError (TeamMemberNotFound, TeamNotFound)) import Wire.API.Meeting qualified as API import Wire.API.Team.Feature import Wire.API.Team.Member (TeamMember, mkTeamMember) @@ -68,6 +71,8 @@ type TestStack = State UTCTime, Random, State StdGen, + ErrorS 'TeamMemberNotFound, + ErrorS 'TeamNotFound, Embed IO ] @@ -81,6 +86,11 @@ interpretFeaturesConfigSubsystemPure configs = interpret $ \case GetAllTeamFeaturesForTeamMember _luid _tid -> pure def GetAllTeamFeaturesForTeam _tid -> pure def GetAllTeamFeaturesForServer -> pure def + GuardSecondFactorDisabled _ _ -> error "not implemented" + FeatureEnabledForTeam _ _ -> error "not implemented" + GetAllTeamFeaturesForUser _ -> error "not implemented" + GetSingleFeatureForUser _ -> error "not implemented" + GetFeatureInternal _ -> error "not implemented" runTestStack :: UTCTime -> @@ -91,6 +101,9 @@ runTestStack :: IO (Either MeetingError a) runTestStack now gen teams configs = runM + . fmap (either (error . show) (either (error . show) Imports.id)) + . runError @(Tagged 'TeamNotFound ()) + . runError @(Tagged 'TeamMemberNotFound ()) . evalState gen . randomToStatefulStdGen . evalState now diff --git a/libs/wire-subsystems/test/unit/Wire/MiniBackend.hs b/libs/wire-subsystems/test/unit/Wire/MiniBackend.hs index d64f1e10b5b..6b45b3984c6 100644 --- a/libs/wire-subsystems/test/unit/Wire/MiniBackend.hs +++ b/libs/wire-subsystems/test/unit/Wire/MiniBackend.hs @@ -59,6 +59,7 @@ import Data.Map.Lazy qualified as LM import Data.Map.Strict qualified as M import Data.Proxy import Data.Qualified +import Data.Tagged (Tagged) import Data.Time import Data.Type.Equality import Data.Vector qualified as Vector @@ -77,6 +78,9 @@ import System.Logger qualified as Log import Test.QuickCheck import Type.Reflection import Wire.API.Allowlists (AllowlistEmailDomains) +import Wire.API.Conversation.Config (ConversationSubsystemConfig (..)) +import Wire.API.Error (ErrorS) +import Wire.API.Error.Galley (GalleyError (TeamMemberNotFound, TeamNotFound)) import Wire.API.Federation.API import Wire.API.Federation.Component import Wire.API.Federation.Error @@ -96,10 +100,13 @@ import Wire.AuthenticationSubsystem import Wire.AuthenticationSubsystem.Config import Wire.AuthenticationSubsystem.Cookie.Limit import Wire.AuthenticationSubsystem.Interpreter +import Wire.BackendNotificationQueueAccess (BackendNotificationQueueAccess) import Wire.BlockListStore +import Wire.BrigAPIAccess (BrigAPIAccess) import Wire.ClientStore import Wire.ClientSubsystem import Wire.ClientSubsystem.Interpreter +import Wire.ConversationSubsystem (ConversationSubsystem) import Wire.DeleteQueue import Wire.DeleteQueue.InMemory import Wire.DomainRegistrationStore qualified as DRS @@ -132,6 +139,7 @@ import Wire.TeamCollaboratorsSubsystem import Wire.TeamCollaboratorsSubsystem.Interpreter import Wire.TeamSubsystem (TeamSubsystem) import Wire.TeamSubsystem.GalleyAPI +import Wire.UserClientIndexStore (UserClientIndexStore) import Wire.UserGroupStore (UserGroupStore) import Wire.UserKeyStore import Wire.UserStore @@ -242,7 +250,8 @@ data MiniBackendParams r = MiniBackendParams teams :: Map TeamId [TeamMember], galleyConfigs :: AllTeamFeatures, usrCfg :: UserSubsystemConfig, - appCfg :: AppSubsystemConfig + appCfg :: AppSubsystemConfig, + conversationCfg :: ConversationSubsystemConfig } -- | `MiniBackendLowerEffects` is not a long, flat list, but a tree of effects. This way we @@ -253,7 +262,13 @@ data MiniBackendParams r = MiniBackendParams -- organize along effect types ("all `State`s"), but the domain ("everything about block -- lists"). type MiniBackendLowerEffects = - '[ TeamSubsystem, + '[ ClientSubsystem, + Input ConversationSubsystemConfig, + BrigAPIAccess, + UserClientIndexStore, + BackendNotificationQueueAccess, + ConversationSubsystem, + TeamSubsystem, EmailSubsystem, NotificationSubsystem, VerificationCodeSubsystem, @@ -281,7 +296,10 @@ type MiniBackendLowerEffects = Events, CryptoSign, Random, - Now + Now, + ErrorS 'TeamMemberNotFound, + ErrorS 'TeamNotFound, + Error FederationError ] `Append` InputEffects `Append` '[ Metrics @@ -305,6 +323,12 @@ miniBackendLowerEffectsInterpreters mb@(MiniBackendParams {..}) = . stateEffectsInterpreters mb . ignoreMetrics . inputEffectsInterpreters usrCfg appCfg localBackend.teamIdps + . fmap (either (error . show) Imports.id) + . runError @FederationError + . fmap (either (error . show) Imports.id) + . runError @(Tagged 'TeamNotFound ()) + . fmap (either (error . show) Imports.id) + . runError @(Tagged 'TeamMemberNotFound ()) . interpretNowConst (UTCTime (ModifiedJulianDay 0) 0) . runRandomPure . runCryptoSignUnsafe @@ -334,6 +358,29 @@ miniBackendLowerEffectsInterpreters mb@(MiniBackendParams {..}) = . inMemoryNotificationSubsystemInterpreter . noopEmailSubsystemInterpreter . interpretTeamSubsystemToGalleyAPI + . mockConversationSubsystem + . mockBackendNotificationQueueAccess + . mockUserClientIndexStore + . mockBrigAPIAccess + . runInputConst conversationCfg + . runClientSubsystem + where + -- Mock BrigAPIAccess interpreter for tests + mockBrigAPIAccess :: forall r'. InterpreterFor BrigAPIAccess r' + mockBrigAPIAccess = interpret $ \case + _ -> error "Unimplemented BrigAPIAccess operation in mock" + -- Mock UserClientIndexStore interpreter for tests + mockUserClientIndexStore :: forall r'. InterpreterFor UserClientIndexStore r' + mockUserClientIndexStore = interpret $ \case + _ -> error "Unimplemented UserClientIndexStore operation in mock" + -- Mock BackendNotificationQueueAccess interpreter for tests + mockBackendNotificationQueueAccess :: forall r'. InterpreterFor BackendNotificationQueueAccess r' + mockBackendNotificationQueueAccess = interpret $ \case + _ -> error "Unimplemented BackendNotificationQueueAccess operation in mock" + -- Mock ConversationSubsystem interpreter for tests + mockConversationSubsystem :: forall r'. InterpreterFor ConversationSubsystem r' + mockConversationSubsystem = interpretH $ \case + _ -> error "Unimplemented ConversationSubsystem operation in mock" type StateEffects = '[ State [Push], @@ -643,6 +690,14 @@ interpretFederationStackState localBackend backends teams usrCfg = localBackend = localBackend, galleyConfigs = def, appCfg = def, + conversationCfg = + ConversationSubsystemConfig + { listClientsUsingBrig = False, + legalholdDefaults = def, + mlsKeys = Nothing, + maxConvSize = 10, + federationProtocols = Nothing + }, .. } @@ -705,6 +760,14 @@ interpretNoFederationStackState localBackend teams galleyConfigs usrCfg = localBackend = localBackend, galleyConfigs = galleyConfigs, appCfg = def, + conversationCfg = + ConversationSubsystemConfig + { listClientsUsingBrig = False, + legalholdDefaults = def, + mlsKeys = Nothing, + maxConvSize = 10, + federationProtocols = Nothing + }, .. } diff --git a/libs/wire-subsystems/test/unit/Wire/SAMLEmailSubsystem/InterpreterSpec.hs b/libs/wire-subsystems/test/unit/Wire/SAMLEmailSubsystem/InterpreterSpec.hs index 31ebb431d53..fba4f321bd2 100644 --- a/libs/wire-subsystems/test/unit/Wire/SAMLEmailSubsystem/InterpreterSpec.hs +++ b/libs/wire-subsystems/test/unit/Wire/SAMLEmailSubsystem/InterpreterSpec.hs @@ -6,6 +6,7 @@ import Data.LegalHold (UserLegalHoldStatus (..)) import Data.List.NonEmpty qualified as NE import Data.Map qualified as Map import Data.Set qualified as Set +import Data.Tagged (Tagged) import Data.Text.Lazy qualified as TL import Data.Text.Lazy.Encoding (decodeUtf8) import Data.Text.Lazy.IO qualified as TL @@ -14,6 +15,7 @@ import Data.X509.CertificateStore qualified as X509 import Imports import Network.Mail.Mime (Address (..), Mail (..), Part (..), PartContent (..)) import Polysemy +import Polysemy.Error (runError) import Polysemy.State import SAML2.WebSSO import System.FilePath @@ -23,6 +25,8 @@ import Test.Hspec.QuickCheck import Test.QuickCheck import Text.Email.Parser (unsafeEmailAddress) import URI.ByteString +import Wire.API.Error (ErrorS) +import Wire.API.Error.Galley (GalleyError (TeamMemberNotFound, TeamNotFound)) import Wire.API.Locale import Wire.API.Password import Wire.API.Routes.Internal.Brig (IdpChangedNotification (..)) @@ -347,6 +351,8 @@ runInterpreters :: Logger (Logger.Msg -> Logger.Msg), EmailSending, State [Mail], + ErrorS 'TeamMemberNotFound, + ErrorS 'TeamNotFound, Embed IO ] a -> @@ -355,6 +361,9 @@ runInterpreters users teamMap teamTemplates branding action = do lr <- newLogRecorder (mails, res) <- runM + . fmap (either (error . show) (either (error . show) Imports.id)) + . runError @(Tagged 'TeamNotFound ()) + . runError @(Tagged 'TeamMemberNotFound ()) . runState @[Mail] [] -- Use runState to capture and return the Mail state . recordingEmailSendingInterpreter . recordLogs lr diff --git a/libs/wire-subsystems/test/unit/Wire/ScimSubsystem/InterpreterSpec.hs b/libs/wire-subsystems/test/unit/Wire/ScimSubsystem/InterpreterSpec.hs index 3cdf8408a41..6861f097797 100644 --- a/libs/wire-subsystems/test/unit/Wire/ScimSubsystem/InterpreterSpec.hs +++ b/libs/wire-subsystems/test/unit/Wire/ScimSubsystem/InterpreterSpec.hs @@ -75,10 +75,10 @@ runDependenciesSafe :: [StoredUser] -> Map TeamId [TeamMember] -> Sem AllDependencies a -> - Either UGS.UserGroupSubsystemError (Either ScimSubsystemError a) + Either UGS.LocalErrors (Either ScimSubsystemError a) runDependenciesSafe initialUsers initialTeams = run - . runError + . UGS.runLocalErrors . UGS.interpretDependencies initialUsers initialTeams . UGS.interpretUserGroupSubsystem . mockBrigAPIAccess initialUsers diff --git a/libs/wire-subsystems/test/unit/Wire/TeamInvitationSubsystem/InterpreterSpec.hs b/libs/wire-subsystems/test/unit/Wire/TeamInvitationSubsystem/InterpreterSpec.hs index dd2cf24f721..ce135460d02 100644 --- a/libs/wire-subsystems/test/unit/Wire/TeamInvitationSubsystem/InterpreterSpec.hs +++ b/libs/wire-subsystems/test/unit/Wire/TeamInvitationSubsystem/InterpreterSpec.hs @@ -26,6 +26,7 @@ import Data.Id import Data.LegalHold import Data.Map qualified as Map import Data.Qualified +import Data.Tagged (Tagged) import Data.Text.Encoding import Data.Time import Imports @@ -38,6 +39,8 @@ import Test.Hspec import Test.Hspec.QuickCheck import Test.QuickCheck import Wire.API.EnterpriseLogin +import Wire.API.Error (ErrorS) +import Wire.API.Error.Galley (GalleyError (TeamMemberNotFound, TeamNotFound)) import Wire.API.Team.Invitation import Wire.API.Team.Member import Wire.API.Team.Permission @@ -62,8 +65,7 @@ import Wire.UserSubsystem import Wire.Util type AllEffects = - [ Error TeamInvitationSubsystemError, - EnterpriseLoginSubsystem, + [ EnterpriseLoginSubsystem, TinyLog, TeamSubsystem, GalleyAPIAccess, @@ -75,6 +77,9 @@ type AllEffects = State (Map (InvitationCode) StoredInvitation), Now, State UTCTime, + Error TeamInvitationSubsystemError, + ErrorS 'TeamMemberNotFound, + ErrorS 'TeamNotFound, EmailSubsystem, State (Map EmailAddress [SentMail]), UserSubsystem, @@ -89,7 +94,7 @@ data RunAllEffectsArgs = RunAllEffectsArgs } deriving (Eq, Show) -runAllEffects :: RunAllEffectsArgs -> Sem AllEffects a -> Either TeamInvitationSubsystemError a +runAllEffects :: RunAllEffectsArgs -> Sem AllEffects a -> Either LocalErrors a runAllEffects args = run . runInMemoryUserKeyStoreIntepreterWithStoredUsers args.initialUsers @@ -97,6 +102,7 @@ runAllEffects args = . inMemoryUserSubsystemInterpreter . evalState mempty . noopEmailSubsystemInterpreter + . runLocalErrors . evalState defaultTime . interpretNowAsState . evalState mempty @@ -109,7 +115,26 @@ runAllEffects args = . interpretTeamSubsystemToGalleyAPI . discardTinyLogs . enterpriseLoginSubsystemTestInterpreter args.constGuardResult - . runError + +data LocalErrors + = ETeamMemberNotFound + | ETeamNotFound + | ESubsystem TeamInvitationSubsystemError + deriving stock (Eq, Show) + +runLocalErrors :: + Sem (Error TeamInvitationSubsystemError ': ErrorS 'TeamMemberNotFound ': ErrorS 'TeamNotFound ': r) a -> + Sem r (Either LocalErrors a) +runLocalErrors = fmap toLocalErrors . runError . runError . runError + where + toLocalErrors :: + Either (Tagged 'TeamNotFound ()) (Either (Tagged 'TeamMemberNotFound ()) (Either TeamInvitationSubsystemError a)) -> + Either LocalErrors a + toLocalErrors = \case + Right (Right (Right a)) -> Right a + Right (Right (Left e)) -> Left (ESubsystem e) + Right (Left _) -> Left ETeamMemberNotFound + Left _ -> Left ETeamNotFound spec :: Spec spec = do @@ -192,7 +217,7 @@ spec = do -- run the test -- - outcome :: Either TeamInvitationSubsystemError () + outcome :: Either LocalErrors () outcome = runAllEffects args . runTeamInvitationSubsystem cfg $ do void $ inviteUser inviterLuid tid invReq @@ -201,11 +226,11 @@ spec = do teamNotAllowedOrWrongTeamIdFails = outcome === case domRegUpd.teamInvite of Allowed -> Right () - NotAllowed -> Left TeamInvitationNotAllowedForEmail + NotAllowed -> Left (ESubsystem TeamInvitationNotAllowedForEmail) Team allowedTid -> if allowedTid == tid then Right () - else Left TeamInvitationNotAllowedForEmail + else Left (ESubsystem TeamInvitationNotAllowedForEmail) backendRedirectOrNoRegistrationFails = case domRegUpd.domainRedirect of Backend _ _ -> @@ -213,7 +238,7 @@ spec = do teamNotAllowedOrWrongTeamIdFails NoRegistration -> if isJust preExistingPersonalAccount - then outcome === Left TeamInvitationNotAllowedForEmail + then outcome === Left (ESubsystem TeamInvitationNotAllowedForEmail) else teamNotAllowedOrWrongTeamIdFails _ -> teamNotAllowedOrWrongTeamIdFails @@ -283,7 +308,7 @@ spec = do constGuardResult = Nothing } - outcome :: Either TeamInvitationSubsystemError () + outcome :: Either LocalErrors () outcome = runAllEffects interpreterArgs . runTeamInvitationSubsystem config $ do void $ inviteUser inviterLuid tid invitationRequest - in pure $ outcome === Left TeamInvitationBlockedDomain + in pure $ outcome === Left (ESubsystem TeamInvitationBlockedDomain) diff --git a/libs/wire-subsystems/test/unit/Wire/UserGroupSubsystem/InterpreterSpec.hs b/libs/wire-subsystems/test/unit/Wire/UserGroupSubsystem/InterpreterSpec.hs index c4288743fee..404c0931af3 100644 --- a/libs/wire-subsystems/test/unit/Wire/UserGroupSubsystem/InterpreterSpec.hs +++ b/libs/wire-subsystems/test/unit/Wire/UserGroupSubsystem/InterpreterSpec.hs @@ -33,6 +33,7 @@ import Data.Map qualified as Map import Data.Qualified import Data.Range import Data.Set qualified as Set +import Data.Tagged (Tagged) import Data.UUID qualified as UUID import Data.Vector qualified as V import Imports @@ -46,6 +47,8 @@ import System.Timeout (timeout) import Test.Hspec import Test.Hspec.QuickCheck import Test.QuickCheck +import Wire.API.Error (ErrorS) +import Wire.API.Error.Galley (GalleyError (TeamMemberNotFound, TeamNotFound)) import Wire.API.Pagination import Wire.API.Push.V2 (RecipientClients (RecipientClientsAll), Route (RouteAny)) import Wire.API.Team.Member as TM @@ -80,26 +83,34 @@ type AllDependencies = BackgroundJobsPublisher.BackgroundJobsPublisher, State [Push], Random.Random, - Error UserGroupSubsystemError + Error UserGroupSubsystemError, + ErrorS 'TeamMemberNotFound, + ErrorS 'TeamNotFound ] -runDependenciesFailOnError :: (HasCallStack) => [StoredUser] -> Map TeamId [TeamMember] -> Sem AllDependencies (IO ()) -> IO () -runDependenciesFailOnError usrs team = either (error . ("no assertion: " <>) . show) Imports.id . runDependencies usrs team +runDependenciesFailOnError :: + (HasCallStack) => + [StoredUser] -> + Map TeamId [TeamMember] -> + Sem AllDependencies (IO ()) -> + IO () +runDependenciesFailOnError usrs team = + either (error . ("no assertion: " <>) . show) Imports.id . runDependencies usrs team runDependencies :: [StoredUser] -> Map TeamId [TeamMember] -> Sem AllDependencies a -> - Either UserGroupSubsystemError a + Either LocalErrors a runDependencies initialUsers initialTeams = - run . runError . interpretDependencies initialUsers initialTeams + run . runLocalErrors . interpretDependencies initialUsers initialTeams interpretDependencies :: forall r a. [StoredUser] -> Map TeamId [TeamMember] -> Sem (AllDependencies `Append` r) a -> - Sem ('[Error UserGroupSubsystemError] `Append` r) a + Sem ('[Error UserGroupSubsystemError, ErrorS 'TeamMemberNotFound, ErrorS 'TeamNotFound] `Append` r) a interpretDependencies initialUsers initialTeams = Random.randomToNull . evalState mempty @@ -116,10 +127,10 @@ runDependenciesWithReturnState :: [StoredUser] -> Map TeamId [TeamMember] -> Sem AllDependencies a -> - Either UserGroupSubsystemError ([Push], a) + Either LocalErrors ([Push], a) runDependenciesWithReturnState initialUsers initialTeams = run - . runError + . runLocalErrors . Random.randomToNull . runState mempty . noopBackgroundJobsPublisher @@ -131,6 +142,26 @@ runDependenciesWithReturnState initialUsers initialTeams = . interpretTeamSubsystemToGalleyAPI . runInMemoryUserSubsytemInterpreter initialUsers mempty +data LocalErrors + = ETeamMemberNotFound + | ETeamNotFound + | ESubsystem UserGroupSubsystemError + deriving stock (Eq, Show) + +runLocalErrors :: + Sem (Error UserGroupSubsystemError ': ErrorS 'TeamMemberNotFound ': ErrorS 'TeamNotFound ': r) a -> + Sem r (Either LocalErrors a) +runLocalErrors = fmap toLocalErrors . runError . runError . runError + where + toLocalErrors :: + Either (Tagged 'TeamNotFound ()) (Either (Tagged 'TeamMemberNotFound ()) (Either UserGroupSubsystemError a)) -> + Either LocalErrors a + toLocalErrors = \case + Right (Right (Right a)) -> Right a + Right (Right (Left e)) -> Left (ESubsystem e) + Right (Left _) -> Left ETeamMemberNotFound + Left _ -> Left ETeamNotFound + expectRight :: (Show err) => Either err Property -> Property expectRight = \case Left err -> counterexample ("Unexpected error: " <> show err) False @@ -231,7 +262,7 @@ spec = timeoutHook $ describe "UserGroupSubsystem.Interpreter" do prop "only team admins should be able to create a group" $ \((WithMods team) :: WithMods '[AtLeastOneNonAdmin] ArbitraryTeam) newUserGroupName -> - expectLeft UserGroupNotATeamAdmin + expectLeft (ESubsystem UserGroupNotATeamAdmin) . runDependencies (allUsers team) (galleyTeam team) . interpretUserGroupSubsystem $ do @@ -243,7 +274,7 @@ spec = timeoutHook $ describe "UserGroupSubsystem.Interpreter" do prop "only team members are allowed in the group" $ \team otherUsers newUserGroupName -> let othersWithoutTeamMembers = filter (\u -> u.teamId /= Just team.tid) otherUsers in notNull othersWithoutTeamMembers - ==> expectLeft UserGroupMemberIsNotInTheSameTeam + ==> expectLeft (ESubsystem UserGroupMemberIsNotInTheSameTeam) . runDependencies (allUsers team <> otherUsers) (galleyTeam team) . interpretUserGroupSubsystem $ do @@ -535,7 +566,7 @@ spec = timeoutHook $ describe "UserGroupSubsystem.Interpreter" do prop "only team admins should be able to update a group" $ \((WithMods team) :: WithMods '[AtLeastOneNonAdmin] ArbitraryTeam) newUserGroupName newUserGroupName2 -> - expectLeft UserGroupNotATeamAdmin + expectLeft (ESubsystem UserGroupNotATeamAdmin) . runDependencies (allUsers team) (galleyTeam team) . interpretUserGroupSubsystem $ do @@ -603,7 +634,7 @@ spec = timeoutHook $ describe "UserGroupSubsystem.Interpreter" do prop "only team admins can delete user groups" $ \((WithMods team) :: WithMods '[AtLeastOneNonAdmin] ArbitraryTeam) groupName -> - expectLeft UserGroupNotATeamAdmin + expectLeft (ESubsystem UserGroupNotATeamAdmin) . runDependencies (allUsers team) (galleyTeam team) . interpretUserGroupSubsystem $ do @@ -662,7 +693,7 @@ spec = timeoutHook $ describe "UserGroupSubsystem.Interpreter" do newGroupName (team2 :: ArbitraryTeam) (addOrRemove :: Bool) -> - expectLeft UserGroupMemberIsNotInTheSameTeam + expectLeft (ESubsystem UserGroupMemberIsNotInTheSameTeam) . runDependencies (allUsers team) (galleyTeam team) . interpretUserGroupSubsystem $ do @@ -675,7 +706,7 @@ spec = timeoutHook $ describe "UserGroupSubsystem.Interpreter" do newGroupName (team2 :: ArbitraryTeam) (addOrRemove :: Bool) -> - expectLeft UserGroupNotFound + expectLeft (ESubsystem UserGroupNotFound) . runDependencies (allUsers team) (galleyTeam team) . interpretUserGroupSubsystem $ do diff --git a/libs/wire-subsystems/wire-subsystems.cabal b/libs/wire-subsystems/wire-subsystems.cabal index ab8c25cdd51..d1194d10d56 100644 --- a/libs/wire-subsystems/wire-subsystems.cabal +++ b/libs/wire-subsystems/wire-subsystems.cabal @@ -263,12 +263,10 @@ library Wire.ConversationSubsystem.Clients Wire.ConversationSubsystem.Create Wire.ConversationSubsystem.CreateInternal - Wire.ConversationSubsystem.Features Wire.ConversationSubsystem.Federation Wire.ConversationSubsystem.Fetch Wire.ConversationSubsystem.Internal Wire.ConversationSubsystem.Interpreter - Wire.ConversationSubsystem.Legalhold Wire.ConversationSubsystem.LegalholdConflicts Wire.ConversationSubsystem.Mapping Wire.ConversationSubsystem.Message diff --git a/services/brig/src/Brig/CanonicalInterpreter.hs b/services/brig/src/Brig/CanonicalInterpreter.hs index c1fa80ebf30..7784140ef67 100644 --- a/services/brig/src/Brig/CanonicalInterpreter.hs +++ b/services/brig/src/Brig/CanonicalInterpreter.hs @@ -42,6 +42,7 @@ import Data.ZAuth.CryptoSign (CryptoSign, runCryptoSign) import Hasql.Pool (UsageError) import Hasql.Pool qualified as Hasql import Imports +import Network.Wai.Utilities.Error qualified as Wai import Polysemy import Polysemy.Async import Polysemy.Conc @@ -50,6 +51,8 @@ import Polysemy.Error (Error, errorToIOFinal, mapError, runError) import Polysemy.Input (Input, runInputConst) import Polysemy.Internal.Kind import Polysemy.TinyLog (TinyLog) +import Wire.API.Error (ErrorS, errorToWai) +import Wire.API.Error.Galley import Wire.API.Federation.Client qualified import Wire.API.Federation.Error import Wire.API.Team.Collaborator @@ -205,6 +208,10 @@ type BrigLowerLevelEffects = Error VerificationCodeSubsystemError, Error PropertySubsystemError, Error RateLimitExceeded, + ErrorS 'TeamMemberNotFound, + ErrorS 'TeamNotFound, + Error Wai.Error, + Error HttpError, Wire.FederationAPIAccess.FederationAPIAccess Wire.API.Federation.Client.FederatorClient, DomainVerificationChallengeStore, DomainRegistrationStore, @@ -426,6 +433,10 @@ runBrigToIO e (AppT ma) = do . interpretDomainRegistrationStoreToCassandra e.casClient . interpretDomainVerificationChallengeStoreToCassandra e.casClient e.settings.challengeTTL . interpretFederationAPIAccess federationApiAccessConfig + . rethrowHttpErrorIO + . mapError StdError -- Wai.Error + . mapError (const $ errorToWai @'TeamNotFound) -- ErrorS 'TeamNotFound + . mapError (const $ errorToWai @'TeamMemberNotFound) -- ErrorS 'TeamMemberNotFound . mapError rateLimitExceededToHttpError . mapError propertySubsystemErrorToHttpError . mapError verificationCodeSubsystemErrorToHttpError diff --git a/services/galley/src/Galley/API/Internal.hs b/services/galley/src/Galley/API/Internal.hs index b7e418181ab..27927ca5aae 100644 --- a/services/galley/src/Galley/API/Internal.hs +++ b/services/galley/src/Galley/API/Internal.hs @@ -85,7 +85,7 @@ import Wire.ConversationStore.MLS.Types import Wire.ConversationSubsystem as Conv import Wire.ConversationSubsystem.LegalholdConflicts (LegalholdConflicts, LegalholdConflictsOldClients) import Wire.CustomBackendStore -import Wire.FeaturesConfigSubsystem (FeaturesConfigSubsystem, getAllTeamFeaturesForServer, getFeatureForTeam) +import Wire.FeaturesConfigSubsystem import Wire.FederationSubsystem (getFederationStatus) import Wire.LegalHoldStore as LegalHoldStore import Wire.ListItems @@ -102,6 +102,7 @@ import Wire.TeamStore qualified as E import Wire.TeamSubsystem (TeamSubsystem) import Wire.TeamSubsystem qualified as TeamSubsystem import Wire.UserClientIndexStore +import Wire.UserClientIndexStore as UserClientIndexStore import Wire.UserList import Wire.Util @@ -234,7 +235,7 @@ miscAPI :: API IMiscAPI GalleyEffects miscAPI = mkNamedAPI @"get-team-members" Teams.getBindingTeamMembers <@> mkNamedAPI @"get-team-id" lookupBindingTeam - <@> mkNamedAPI @"test-get-clients" Conv.getClients + <@> mkNamedAPI @"test-get-clients" UserClientIndexStore.getClientsId <@> mkNamedAPI @"test-add-client" createClient <@> mkNamedAPI @"test-delete-client" rmClient <@> mkNamedAPI @"add-service" createService @@ -345,6 +346,7 @@ rmUser :: Member (Error FederationError) r, Member NotificationSubsystem r, Member ConversationSubsystem r, + Member TeamSubsystem r, Member Now r, Member (ListItems p2 TeamId) r, Member P.TinyLog r, @@ -380,7 +382,7 @@ rmUser lusr conn = do getFeatureForTeam @_ @LimitedEventFanoutConfig tid >>= ( \case FeatureStatusEnabled -> Left <$> E.getTeamAdmins tid - FeatureStatusDisabled -> Right <$> getTeamMembersForFanout tid + FeatureStatusDisabled -> Right <$> TeamSubsystem.getTeamMembersForFanout tid ) . (.status) uncheckedDeleteTeamMember lusr conn tid (tUnqualified lusr) toNotify diff --git a/services/galley/src/Galley/API/LegalHold.hs b/services/galley/src/Galley/API/LegalHold.hs index b241eac5613..30fe7d1d693 100644 --- a/services/galley/src/Galley/API/LegalHold.hs +++ b/services/galley/src/Galley/API/LegalHold.hs @@ -86,11 +86,12 @@ createSettings :: ( Member (ErrorS 'LegalHoldNotEnabled) r, Member (ErrorS 'LegalHoldServiceInvalidKey) r, Member (ErrorS 'LegalHoldServiceBadResponse) r, + Member (ErrorS OperationDenied) r, + Member (ErrorS 'NotATeamMember) r, Member LegalHoldData.LegalHoldStore r, Member P.TinyLog r, Member (Input (FeatureDefaults LegalholdConfig)) r, Member TeamSubsystem r, - Member ConversationSubsystem r, Member FeaturesConfigSubsystem r ) => Local UserId -> @@ -105,7 +106,7 @@ createSettings lzusr tid newService = do -- Log.debug $ -- Log.field "targets" (toByteString . show $ toByteString <$> zothers) -- . Log.field "action" (Log.val "LegalHold.createSettings") - void $ permissionCheck ChangeLegalHoldTeamSettings zusrMembership + void $ TeamSubsystem.permissionCheck ChangeLegalHoldTeamSettings zusrMembership (key :: ServiceKey, fpr :: Fingerprint Rsa) <- LegalHoldData.validateServiceKey newService.newLegalHoldServiceKey >>= noteS @'LegalHoldServiceInvalidKey @@ -146,6 +147,9 @@ removeSettingsInternalPaging :: Member (ErrorS 'LegalHoldNotEnabled) r, Member (ErrorS 'LegalHoldServiceNotRegistered) r, Member (ErrorS 'UserLegalHoldIllegalOperation) r, + Member (ErrorS OperationDenied) r, + Member (ErrorS 'NotATeamMember) r, + Member (Error AuthenticationError) r, Member FireAndForget r, Member ConversationSubsystem r, Member LegalHoldData.LegalHoldStore r, @@ -177,6 +181,9 @@ removeSettings :: Member (ErrorS 'LegalHoldNotEnabled) r, Member (ErrorS 'LegalHoldServiceNotRegistered) r, Member (ErrorS 'UserLegalHoldIllegalOperation) r, + Member (ErrorS OperationDenied) r, + Member (ErrorS 'NotATeamMember) r, + Member (Error AuthenticationError) r, Member FireAndForget r, Member ConversationSubsystem r, Member P.TinyLog r, @@ -198,7 +205,7 @@ removeSettings zusr tid (Public.RemoveLegalHoldSettingsRequest mPassword) = do -- Log.debug $ -- Log.field "targets" (toByteString . show $ toByteString <$> zothers) -- . Log.field "action" (Log.val "LegalHold.removeSettings") - void $ permissionCheck ChangeLegalHoldTeamSettings zusrMembership + void $ TeamSubsystem.permissionCheck ChangeLegalHoldTeamSettings zusrMembership ensureReAuthorised zusr mPassword Nothing Nothing removeSettings' @p tid where @@ -224,6 +231,7 @@ removeSettings' :: Member (ErrorS 'LegalHoldCouldNotBlockConnections) r, Member FireAndForget r, Member ConversationSubsystem r, + Member TeamSubsystem r, Member LegalHoldData.LegalHoldStore r, Member (TeamMemberStore p) r, Member TeamStore r, @@ -297,6 +305,8 @@ requestDevice :: Member (ErrorS 'TeamMemberNotFound) r, Member (ErrorS 'UserLegalHoldAlreadyEnabled) r, Member (ErrorS 'UserLegalHoldIllegalOperation) r, + Member (ErrorS OperationDenied) r, + Member (ErrorS 'NotATeamMember) r, Member ConversationSubsystem r, Member LegalHoldData.LegalHoldStore r, Member P.TinyLog r, @@ -318,7 +328,7 @@ requestDevice lzusr tid uid = do Log.field "targets" (toByteString (tUnqualified luid)) . Log.field "action" (Log.val "LegalHold.requestDevice") zusrMembership <- TeamSubsystem.internalGetTeamMember zusr tid - void $ permissionCheck ChangeLegalHoldUserSettings zusrMembership + void $ TeamSubsystem.permissionCheck ChangeLegalHoldUserSettings zusrMembership member <- noteS @'TeamMemberNotFound =<< TeamSubsystem.internalGetTeamMember uid tid case member ^. legalHoldStatus of UserLegalHoldEnabled -> throwS @'UserLegalHoldAlreadyEnabled @@ -379,6 +389,7 @@ approveDevice :: Member (ErrorS 'UserLegalHoldAlreadyEnabled) r, Member (ErrorS 'UserLegalHoldIllegalOperation) r, Member (ErrorS 'UserLegalHoldNotPending) r, + Member (ErrorS 'NotATeamMember) r, Member ConversationSubsystem r, Member LegalHoldData.LegalHoldStore r, Member P.TinyLog r, @@ -402,7 +413,7 @@ approveDevice lzusr connId tid uid (Public.ApproveLegalHoldForUserRequest mPassw Log.field "targets" (toByteString (tUnqualified luid)) . Log.field "action" (Log.val "LegalHold.approveDevice") unless (zusr == tUnqualified luid) $ throwS @'AccessDenied - assertOnTeam (tUnqualified luid) tid + TeamSubsystem.assertOnTeam (tUnqualified luid) tid ensureReAuthorised zusr mPassword Nothing Nothing userLHStatus <- maybe defUserLegalHoldStatus (view legalHoldStatus) <$> TeamSubsystem.internalGetTeamMember (tUnqualified luid) tid @@ -443,6 +454,9 @@ disableForUser :: Member (ErrorS 'LegalHoldCouldNotBlockConnections) r, Member (ErrorS 'LegalHoldServiceNotRegistered) r, Member (ErrorS 'UserLegalHoldIllegalOperation) r, + Member (ErrorS OperationDenied) r, + Member (ErrorS 'NotATeamMember) r, + Member (Error AuthenticationError) r, Member ConversationSubsystem r, Member LegalHoldData.LegalHoldStore r, Member P.TinyLog r, @@ -461,7 +475,7 @@ disableForUser lzusr tid uid (Public.DisableLegalHoldForUserRequest mPassword) = Log.field "targets" (toByteString (tUnqualified luid)) . Log.field "action" (Log.val "LegalHold.disableForUser") zusrMembership <- TeamSubsystem.internalGetTeamMember (tUnqualified lzusr) tid - void $ permissionCheck ChangeLegalHoldUserSettings zusrMembership + void $ TeamSubsystem.permissionCheck ChangeLegalHoldUserSettings zusrMembership userLHStatus <- maybe defUserLegalHoldStatus (view legalHoldStatus) <$> TeamSubsystem.internalGetTeamMember (tUnqualified luid) tid @@ -498,6 +512,7 @@ changeLegalholdStatusAndHandlePolicyConflicts :: Member (ErrorS 'LegalHoldCouldNotBlockConnections) r, Member (ErrorS 'UserLegalHoldIllegalOperation) r, Member ConversationSubsystem r, + Member TeamSubsystem r, Member LegalHoldData.LegalHoldStore r, Member TeamStore r, Member P.TinyLog r @@ -548,7 +563,7 @@ blockNonConsentingConnections :: Member TeamStore r, Member P.TinyLog r, Member (ErrorS 'LegalHoldCouldNotBlockConnections) r, - Member ConversationSubsystem r + Member TeamSubsystem r ) => UserId -> Sem r () @@ -569,7 +584,7 @@ blockNonConsentingConnections uid = do -- FUTUREWORK: Handle remoteUsers here when federation is implemented for (chunksOf 32 localUids) $ \others -> do teamsOfUsers <- getUsersTeams others - filterM (fmap (== ConsentNotGiven) . checkConsent teamsOfUsers) others + filterM (fmap (== TeamSubsystem.ConsentNotGiven) . TeamSubsystem.checkConsent teamsOfUsers) others blockConflicts :: UserId -> [UserId] -> Sem r [String] blockConflicts _ [] = pure [] @@ -603,7 +618,8 @@ unsetTeamLegalholdWhitelistedH tid = do handleGroupConvPolicyConflicts :: ( Member (Error InternalError) r, Member (ErrorS ('ActionDenied 'RemoveConversationMember)) r, - Member ConversationSubsystem r + Member ConversationSubsystem r, + Member TeamSubsystem r ) => Local UserId -> UserLegalHoldStatus -> @@ -615,7 +631,7 @@ handleGroupConvPolicyConflicts luid hypotheticalLHStatus = do membersAndLHStatus :: [(LocalMember, UserLegalHoldStatus)] <- do let mems = conv.localMembers - uidsLHStatus <- getLHStatusForUsers ((.id_) <$> mems) + uidsLHStatus <- TeamSubsystem.getLHStatusForUsers ((.id_) <$> mems) pure $ zipWith ( \mem (mid, status) -> @@ -636,10 +652,10 @@ handleGroupConvPolicyConflicts luid hypotheticalLHStatus = do (InternalErrorWithDescription "conversation disappeared while iterating on a list of conversations") . mapErrorS @('ActionDenied 'LeaveConversation) @('ActionDenied 'RemoveConversationMember) $ if any - ((== ConsentGiven) . consentGiven . snd) + ((== TeamSubsystem.ConsentGiven) . TeamSubsystem.consentGiven . snd) (filter ((== roleNameWireAdmin) . (.convRoleName) . fst) membersAndLHStatus) then do - for_ (filter ((== ConsentNotGiven) . consentGiven . snd) membersAndLHStatus) $ \(memberNoConsent, _) -> do + for_ (filter ((== TeamSubsystem.ConsentNotGiven) . TeamSubsystem.consentGiven . snd) membersAndLHStatus) $ \(memberNoConsent, _) -> do let lusr = qualifyAs luid memberNoConsent.id_ removeMemberFromLocalConv lcnv lusr Nothing (tUntagged lusr) else do diff --git a/services/galley/src/Galley/API/LegalHold/Team.hs b/services/galley/src/Galley/API/LegalHold/Team.hs index c7f511d4491..632dff0658d 100644 --- a/services/galley/src/Galley/API/LegalHold/Team.hs +++ b/services/galley/src/Galley/API/LegalHold/Team.hs @@ -21,19 +21,25 @@ module Galley.API.LegalHold.Team assertLegalHoldEnabledForTeam, ensureNotTooLargeToActivateLegalHold, teamSizeBelowLimit, + ensureReAuthorised, ) where +import Data.Code qualified as Code import Data.Id +import Data.Misc (PlainTextPassword6) import Data.Range import Imports import Polysemy +import Polysemy.Error import Polysemy.Input (Input, input) import Wire.API.Error import Wire.API.Error.Galley import Wire.API.Team.Feature import Wire.API.Team.FeatureFlags as Team (FanoutLimit, FeatureDefaults (..)) import Wire.API.Team.Size +import Wire.API.User (VerificationAction) +import Wire.API.User.Auth.ReAuth import Wire.BrigAPIAccess import Wire.FeaturesConfigSubsystem (FeaturesConfigSubsystem, getDbFeatureRawInternal) import Wire.LegalHold @@ -94,3 +100,15 @@ teamSizeBelowLimit teamSize = do FeatureLegalHoldWhitelistTeamsAndImplicitConsent -> -- unlimited, see docs of 'ensureNotTooLargeForLegalHold' pure True + +ensureReAuthorised :: + ( Member BrigAPIAccess r, + Member (Error AuthenticationError) r + ) => + UserId -> + Maybe PlainTextPassword6 -> + Maybe Code.Value -> + Maybe VerificationAction -> + Sem r () +ensureReAuthorised u secret mbAction mbCode = + reauthUser u (ReAuthUser secret mbAction mbCode) >>= fromEither diff --git a/services/galley/src/Galley/API/Public/Bot.hs b/services/galley/src/Galley/API/Public/Bot.hs index 22cccf3da33..ff47adc6254 100644 --- a/services/galley/src/Galley/API/Public/Bot.hs +++ b/services/galley/src/Galley/API/Public/Bot.hs @@ -25,6 +25,7 @@ import Wire.API.Provider.Bot import Wire.API.Routes.API import Wire.API.Routes.Public.Galley.Bot import Wire.ConversationSubsystem +import Wire.FeaturesConfigSubsystem botAPI :: API BotAPI GalleyEffects botAPI = @@ -33,7 +34,9 @@ botAPI = getBotConversationH :: forall r. - (Member ConversationSubsystem r) => + ( Member ConversationSubsystem r, + Member FeaturesConfigSubsystem r + ) => BotId -> ConvId -> Sem r BotConvView diff --git a/services/galley/src/Galley/API/Public/Feature.hs b/services/galley/src/Galley/API/Public/Feature.hs index 0443b851295..6fea67c5905 100644 --- a/services/galley/src/Galley/API/Public/Feature.hs +++ b/services/galley/src/Galley/API/Public/Feature.hs @@ -29,8 +29,7 @@ import Wire.API.Routes.API import Wire.API.Routes.Public.Galley.Feature import Wire.API.Routes.Version import Wire.API.Team.Feature -import Wire.ConversationSubsystem -import Wire.FeaturesConfigSubsystem (getAllTeamFeaturesForTeamMember, getFeature) +import Wire.FeaturesConfigSubsystem featureAPIGetPut :: forall cfg r. (_) => API (FeatureAPIGetPut cfg) r featureAPIGetPut = diff --git a/services/galley/src/Galley/API/Public/LegalHold.hs b/services/galley/src/Galley/API/Public/LegalHold.hs index ed0afa99c31..33a5eeb8bb1 100644 --- a/services/galley/src/Galley/API/Public/LegalHold.hs +++ b/services/galley/src/Galley/API/Public/LegalHold.hs @@ -21,7 +21,7 @@ import Galley.API.LegalHold import Galley.App import Wire.API.Routes.API import Wire.API.Routes.Public.Galley.LegalHold -import Wire.ConversationSubsystem +import Wire.TeamSubsystem (getUserStatus) legalHoldAPI :: API LegalHoldAPI GalleyEffects legalHoldAPI = diff --git a/services/galley/src/Galley/API/Teams.hs b/services/galley/src/Galley/API/Teams.hs index 051104b8a90..ed3ff69dab6 100644 --- a/services/galley/src/Galley/API/Teams.hs +++ b/services/galley/src/Galley/API/Teams.hs @@ -297,11 +297,12 @@ updateTeamStatus tid (TeamStatusUpdate newStatus cur) = do (_, _) -> throwS @'InvalidTeamStatusUpdate updateTeamH :: - ( Member ConversationSubsystem r, - Member NotificationSubsystem r, + ( Member NotificationSubsystem r, Member Now r, Member TeamStore r, - Member TeamSubsystem r + Member TeamSubsystem r, + Member (ErrorS (MissingPermission (Just SetTeamData))) r, + Member (ErrorS NotATeamMember) r ) => UserId -> ConnId -> @@ -310,7 +311,7 @@ updateTeamH :: Sem r () updateTeamH zusr zcon tid updateData = do zusrMembership <- TeamSubsystem.internalGetTeamMember zusr tid - void $ permissionCheckS SSetTeamData zusrMembership + void $ TeamSubsystem.permissionCheckS SSetTeamData zusrMembership E.setTeamData tid updateData now <- Now.get admins <- E.getTeamAdmins tid @@ -328,12 +329,15 @@ updateTeamH zusr zcon tid updateData = do deleteTeam :: forall r. - ( Member ConversationSubsystem r, - Member (ErrorS 'DeleteQueueFull) r, + ( Member (ErrorS 'DeleteQueueFull) r, Member (ErrorS 'TeamNotFound) r, + Member (ErrorS OperationDenied) r, + Member (Error AuthenticationError) r, Member (E.Queue DeleteItem) r, + Member (ErrorS NotATeamMember) r, Member TeamStore r, - Member TeamSubsystem r + Member TeamSubsystem r, + Member E.BrigAPIAccess r ) => UserId -> ConnId -> @@ -351,7 +355,7 @@ deleteTeam zusr zcon tid body = do queueTeamDeletion tid zusr (Just zcon) where checkPermissions team = do - void $ permissionCheck DeleteTeam =<< TeamSubsystem.internalGetTeamMember zusr tid + void $ TeamSubsystem.permissionCheck DeleteTeam =<< TeamSubsystem.internalGetTeamMember zusr tid when (tdTeam team ^. teamBinding == Binding) $ do ensureReAuthorised zusr (body ^. tdAuthPassword) (body ^. tdVerificationCode) (Just U.DeleteTeam) @@ -507,7 +511,6 @@ uncheckedGetTeamMember tid uid = addTeamMember :: forall r. ( Member E.BrigAPIAccess r, - Member ConversationSubsystem r, Member NotificationSubsystem r, Member (ErrorS 'InvalidPermissions) r, Member (ErrorS 'NoAddToBinding) r, @@ -516,6 +519,9 @@ addTeamMember :: Member (ErrorS 'TooManyTeamAdmins) r, Member (ErrorS 'UserBindingExists) r, Member (ErrorS 'TooManyTeamMembersOnTeamWithLegalhold) r, + Member (ErrorS OperationDenied) r, + Member (ErrorS 'NotATeamMember) r, + Member (ErrorS 'NotConnected) r, Member (Input Opts) r, Member Now r, Member LegalHoldStore r, @@ -541,12 +547,12 @@ addTeamMember lzusr zcon tid nmem = do -- verify permissions zusrMembership <- TeamSubsystem.internalGetTeamMember zusr tid - >>= permissionCheck AddTeamMember + >>= TeamSubsystem.permissionCheck AddTeamMember let targetPermissions = nmem ^. nPermissions targetPermissions `ensureNotElevated` zusrMembership ensureNonBindingTeam tid ensureUnboundUsers [uid] - ensureConnectedToLocals zusr [uid] + E.ensureConnectedToLocals zusr [uid] (TeamSize sizeBeforeJoin) <- E.getSize tid ensureNotTooLargeForLegalHold tid (fromIntegral sizeBeforeJoin + 1) void $ addTeamMemberInternal tid (Just zusr) (Just zcon) nmem @@ -640,12 +646,13 @@ uncheckedUpdateTeamMember mlzusr mZcon tid newMem = do updateTeamMember :: forall r. ( Member E.BrigAPIAccess r, - Member ConversationSubsystem r, Member (ErrorS 'AccessDenied) r, Member (ErrorS 'InvalidPermissions) r, Member (ErrorS 'TeamNotFound) r, Member (ErrorS 'TeamMemberNotFound) r, Member (ErrorS 'TooManyTeamAdmins) r, + Member (ErrorS OperationDenied) r, + Member (ErrorS 'NotATeamMember) r, Member NotificationSubsystem r, Member Now r, Member P.TinyLog r, @@ -670,7 +677,7 @@ updateTeamMember lzusr zcon tid newMem = do -- get the team and verify permissions user <- TeamSubsystem.internalGetTeamMember zusr tid - >>= permissionCheck SetMemberPermissions + >>= TeamSubsystem.permissionCheck SetMemberPermissions -- user may not elevate permissions targetPermissions `ensureNotElevated` user @@ -699,6 +706,8 @@ deleteTeamMember :: Member (ErrorS 'TeamMemberNotFound) r, Member (ErrorS 'TeamNotFound) r, Member (ErrorS 'NotATeamMember) r, + Member (ErrorS OperationDenied) r, + Member (Error AuthenticationError) r, Member ConversationSubsystem r, Member TeamSubsystem r, Member NotificationSubsystem r, @@ -724,6 +733,8 @@ deleteNonBindingTeamMember :: Member (ErrorS 'TeamMemberNotFound) r, Member (ErrorS 'TeamNotFound) r, Member (ErrorS 'NotATeamMember) r, + Member (ErrorS OperationDenied) r, + Member (Error AuthenticationError) r, Member ConversationSubsystem r, Member TeamSubsystem r, Member NotificationSubsystem r, @@ -749,6 +760,8 @@ deleteTeamMember' :: Member (ErrorS 'TeamMemberNotFound) r, Member (ErrorS 'TeamNotFound) r, Member (ErrorS 'NotATeamMember) r, + Member (ErrorS OperationDenied) r, + Member (Error AuthenticationError) r, Member ConversationSubsystem r, Member NotificationSubsystem r, Member FeaturesConfigSubsystem r, @@ -770,7 +783,7 @@ deleteTeamMember' lusr zcon tid remove mBody = do . Log.field "action" (Log.val "Teams.deleteTeamMember") zusrMember <- TeamSubsystem.internalGetTeamMember (tUnqualified lusr) tid targetMember <- TeamSubsystem.internalGetTeamMember remove tid - void $ permissionCheck RemoveTeamMember zusrMember + void $ TeamSubsystem.permissionCheck RemoveTeamMember zusrMember do dm <- noteS @'NotATeamMember zusrMember tm <- noteS @'TeamMemberNotFound targetMember @@ -800,7 +813,7 @@ deleteTeamMember' lusr zcon tid remove mBody = do admins <- E.getTeamAdmins tid uncheckedDeleteTeamMember lusr (Just zcon) tid remove (Left admins) FeatureStatusDisabled -> do - mems <- getTeamMembersForFanout tid + mems <- TeamSubsystem.getTeamMembersForFanout tid uncheckedDeleteTeamMember lusr (Just zcon) tid remove (Right mems) pure TeamMemberDeleteCompleted @@ -957,7 +970,8 @@ deleteTeamConversation lusr zcon _tid cid = do getSearchVisibility :: ( Member TeamStore r, - Member ConversationSubsystem r, + Member (ErrorS OperationDenied) r, + Member (ErrorS 'NotATeamMember) r, Member TeamSubsystem r ) => Local UserId -> @@ -965,14 +979,15 @@ getSearchVisibility :: Sem r TeamSearchVisibilityView getSearchVisibility luid tid = do zusrMembership <- TeamSubsystem.internalGetTeamMember (tUnqualified luid) tid - void $ permissionCheck ViewTeamSearchVisibility zusrMembership + void $ TeamSubsystem.permissionCheck ViewTeamSearchVisibility zusrMembership getSearchVisibilityInternal tid setSearchVisibility :: forall r. ( Member TeamStore r, Member (ErrorS 'TeamSearchVisibilityNotEnabled) r, - Member ConversationSubsystem r, + Member (ErrorS OperationDenied) r, + Member (ErrorS 'NotATeamMember) r, Member TeamSubsystem r ) => (TeamId -> Sem r Bool) -> @@ -982,7 +997,7 @@ setSearchVisibility :: Sem r () setSearchVisibility availableForTeam luid tid req = do zusrMembership <- TeamSubsystem.internalGetTeamMember (tUnqualified luid) tid - void $ permissionCheck ChangeTeamSearchVisibility zusrMembership + void $ TeamSubsystem.permissionCheck ChangeTeamSearchVisibility zusrMembership setSearchVisibilityInternal availableForTeam tid req -- Internal ----------------------------------------------------------------- @@ -1143,13 +1158,13 @@ getBindingTeamMembers :: ( Member (ErrorS 'TeamNotFound) r, Member (ErrorS 'NonBindingTeam) r, Member TeamStore r, - Member ConversationSubsystem r + Member TeamSubsystem r ) => UserId -> Sem r TeamMemberList getBindingTeamMembers zusr = do tid <- E.lookupBindingTeam zusr - getTeamMembersForFanout tid + TeamSubsystem.getTeamMembersForFanout tid -- This could be extended for more checks, for now we test only legalhold -- @@ -1242,6 +1257,8 @@ updateTeamCollaborator :: forall r. ( Member ConversationStore r, Member P.TinyLog r, + Member (ErrorS OperationDenied) r, + Member (ErrorS 'NotATeamMember) r, Member TeamCollaboratorsSubsystem r, Member ConversationSubsystem r, Member TeamSubsystem r @@ -1256,7 +1273,7 @@ updateTeamCollaborator lusr tid rusr perms = do Log.field "targets" (toByteString rusr) . Log.field "action" (Log.val "Teams.updateTeamCollaborator") zusrMember <- TeamSubsystem.internalGetTeamMember (tUnqualified lusr) tid - void $ permissionCheck UpdateTeamCollaborator zusrMember + void $ TeamSubsystem.permissionCheck UpdateTeamCollaborator zusrMember when (Set.null $ Set.intersection (Set.fromList [Collaborator.CreateTeamConversation, Collaborator.ImplicitConnection]) perms) $ removeFromConvsAndPushConvLeaveEvent lusr Nothing tid rusr internalUpdateTeamCollaborator rusr tid perms @@ -1269,6 +1286,8 @@ removeTeamCollaborator :: Member ConversationSubsystem r, Member Now r, Member P.TinyLog r, + Member (ErrorS OperationDenied) r, + Member (ErrorS 'NotATeamMember) r, Member FeaturesConfigSubsystem r, Member TeamStore r, Member TeamCollaboratorsSubsystem r, @@ -1283,12 +1302,12 @@ removeTeamCollaborator lusr tid rusr = do Log.field "targets" (toByteString rusr) . Log.field "action" (Log.val "Teams.removeTeamCollaborator") zusrMember <- TeamSubsystem.internalGetTeamMember (tUnqualified lusr) tid - void $ permissionCheck RemoveTeamCollaborator zusrMember + void $ TeamSubsystem.permissionCheck RemoveTeamCollaborator zusrMember toNotify <- (getFeatureForTeam @_ @LimitedEventFanoutConfig tid) >>= ( \case FeatureStatusEnabled -> Left <$> E.getTeamAdmins tid - FeatureStatusDisabled -> Right <$> getTeamMembersForFanout tid + FeatureStatusDisabled -> Right <$> TeamSubsystem.getTeamMembersForFanout tid ) . (.status) uncheckedDeleteTeamMember lusr Nothing tid rusr toNotify diff --git a/services/galley/src/Galley/API/Teams/Features.hs b/services/galley/src/Galley/API/Teams/Features.hs index f37af6d36ec..3b528db8395 100644 --- a/services/galley/src/Galley/API/Teams/Features.hs +++ b/services/galley/src/Galley/API/Teams/Features.hs @@ -55,6 +55,7 @@ import Wire.API.Error.Galley import Wire.API.Event.FeatureConfig import Wire.API.Federation.Client (FederatorClient) import Wire.API.Federation.Error +import Wire.API.Routes.Internal.Galley.TeamFeatureNoConfigMulti qualified as Multi import Wire.API.Team.Feature import Wire.API.Team.FeatureFlags import Wire.API.Team.Member @@ -94,15 +95,15 @@ patchFeatureInternal :: SetFeatureForTeamConstraints cfg r, Member TeamFeatureStore r, Member P.TinyLog r, - Member ConversationSubsystem r, Member NotificationSubsystem r, + Member TeamSubsystem r, GetFeatureConfigEffects r ) => TeamId -> LockableFeaturePatch cfg -> Sem r (LockableFeature cfg) patchFeatureInternal tid patch = do - assertTeamExists tid + TeamSubsystem.assertTeamExists tid dbFeature <- getDbFeatureRawInternal tid defFeature :: LockableFeature cfg <- resolveServerFeature let dbFeatureWithDefaults = dbFeature.applyDbFeature defFeature @@ -127,9 +128,10 @@ setFeature :: ComputeFeatureConstraints cfg r, SetFeatureForTeamConstraints cfg r, Member (Error TeamFeatureError) r, + Member (ErrorS OperationDenied) r, + Member (ErrorS 'NotATeamMember) r, Member TeamFeatureStore r, Member P.TinyLog r, - Member ConversationSubsystem r, Member NotificationSubsystem r, Member TeamSubsystem r ) => @@ -139,7 +141,7 @@ setFeature :: Sem r (LockableFeature cfg) setFeature uid tid feat = do zusrMembership <- TeamSubsystem.internalGetTeamMember uid tid - void $ permissionCheck ChangeTeamFeature zusrMembership + void $ TeamSubsystem.permissionCheck ChangeTeamFeature zusrMembership setFeatureUnchecked tid feat setFeatureInternal :: @@ -150,14 +152,14 @@ setFeatureInternal :: Member (Error TeamFeatureError) r, Member TeamFeatureStore r, Member P.TinyLog r, - Member ConversationSubsystem r, - Member NotificationSubsystem r + Member NotificationSubsystem r, + Member TeamSubsystem r ) => TeamId -> Feature cfg -> Sem r (LockableFeature cfg) setFeatureInternal tid feat = do - assertTeamExists tid + TeamSubsystem.assertTeamExists tid setFeatureUnchecked tid feat setFeatureUnchecked :: @@ -169,7 +171,7 @@ setFeatureUnchecked :: Member TeamFeatureStore r, Member (P.Logger (Log.Msg -> Log.Msg)) r, Member NotificationSubsystem r, - Member ConversationSubsystem r + Member TeamSubsystem r ) => TeamId -> Feature cfg -> @@ -183,13 +185,13 @@ updateLockStatus :: forall cfg r. ( IsFeatureConfig cfg, Member TeamFeatureStore r, - Member ConversationSubsystem r + Member TeamSubsystem r ) => TeamId -> LockStatus -> Sem r LockStatusResponse updateLockStatus tid lockStatus = do - assertTeamExists tid + TeamSubsystem.assertTeamExists tid setFeatureLockStatus @cfg tid lockStatus pure $ LockStatusResponse lockStatus @@ -210,14 +212,14 @@ pushFeatureEvent :: forall cfg r. ( IsFeatureConfig cfg, Member NotificationSubsystem r, - Member ConversationSubsystem r, + Member TeamSubsystem r, Member P.TinyLog r ) => TeamId -> Event -> Sem r () pushFeatureEvent tid event = do - memList <- getTeamMembersForFanout tid + memList <- TeamSubsystem.getTeamMembersForFanout tid if ((memList ^. teamMemberListType) == ListTruncated) then do P.warn $ @@ -245,9 +247,9 @@ setFeatureForTeam :: SetFeatureForTeamConstraints cfg r, ComputeFeatureConstraints cfg r, Member P.TinyLog r, - Member ConversationSubsystem r, Member NotificationSubsystem r, - Member TeamFeatureStore r + Member TeamFeatureStore r, + Member TeamSubsystem r ) => TeamId -> LockableFeature cfg -> @@ -389,7 +391,7 @@ instance SetFeatureConfig SndFactorPasswordChallengeConfig instance SetFeatureConfig SearchVisibilityInboundConfig where type SetFeatureForTeamConstraints SearchVisibilityInboundConfig (r :: EffectRow) = (Member BrigAPIAccess r) prepareFeature tid feat = do - updateSearchVisibilityInbound $ toTeamStatus tid feat + updateSearchVisibilityInbound $ Multi.TeamStatus tid feat.status instance SetFeatureConfig MLSConfig where type From ade70ed05f550d623e1c209d9b2ad5b15ac062d1 Mon Sep 17 00:00:00 2001 From: Gautier DI FOLCO Date: Wed, 1 Apr 2026 15:28:10 +0200 Subject: [PATCH 08/19] fix(leif): add dedicated error type forConversationSubsystem --- .../Wire/ConversationSubsystem/Interpreter.hs | 603 ++++++++++++------ services/galley/src/Galley/App.hs | 109 +--- 2 files changed, 422 insertions(+), 290 deletions(-) diff --git a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Interpreter.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Interpreter.hs index ec52fe7f82d..55683df334e 100644 --- a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Interpreter.hs @@ -22,12 +22,15 @@ module Wire.ConversationSubsystem.Interpreter GroupInfoCheckEnabled (..), IntraListing (..), Update.GuestLinkTTLSeconds (..), + ConversationSubsystemError (..), ) where import Data.Qualified +import Data.Tagged import Galley.Types.Error (InternalError, InvalidInput (..)) import Imports +import Network.Wai.Utilities.JSONResponse (JSONResponse) import Polysemy import Polysemy.Error import Polysemy.Input @@ -41,6 +44,7 @@ import Wire.API.Error.Galley import Wire.API.Federation.Client (FederatorClient) import Wire.API.Federation.Error import Wire.API.MLS.Keys (MLSKeysByPurpose, MLSPrivateKeys) +import Wire.API.Routes.API (ServerEffect (interpretServerEffect)) import Wire.API.Team.Feature (LegalholdConfig) import Wire.API.Team.FeatureFlags (FanoutLimit, FeatureDefaults, FeatureFlags) import Wire.BackendNotificationQueueAccess (BackendNotificationQueueAccess) @@ -88,76 +92,10 @@ import Wire.UserClientIndexStore (UserClientIndexStore) import Wire.UserGroupStore (UserGroupStore) interpretConversationSubsystem :: - ( Member (Error FederationError) r, - Member (Error UnreachableBackends) r, - Member (Error InternalError) r, - Member (Error InvalidInput) r, - Member (ErrorS 'ConvAccessDenied) r, - Member (ErrorS 'NotATeamMember) r, - Member (ErrorS OperationDenied) r, - Member (Error AuthenticationError) r, + ( Member (Error ConversationSubsystemError) r, + Member (Error JSONResponse) r, + Member (Error DynError) r, Member (Input (FeatureDefaults LegalholdConfig)) r, - Member (ErrorS 'NotConnected) r, - Member (ErrorS 'MLSNotEnabled) r, - Member (ErrorS 'MLSNonEmptyMemberList) r, - Member (ErrorS 'MissingLegalholdConsent) r, - Member (ErrorS 'NonBindingTeam) r, - Member (ErrorS 'NoBindingTeamMembers) r, - Member (ErrorS 'TeamNotFound) r, - Member (ErrorS 'InvalidOperation) r, - Member (ErrorS 'ConvNotFound) r, - Member (ErrorS 'ChannelsNotEnabled) r, - Member (ErrorS 'NotAnMlsConversation) r, - Member (ErrorS 'MLSLegalholdIncompatible) r, - Member (ErrorS 'MLSIdentityMismatch) r, - Member (ErrorS 'MLSUnsupportedMessage) r, - Member (ErrorS 'MLSStaleMessage) r, - Member (ErrorS 'MLSProposalNotFound) r, - Member (ErrorS 'MLSCommitMissingReferences) r, - Member (ErrorS 'MLSSelfRemovalNotAllowed) r, - Member (ErrorS 'MLSClientSenderUserMismatch) r, - Member (ErrorS 'MLSSubConvClientNotInParent) r, - Member (ErrorS 'MLSInvalidLeafNodeSignature) r, - Member (ErrorS 'MLSClientMismatch) r, - Member (ErrorS 'MLSInvalidLeafNodeIndex) r, - Member (ErrorS 'MLSUnsupportedProposal) r, - Member (Error MLSProtocolError) r, - Member (Error GroupInfoDiagnostics) r, - Member (Error MLSOutOfSyncError) r, - Member (Error MLSProposalFailure) r, - Member (Error NonFederatingBackends) r, - Member (ErrorS 'GroupIdVersionNotSupported) r, - Member (ErrorS 'ConvMemberNotFound) r, - Member (ErrorS 'HistoryNotSupported) r, - Member (Error UnreachableBackendsLegacy) r, - Member (ErrorS MLSGroupConversationMismatch) r, - Member (ErrorS ('ActionDenied ConvRole.LeaveConversation)) r, - Member (ErrorS ('ActionDenied ConvRole.RemoveConversationMember)) r, - Member (ErrorS ('ActionDenied ConvRole.DeleteConversation)) r, - Member (ErrorS 'BroadcastLimitExceeded) r, - Member (ErrorS 'MLSFederatedResetNotSupported) r, - Member (ErrorS 'MLSSubConvUnsupportedConvType) r, - Member (ErrorS 'TeamMemberNotFound) r, - Member (ErrorS 'AccessDenied) r, - Member (ErrorS 'MLSMissingGroupInfo) r, - Member (ErrorS 'CodeNotFound) r, - Member (ErrorS 'InvalidConversationPassword) r, - Member (ErrorS 'GuestLinksDisabled) r, - Member (ErrorS 'MLSFederatedOne2OneNotSupported) r, - Member (ErrorS 'TooManyMembers) r, - Member (ErrorS 'CreateConversationCodeConflict) r, - Member (ErrorS 'InvalidTarget) r, - Member (ErrorS 'MLSReadReceiptsNotAllowed) r, - Member (ErrorS 'InvalidTargetAccess) r, - Member (ErrorS 'ConvInvalidProtocolTransition) r, - Member (ErrorS 'MLSMigrationCriteriaNotSatisfied) r, - Member (ErrorS ('ActionDenied ConvRole.AddConversationMember)) r, - Member (ErrorS ('ActionDenied ConvRole.ModifyOtherConversationMember)) r, - Member (ErrorS ('ActionDenied ConvRole.ModifyConversationName)) r, - Member (ErrorS ('ActionDenied ConvRole.ModifyConversationMessageTimer)) r, - Member (ErrorS ('ActionDenied ConvRole.ModifyConversationReceiptMode)) r, - Member (ErrorS ('ActionDenied ConvRole.ModifyConversationAccess)) r, - Member (ErrorS ('ActionDenied ConvRole.ModifyAddPermission)) r, Member UserGroupStore r, Member (Input (Maybe Update.GuestLinkTTLSeconds)) r, Member HashPassword r, @@ -196,245 +134,534 @@ interpretConversationSubsystem :: Sem r a interpretConversationSubsystem = interpretH $ \case NotifyConversationAction tag quid notifyOrigDomain con lconv targetsLocal targetsRemote targetsBots action extraData -> - liftT $ Notify.notifyConversationActionImpl tag quid notifyOrigDomain con lconv targetsLocal targetsRemote targetsBots action extraData + liftT $ mapErrors $ Notify.notifyConversationActionImpl tag quid notifyOrigDomain con lconv targetsLocal targetsRemote targetsBots action extraData InternalCreateGroupConversation lusr conn newConv -> - liftT $ CreateInternal.createGroupConversationGeneric lusr conn newConv + liftT $ mapErrors $ CreateInternal.createGroupConversationGeneric lusr conn newConv CreateGroupConversationUpToV3 lusr conn newConv -> - liftT $ Create.createGroupConversationUpToV3 lusr conn newConv + liftT $ mapErrors $ Create.createGroupConversationUpToV3 lusr conn newConv CreateGroupOwnConversation lusr conn newConv -> - liftT $ Create.createGroupOwnConversation lusr conn newConv + liftT $ mapErrors $ Create.createGroupOwnConversation lusr conn newConv CreateGroupConversation lusr conn newConv -> - liftT $ Create.createGroupConversation lusr conn newConv + liftT $ mapErrors $ Create.createGroupConversation lusr conn newConv CreateProteusSelfConversation lusr -> - liftT $ Create.createProteusSelfConversation lusr + liftT $ mapErrors $ Create.createProteusSelfConversation lusr CreateOne2OneConversation lusr zcon j -> - liftT $ Create.createOne2OneConversation lusr zcon j + liftT $ mapErrors $ Create.createOne2OneConversation lusr zcon j CreateConnectConversation lusr conn j -> - liftT $ Create.createConnectConversation lusr conn j + liftT $ mapErrors $ Create.createConnectConversation lusr conn j GetConversations convIds -> - liftT $ ConvStore.getConversations convIds + liftT $ mapErrors $ ConvStore.getConversations convIds GetConversationIds lusr maxIds pagingState -> - liftT $ Fetch.getConversationIdsImpl lusr maxIds pagingState + liftT $ mapErrors $ Fetch.getConversationIdsImpl lusr maxIds pagingState InternalGetLocalMember cid uid -> - liftT $ ConvStore.getLocalMember cid uid + liftT $ mapErrors $ ConvStore.getLocalMember cid uid PostMLSCommitBundle loc qusr c ctype qConvOrSub conn oosCheck bundle -> - liftT $ MLSMessage.postMLSCommitBundle loc qusr c ctype qConvOrSub conn oosCheck bundle + liftT $ mapErrors $ MLSMessage.postMLSCommitBundle loc qusr c ctype qConvOrSub conn oosCheck bundle PostMLSCommitBundleFromLocalUser v lusr c conn bundle -> - liftT $ MLSMessage.postMLSCommitBundleFromLocalUser v lusr c conn bundle + liftT $ mapErrors $ MLSMessage.postMLSCommitBundleFromLocalUser v lusr c conn bundle PostMLSMessage loc qusr c ctype qconvOrSub con oosCheck msg -> - liftT $ MLSMessage.postMLSMessage loc qusr c ctype qconvOrSub con oosCheck msg + liftT $ mapErrors $ MLSMessage.postMLSMessage loc qusr c ctype qconvOrSub con oosCheck msg PostMLSMessageFromLocalUser v lusr c conn smsg -> - liftT $ MLSMessage.postMLSMessageFromLocalUser v lusr c conn smsg + liftT $ mapErrors $ MLSMessage.postMLSMessageFromLocalUser v lusr c conn smsg IsMLSEnabled -> - liftT $ MLSEnabled.isMLSEnabled + liftT $ mapErrors $ MLSEnabled.isMLSEnabled IterateConversations luid pageSize handleConvs -> do handleConvsT <- bindT handleConvs ins <- getInitialStateT void $ raise $ interpretConversationSubsystem $ Query.iterateConversations luid pageSize $ handleConvsT . ($>) ins pureT () RemoveMemberFromLocalConv lcnv lusr con victim -> - liftT $ Update.removeMemberFromLocalConv lcnv lusr con victim + liftT $ mapErrors $ Update.removeMemberFromLocalConv lcnv lusr con victim FederationOnConversationCreated domain rc -> - liftT $ Federation.onConversationCreated domain rc + liftT $ mapErrors $ Federation.onConversationCreated domain rc FederationGetConversationsV1 domain req -> - liftT $ Federation.getConversationsV1 domain req + liftT $ mapErrors $ Federation.getConversationsV1 domain req FederationGetConversations domain req -> - liftT $ Federation.getConversations domain req + liftT $ mapErrors $ Federation.getConversations domain req FederationLeaveConversation domain lc -> - liftT $ Federation.leaveConversation domain lc + liftT $ mapErrors $ Federation.leaveConversation domain lc FederationSendMessage domain msr -> - liftT $ Federation.sendMessage domain msr + liftT $ mapErrors $ Federation.sendMessage domain msr FederationUpdateConversation domain uc -> - liftT $ Federation.updateConversation domain uc + liftT $ mapErrors $ Federation.updateConversation domain uc FederationMlsSendWelcome domain req -> - liftT $ Federation.mlsSendWelcome domain req + liftT $ mapErrors $ Federation.mlsSendWelcome domain req FederationSendMLSMessage domain msr -> - liftT $ Federation.sendMLSMessage domain msr + liftT $ mapErrors $ Federation.sendMLSMessage domain msr FederationSendMLSCommitBundle domain msr -> - liftT $ Federation.sendMLSCommitBundle domain msr + liftT $ mapErrors $ Federation.sendMLSCommitBundle domain msr FederationQueryGroupInfo domain req -> - liftT $ Federation.queryGroupInfo domain req + liftT $ mapErrors $ Federation.queryGroupInfo domain req FederationUpdateTypingIndicator domain req -> - liftT $ Federation.updateTypingIndicator domain req + liftT $ mapErrors $ Federation.updateTypingIndicator domain req FederationOnTypingIndicatorUpdated domain td -> - liftT $ Federation.onTypingIndicatorUpdated domain td + liftT $ mapErrors $ Federation.onTypingIndicatorUpdated domain td FederationGetSubConversationForRemoteUser domain req -> - liftT $ Federation.getSubConversationForRemoteUser domain req + liftT $ mapErrors $ Federation.getSubConversationForRemoteUser domain req FederationDeleteSubConversationForRemoteUser domain req -> - liftT $ Federation.deleteSubConversationForRemoteUser domain req + liftT $ mapErrors $ Federation.deleteSubConversationForRemoteUser domain req FederationLeaveSubConversation domain lscr -> - liftT $ Federation.leaveSubConversation domain lscr + liftT $ mapErrors $ Federation.leaveSubConversation domain lscr FederationGetOne2OneConversationV1 domain req -> - liftT $ Federation.getOne2OneConversationV1 domain req + liftT $ mapErrors $ Federation.getOne2OneConversationV1 domain req FederationGetOne2OneConversation domain req -> - liftT $ Federation.getOne2OneConversation domain req + liftT $ mapErrors $ Federation.getOne2OneConversation domain req FederationOnClientRemoved domain req -> - liftT $ Federation.onClientRemoved domain req + liftT $ mapErrors $ Federation.onClientRemoved domain req FederationOnMessageSent domain rm -> - liftT $ Federation.onMessageSent domain rm + liftT $ mapErrors $ Federation.onMessageSent domain rm FederationOnMLSMessageSent domain rmm -> - liftT $ Federation.onMLSMessageSent domain rmm + liftT $ mapErrors $ Federation.onMLSMessageSent domain rmm FederationOnConversationUpdatedV0 domain cu -> - liftT $ Federation.onConversationUpdatedV0 domain cu + liftT $ mapErrors $ Federation.onConversationUpdatedV0 domain cu FederationOnConversationUpdated domain cu -> - liftT $ Federation.onConversationUpdated domain cu + liftT $ mapErrors $ Federation.onConversationUpdated domain cu FederationOnUserDeleted domain udcn -> - liftT $ Federation.onUserDeleted domain udcn + liftT $ mapErrors $ Federation.onUserDeleted domain udcn PostOtrMessageUnqualified lusr con cnv ignore report msg -> - liftT $ Update.postOtrMessageUnqualified lusr con cnv ignore report msg + liftT $ mapErrors $ Update.postOtrMessageUnqualified lusr con cnv ignore report msg PostOtrBroadcastUnqualified lusr con ignore report msg -> - liftT $ Update.postOtrBroadcastUnqualified lusr con ignore report msg + liftT $ mapErrors $ Update.postOtrBroadcastUnqualified lusr con ignore report msg PostProteusMessage lusr con cnv msg -> - liftT $ Update.postProteusMessage lusr con cnv msg + liftT $ mapErrors $ Update.postProteusMessage lusr con cnv msg PostProteusBroadcast lusr con msg -> - liftT $ Update.postProteusBroadcast lusr con msg + liftT $ mapErrors $ Update.postProteusBroadcast lusr con msg DeleteLocalConversation lusr con lcnv -> - liftT $ Update.deleteLocalConversation lusr con lcnv + liftT $ mapErrors $ Update.deleteLocalConversation lusr con lcnv GetMLSPublicKeys fmt -> - liftT $ MLS.getMLSPublicKeys fmt + liftT $ mapErrors $ MLS.getMLSPublicKeys fmt ResetMLSConversation lusr reset -> - liftT $ MLSReset.resetMLSConversation lusr reset + liftT $ mapErrors $ MLSReset.resetMLSConversation lusr reset GetSubConversation lusr cnv sub -> - liftT $ MLSSubConversation.getSubConversation lusr cnv sub + liftT $ mapErrors $ MLSSubConversation.getSubConversation lusr cnv sub GetBotConversation bid cnv -> - liftT $ Query.getBotConversation bid cnv + liftT $ mapErrors $ Query.getBotConversation bid cnv GetUnqualifiedOwnConversation lusr cnv -> - liftT $ Query.getUnqualifiedOwnConversation lusr cnv + liftT $ mapErrors $ Query.getUnqualifiedOwnConversation lusr cnv GetOwnConversation lusr qcnv -> - liftT $ Query.getOwnConversation lusr qcnv + liftT $ mapErrors $ Query.getOwnConversation lusr qcnv GetConversation lusr qcnv -> - liftT $ Query.getConversation lusr qcnv + liftT $ mapErrors $ Query.getConversation lusr qcnv InternalGetConversation cnv -> - liftT $ ConvStore.getConversation cnv + liftT $ mapErrors $ ConvStore.getConversation cnv GetConversationRoles lusr cnv -> - liftT $ Query.getConversationRoles lusr cnv + liftT $ mapErrors $ Query.getConversationRoles lusr cnv GetGroupInfo lusr qcnv -> - liftT $ MLSGroupInfo.getGroupInfo lusr qcnv + liftT $ mapErrors $ MLSGroupInfo.getGroupInfo lusr qcnv ConversationIdsPageFromUnqualified lusr mstart msize -> - liftT $ Query.conversationIdsPageFromUnqualified lusr mstart msize + liftT $ mapErrors $ Query.conversationIdsPageFromUnqualified lusr mstart msize ConversationIdsPageFromV2 listGlobalSelf lself req -> - liftT $ Query.conversationIdsPageFromV2 listGlobalSelf lself req + liftT $ mapErrors $ Query.conversationIdsPageFromV2 listGlobalSelf lself req ConversationIdsPageFrom lusr req -> - liftT $ Query.conversationIdsPageFrom lusr req + liftT $ mapErrors $ Query.conversationIdsPageFrom lusr req ListConversations luser req -> - liftT $ Query.listConversations luser req + liftT $ mapErrors $ Query.listConversations luser req GetConversationByReusableCode lusr key value -> - liftT $ Query.getConversationByReusableCode lusr key value + liftT $ mapErrors $ Query.getConversationByReusableCode lusr key value GetMLSSelfConversationWithError lusr -> - liftT $ Query.getMLSSelfConversationWithError lusr + liftT $ mapErrors $ Query.getMLSSelfConversationWithError lusr GetMLSOne2OneConversationV5 lself qother -> - liftT $ Query.getMLSOne2OneConversationV5 lself qother + liftT $ mapErrors $ Query.getMLSOne2OneConversationV5 lself qother GetMLSOne2OneConversationV6 lself qother -> - liftT $ Query.getMLSOne2OneConversationV6 lself qother + liftT $ mapErrors $ Query.getMLSOne2OneConversationV6 lself qother GetMLSOne2OneConversation lself qother fmt -> - liftT $ Query.getMLSOne2OneConversation lself qother fmt + liftT $ mapErrors $ Query.getMLSOne2OneConversation lself qother fmt GetLocalSelf lusr cnv -> - liftT $ Query.getLocalSelf lusr cnv + liftT $ mapErrors $ Query.getLocalSelf lusr cnv GetSelfMember lusr qcnv -> - liftT $ Query.getSelfMember lusr qcnv + liftT $ mapErrors $ Query.getSelfMember lusr qcnv GetConversationGuestLinksStatus uid cid -> - liftT $ Query.getConversationGuestLinksStatus uid cid + liftT $ mapErrors $ Query.getConversationGuestLinksStatus uid cid GetCode mcode lusr cnv -> - liftT $ Update.getCode mcode lusr cnv + liftT $ mapErrors $ Update.getCode mcode lusr cnv AddMembersUnqualified lusr con cnv invite -> - liftT $ Update.addMembersUnqualified lusr con cnv invite + liftT $ mapErrors $ Update.addMembersUnqualified lusr con cnv invite AddMembersUnqualifiedV2 lusr con cnv invite -> - liftT $ Update.addMembersUnqualifiedV2 lusr con cnv invite + liftT $ mapErrors $ Update.addMembersUnqualifiedV2 lusr con cnv invite AddMembers lusr zcon qcnv invite -> - liftT $ Update.addMembers lusr zcon qcnv invite + liftT $ mapErrors $ Update.addMembers lusr zcon qcnv invite ReplaceMembers lusr zcon qcnv invite -> - liftT $ Update.replaceMembers lusr zcon qcnv invite + liftT $ mapErrors $ Update.replaceMembers lusr zcon qcnv invite JoinConversationById lusr con cnv -> - liftT $ Update.joinConversationById lusr con cnv + liftT $ mapErrors $ Update.joinConversationById lusr con cnv JoinConversationByReusableCode lusr con req -> - liftT $ Update.joinConversationByReusableCode lusr con req + liftT $ mapErrors $ Update.joinConversationByReusableCode lusr con req CheckReusableCode addr code -> - liftT $ Update.checkReusableCode addr code + liftT $ mapErrors $ Update.checkReusableCode addr code AddCodeUnqualified mReq usr mbZHost mZcon cnv -> - liftT $ Update.addCodeUnqualified mReq usr mbZHost mZcon cnv + liftT $ mapErrors $ Update.addCodeUnqualified mReq usr mbZHost mZcon cnv AddCodeUnqualifiedWithReqBody lusr mname mconn cnv req -> - liftT $ Update.addCodeUnqualifiedWithReqBody lusr mname mconn cnv req + liftT $ mapErrors $ Update.addCodeUnqualifiedWithReqBody lusr mname mconn cnv req RmCodeUnqualified lusr con cnv -> - liftT $ Update.rmCodeUnqualified lusr con cnv + liftT $ mapErrors $ Update.rmCodeUnqualified lusr con cnv MemberTypingUnqualified lusr con cnv status -> - liftT $ Update.memberTypingUnqualified lusr con cnv status + liftT $ mapErrors $ Update.memberTypingUnqualified lusr con cnv status MemberTyping lusr con qcnv status -> - liftT $ Update.memberTyping lusr con qcnv status + liftT $ mapErrors $ Update.memberTyping lusr con qcnv status RemoveMemberUnqualified lusr con cnv uid -> - liftT $ Update.removeMemberUnqualified lusr con cnv uid + liftT $ mapErrors $ Update.removeMemberUnqualified lusr con cnv uid RemoveMemberQualified lusr con qcnv quid -> - liftT $ Update.removeMemberQualified lusr con qcnv quid + liftT $ mapErrors $ Update.removeMemberQualified lusr con qcnv quid UpdateOtherMemberUnqualified lusr con cnv uid update -> - liftT $ Update.updateOtherMemberUnqualified lusr con cnv uid update + liftT $ mapErrors $ Update.updateOtherMemberUnqualified lusr con cnv uid update UpdateOtherMember lusr con qcnv quid update -> - liftT $ Update.updateOtherMember lusr con qcnv quid update + liftT $ mapErrors $ Update.updateOtherMember lusr con qcnv quid update UpdateUnqualifiedConversationName lusr con cnv rename -> - liftT $ Update.updateUnqualifiedConversationName lusr con cnv rename + liftT $ mapErrors $ Update.updateUnqualifiedConversationName lusr con cnv rename UpdateConversationName lusr zcon qcnv rename -> - liftT $ Update.updateConversationName lusr zcon qcnv rename + liftT $ mapErrors $ Update.updateConversationName lusr zcon qcnv rename UpdateConversationMessageTimerUnqualified lusr con cnv update -> - liftT $ Update.updateConversationMessageTimerUnqualified lusr con cnv update + liftT $ mapErrors $ Update.updateConversationMessageTimerUnqualified lusr con cnv update UpdateConversationMessageTimer lusr zcon qcnv update -> - liftT $ Update.updateConversationMessageTimer lusr zcon qcnv update + liftT $ mapErrors $ Update.updateConversationMessageTimer lusr zcon qcnv update UpdateConversationReceiptModeUnqualified lusr con cnv update -> - liftT $ Update.updateConversationReceiptModeUnqualified lusr con cnv update + liftT $ mapErrors $ Update.updateConversationReceiptModeUnqualified lusr con cnv update UpdateConversationReceiptMode lusr zcon qcnv update -> - liftT $ Update.updateConversationReceiptMode lusr zcon qcnv update + liftT $ mapErrors $ Update.updateConversationReceiptMode lusr zcon qcnv update UpdateConversationAccessUnqualified lusr con cnv update -> - liftT $ Update.updateConversationAccessUnqualified lusr con cnv update + liftT $ mapErrors $ Update.updateConversationAccessUnqualified lusr con cnv update UpdateConversationAccess lusr zcon qcnv update -> - liftT $ Update.updateConversationAccess lusr zcon qcnv update + liftT $ mapErrors $ Update.updateConversationAccess lusr zcon qcnv update UpdateConversationHistory lusr zcon qcnv update -> - liftT $ Update.updateConversationHistory lusr zcon qcnv update + liftT $ mapErrors $ Update.updateConversationHistory lusr zcon qcnv update UpdateUnqualifiedSelfMember lusr con cnv update -> - liftT $ Update.updateUnqualifiedSelfMember lusr con cnv update + liftT $ mapErrors $ Update.updateUnqualifiedSelfMember lusr con cnv update UpdateSelfMember lusr zcon qcnv update -> - liftT $ Update.updateSelfMember lusr zcon qcnv update + liftT $ mapErrors $ Update.updateSelfMember lusr zcon qcnv update UpdateConversationProtocolWithLocalUser lusr conn qcnv update -> - liftT $ Update.updateConversationProtocolWithLocalUser lusr conn qcnv update + liftT $ mapErrors $ Update.updateConversationProtocolWithLocalUser lusr conn qcnv update UpdateChannelAddPermission lusr conn qcnv update -> - liftT $ Update.updateChannelAddPermission lusr conn qcnv update + liftT $ mapErrors $ Update.updateChannelAddPermission lusr conn qcnv update PostBotMessageUnqualified bid cnv ignore report msg -> - liftT $ Update.postBotMessageUnqualified bid cnv ignore report msg + liftT $ mapErrors $ Update.postBotMessageUnqualified bid cnv ignore report msg DeleteSubConversation lusr qcnv sub reset -> - liftT $ MLSSubConversation.deleteSubConversation lusr qcnv sub reset + liftT $ mapErrors $ MLSSubConversation.deleteSubConversation lusr qcnv sub reset GetSubConversationGroupInfo lusr qcnv sub -> - liftT $ MLSSubConversation.getSubConversationGroupInfo lusr qcnv sub + liftT $ mapErrors $ MLSSubConversation.getSubConversationGroupInfo lusr qcnv sub LeaveSubConversation lusr cli qcnv sub -> - liftT $ MLSSubConversation.leaveSubConversation lusr cli qcnv sub + liftT $ mapErrors $ MLSSubConversation.leaveSubConversation lusr cli qcnv sub SendConversationActionNotifications tag quid notifyOrigDomain con lconv targets action extraData -> - liftT $ ActionNotify.sendConversationActionNotifications tag quid notifyOrigDomain con lconv targets action extraData + liftT $ mapErrors $ ActionNotify.sendConversationActionNotifications tag quid notifyOrigDomain con lconv targets action extraData GetPaginatedConversations lusr mids mstart msize -> - liftT $ Query.getConversations lusr mids mstart msize + liftT $ mapErrors $ Query.getConversations lusr mids mstart msize SearchChannels lusr tid searchString sortOrder pageSize lastName lastId discoverable -> - liftT $ Query.searchChannels lusr tid searchString sortOrder pageSize lastName lastId discoverable + liftT $ mapErrors $ Query.searchChannels lusr tid searchString sortOrder pageSize lastName lastId discoverable QualifyLocal a -> - liftT $ Util.qualifyLocal a + liftT $ mapErrors $ Util.qualifyLocal a InternalGetMember qcnv usr -> - liftT $ Query.internalGetMember qcnv usr + liftT $ mapErrors $ Query.internalGetMember qcnv usr GetConversationMeta cnv -> - liftT $ Query.getConversationMeta cnv + liftT $ mapErrors $ Query.getConversationMeta cnv GetMLSOne2OneConversationInternal lself qother -> - liftT $ Query.getMLSOne2OneConversationInternal lself qother + liftT $ mapErrors $ Query.getMLSOne2OneConversationInternal lself qother IsMLSOne2OneEstablished lself qother -> - liftT $ Query.isMLSOne2OneEstablished lself qother + liftT $ mapErrors $ Query.isMLSOne2OneEstablished lself qother GetLocalConversationInternal cid -> - liftT $ Query.getLocalConversationInternal cid + liftT $ mapErrors $ Query.getLocalConversationInternal cid RemoveClient lc qusr c -> - liftT $ MLSRemoval.removeClient lc qusr c + liftT $ mapErrors $ MLSRemoval.removeClient lc qusr c AddBot lusr zcon b -> - liftT $ Update.addBot lusr zcon b + liftT $ mapErrors $ Update.addBot lusr zcon b RmBot lusr zcon b -> - liftT $ Update.rmBot lusr zcon b + liftT $ mapErrors $ Update.rmBot lusr zcon b UpdateCellsState cnv state -> - liftT $ Update.updateCellsState cnv state + liftT $ mapErrors $ Update.updateCellsState cnv state RemoveUser lc includeMain qusr -> - liftT $ MLSRemoval.removeUser lc includeMain qusr + liftT $ mapErrors $ MLSRemoval.removeUser lc includeMain qusr InternalUpsertOne2OneConversation req -> - liftT $ One2One.internalUpsertOne2OneConversation req + liftT $ mapErrors $ One2One.internalUpsertOne2OneConversation req AcceptConv lusr conn cnv -> - liftT $ Update.acceptConv lusr conn cnv + liftT $ mapErrors $ Update.acceptConv lusr conn cnv BlockConv lusr qcnv -> - liftT $ Update.blockConv lusr qcnv + liftT $ mapErrors $ Update.blockConv lusr qcnv UnblockConv lusr conn qcnv -> - liftT $ Update.unblockConv lusr conn qcnv + liftT $ mapErrors $ Update.unblockConv lusr conn qcnv + +data ConversationSubsystemError + = ConversationSubsystemErrorConvAccessDenied + | ConversationSubsystemErrorNotATeamMember + | ConversationSubsystemErrorperationDenied + | ConversationSubsystemErrorNotConnected + | ConversationSubsystemErrorMLSNotEnabled + | ConversationSubsystemErrorMLSNonEmptyMemberList + | ConversationSubsystemErrorMissingLegalholdConsent + | ConversationSubsystemErrorNonBindingTeam + | ConversationSubsystemErrorNoBindingTeamMembers + | ConversationSubsystemErrorTeamNotFound + | ConversationSubsystemErrorInvalidOperation + | ConversationSubsystemErrorConvNotFound + | ConversationSubsystemErrorChannelsNotEnabled + | ConversationSubsystemErrorNotAnMlsConversation + | ConversationSubsystemErrorMLSLegalholdIncompatible + | ConversationSubsystemErrorMLSIdentityMismatch + | ConversationSubsystemErrorMLSUnsupportedMessage + | ConversationSubsystemErrorMLSStaleMessage + | ConversationSubsystemErrorMLSProposalNotFound + | ConversationSubsystemErrorMLSCommitMissingReferences + | ConversationSubsystemErrorMLSSelfRemovalNotAllowed + | ConversationSubsystemErrorMLSClientSenderUserMismatch + | ConversationSubsystemErrorMLSSubConvClientNotInParent + | ConversationSubsystemErrorMLSInvalidLeafNodeSignature + | ConversationSubsystemErrorMLSClientMismatch + | ConversationSubsystemErrorMLSInvalidLeafNodeIndex + | ConversationSubsystemErrorMLSUnsupportedProposal + | ConversationSubsystemErrorGroupIdVersionNotSupported + | ConversationSubsystemErrorConvMemberNotFound + | ConversationSubsystemErrorHistoryNotSupported + | ConversationSubsystemErrorLSGroupConversationMismatch + | ConversationSubsystemErrorActionDeniedLeaveConversation + | ConversationSubsystemErrorActionDeniedRemoveConversationMember + | ConversationSubsystemErrorActionDeniedDeleteConversation + | ConversationSubsystemErrorBroadcastLimitExceeded + | ConversationSubsystemErrorMLSFederatedResetNotSupported + | ConversationSubsystemErrorMLSSubConvUnsupportedConvType + | ConversationSubsystemErrorTeamMemberNotFound + | ConversationSubsystemErrorAccessDenied + | ConversationSubsystemErrorMLSMissingGroupInfo + | ConversationSubsystemErrorCodeNotFound + | ConversationSubsystemErrorInvalidConversationPassword + | ConversationSubsystemErrorGuestLinksDisabled + | ConversationSubsystemErrorMLSFederatedOne2OneNotSupported + | ConversationSubsystemErrorTooManyMembers + | ConversationSubsystemErrorCreateConversationCodeConflict + | ConversationSubsystemErrorInvalidTarget + | ConversationSubsystemErrorMLSReadReceiptsNotAllowed + | ConversationSubsystemErrorInvalidTargetAccess + | ConversationSubsystemErrorConvInvalidProtocolTransition + | ConversationSubsystemErrorMLSMigrationCriteriaNotSatisfied + | ConversationSubsystemErrorActionDeniedAddConversationMember + | ConversationSubsystemErrorActionDeniedModifyOtherConversationMember + | ConversationSubsystemErrorActionDeniedModifyConversationName + | ConversationSubsystemErrorActionDeniedModifyConversationMessageTimer + | ConversationSubsystemErrorActionDeniedModifyConversationReceiptMode + | ConversationSubsystemErrorActionDeniedModifyConversationAccess + | ConversationSubsystemErrorActionDeniedModifyAddPermission + | ConversationSubsystemErrorFederationError FederationError + | ConversationSubsystemErrorUnreachableBackends UnreachableBackends + | ConversationSubsystemErrorInternalError InternalError + | ConversationSubsystemErrorInvalidInput InvalidInput + | ConversationSubsystemErrorMLSProtocolError MLSProtocolError + | ConversationSubsystemErrorGroupInfoDiagnostics GroupInfoDiagnostics + | ConversationSubsystemErrorMLSOutOfSyncError MLSOutOfSyncError + | ConversationSubsystemErrorNonFederatingBackends NonFederatingBackends + | ConversationSubsystemErrorUnreachableBackendsLegacy UnreachableBackendsLegacy + +instance APIError ConversationSubsystemError where + toResponse = + \case + ConversationSubsystemErrorConvAccessDenied -> toResponse $ Tagged @'ConvAccessDenied () + ConversationSubsystemErrorNotATeamMember -> toResponse $ Tagged @'NotATeamMember () + ConversationSubsystemErrorperationDenied -> toResponse $ Tagged @OperationDenied () + ConversationSubsystemErrorNotConnected -> toResponse $ Tagged @'NotConnected () + ConversationSubsystemErrorMLSNotEnabled -> toResponse $ Tagged @'MLSNotEnabled () + ConversationSubsystemErrorMLSNonEmptyMemberList -> toResponse $ Tagged @'MLSNonEmptyMemberList () + ConversationSubsystemErrorMissingLegalholdConsent -> toResponse $ Tagged @'MissingLegalholdConsent () + ConversationSubsystemErrorNonBindingTeam -> toResponse $ Tagged @'NonBindingTeam () + ConversationSubsystemErrorNoBindingTeamMembers -> toResponse $ Tagged @'NoBindingTeamMembers () + ConversationSubsystemErrorTeamNotFound -> toResponse $ Tagged @'TeamNotFound () + ConversationSubsystemErrorInvalidOperation -> toResponse $ Tagged @'InvalidOperation () + ConversationSubsystemErrorConvNotFound -> toResponse $ Tagged @'ConvNotFound () + ConversationSubsystemErrorChannelsNotEnabled -> toResponse $ Tagged @'ChannelsNotEnabled () + ConversationSubsystemErrorNotAnMlsConversation -> toResponse $ Tagged @'NotAnMlsConversation () + ConversationSubsystemErrorMLSLegalholdIncompatible -> toResponse $ Tagged @'MLSLegalholdIncompatible () + ConversationSubsystemErrorMLSIdentityMismatch -> toResponse $ Tagged @'MLSIdentityMismatch () + ConversationSubsystemErrorMLSUnsupportedMessage -> toResponse $ Tagged @'MLSUnsupportedMessage () + ConversationSubsystemErrorMLSStaleMessage -> toResponse $ Tagged @'MLSStaleMessage () + ConversationSubsystemErrorMLSProposalNotFound -> toResponse $ Tagged @'MLSProposalNotFound () + ConversationSubsystemErrorMLSCommitMissingReferences -> toResponse $ Tagged @'MLSCommitMissingReferences () + ConversationSubsystemErrorMLSSelfRemovalNotAllowed -> toResponse $ Tagged @'MLSSelfRemovalNotAllowed () + ConversationSubsystemErrorMLSClientSenderUserMismatch -> toResponse $ Tagged @'MLSClientSenderUserMismatch () + ConversationSubsystemErrorMLSSubConvClientNotInParent -> toResponse $ Tagged @'MLSSubConvClientNotInParent () + ConversationSubsystemErrorMLSInvalidLeafNodeSignature -> toResponse $ Tagged @'MLSInvalidLeafNodeSignature () + ConversationSubsystemErrorMLSClientMismatch -> toResponse $ Tagged @'MLSClientMismatch () + ConversationSubsystemErrorMLSInvalidLeafNodeIndex -> toResponse $ Tagged @'MLSInvalidLeafNodeIndex () + ConversationSubsystemErrorMLSUnsupportedProposal -> toResponse $ Tagged @'MLSUnsupportedProposal () + ConversationSubsystemErrorGroupIdVersionNotSupported -> toResponse $ Tagged @'GroupIdVersionNotSupported () + ConversationSubsystemErrorConvMemberNotFound -> toResponse $ Tagged @'ConvMemberNotFound () + ConversationSubsystemErrorHistoryNotSupported -> toResponse $ Tagged @'HistoryNotSupported () + ConversationSubsystemErrorLSGroupConversationMismatch -> toResponse $ Tagged @MLSGroupConversationMismatch () + ConversationSubsystemErrorActionDeniedLeaveConversation -> toResponse $ Tagged @('ActionDenied ConvRole.LeaveConversation) () + ConversationSubsystemErrorActionDeniedRemoveConversationMember -> toResponse $ Tagged @('ActionDenied ConvRole.RemoveConversationMember) () + ConversationSubsystemErrorActionDeniedDeleteConversation -> toResponse $ Tagged @('ActionDenied ConvRole.DeleteConversation) () + ConversationSubsystemErrorBroadcastLimitExceeded -> toResponse $ Tagged @'BroadcastLimitExceeded () + ConversationSubsystemErrorMLSFederatedResetNotSupported -> toResponse $ Tagged @'MLSFederatedResetNotSupported () + ConversationSubsystemErrorMLSSubConvUnsupportedConvType -> toResponse $ Tagged @'MLSSubConvUnsupportedConvType () + ConversationSubsystemErrorTeamMemberNotFound -> toResponse $ Tagged @'TeamMemberNotFound () + ConversationSubsystemErrorAccessDenied -> toResponse $ Tagged @'AccessDenied () + ConversationSubsystemErrorMLSMissingGroupInfo -> toResponse $ Tagged @'MLSMissingGroupInfo () + ConversationSubsystemErrorCodeNotFound -> toResponse $ Tagged @'CodeNotFound () + ConversationSubsystemErrorInvalidConversationPassword -> toResponse $ Tagged @'InvalidConversationPassword () + ConversationSubsystemErrorGuestLinksDisabled -> toResponse $ Tagged @'GuestLinksDisabled () + ConversationSubsystemErrorMLSFederatedOne2OneNotSupported -> toResponse $ Tagged @'MLSFederatedOne2OneNotSupported () + ConversationSubsystemErrorTooManyMembers -> toResponse $ Tagged @'TooManyMembers () + ConversationSubsystemErrorCreateConversationCodeConflict -> toResponse $ Tagged @'CreateConversationCodeConflict () + ConversationSubsystemErrorInvalidTarget -> toResponse $ Tagged @'InvalidTarget () + ConversationSubsystemErrorMLSReadReceiptsNotAllowed -> toResponse $ Tagged @'MLSReadReceiptsNotAllowed () + ConversationSubsystemErrorInvalidTargetAccess -> toResponse $ Tagged @'InvalidTargetAccess () + ConversationSubsystemErrorConvInvalidProtocolTransition -> toResponse $ Tagged @'ConvInvalidProtocolTransition () + ConversationSubsystemErrorMLSMigrationCriteriaNotSatisfied -> toResponse $ Tagged @'MLSMigrationCriteriaNotSatisfied () + ConversationSubsystemErrorActionDeniedAddConversationMember -> toResponse $ Tagged @('ActionDenied ConvRole.AddConversationMember) () + ConversationSubsystemErrorActionDeniedModifyOtherConversationMember -> toResponse $ Tagged @('ActionDenied ConvRole.ModifyOtherConversationMember) () + ConversationSubsystemErrorActionDeniedModifyConversationName -> toResponse $ Tagged @('ActionDenied ConvRole.ModifyConversationName) () + ConversationSubsystemErrorActionDeniedModifyConversationMessageTimer -> toResponse $ Tagged @('ActionDenied ConvRole.ModifyConversationMessageTimer) () + ConversationSubsystemErrorActionDeniedModifyConversationReceiptMode -> toResponse $ Tagged @('ActionDenied ConvRole.ModifyConversationReceiptMode) () + ConversationSubsystemErrorActionDeniedModifyConversationAccess -> toResponse $ Tagged @('ActionDenied ConvRole.ModifyConversationAccess) () + ConversationSubsystemErrorActionDeniedModifyAddPermission -> toResponse $ Tagged @('ActionDenied ConvRole.ModifyAddPermission) () + ConversationSubsystemErrorFederationError x -> toResponse x + ConversationSubsystemErrorUnreachableBackends x -> toResponse x + ConversationSubsystemErrorInternalError x -> toResponse x + ConversationSubsystemErrorInvalidInput x -> toResponse x + ConversationSubsystemErrorMLSProtocolError x -> toResponse $ (dynError @(MapError 'MLSProtocolErrorTag)) {eMessage = unTagged x} + ConversationSubsystemErrorGroupInfoDiagnostics x -> toResponse x + ConversationSubsystemErrorMLSOutOfSyncError x -> toResponse x + ConversationSubsystemErrorNonFederatingBackends x -> toResponse x + ConversationSubsystemErrorUnreachableBackendsLegacy x -> toResponse x + +type ConversationSubsystemErrorEffects = + '[ ErrorS 'ConvAccessDenied, + ErrorS 'NotATeamMember, + ErrorS OperationDenied, + ErrorS 'NotConnected, + ErrorS 'MLSNotEnabled, + ErrorS 'MLSNonEmptyMemberList, + ErrorS 'MissingLegalholdConsent, + ErrorS 'NonBindingTeam, + ErrorS 'NoBindingTeamMembers, + ErrorS 'TeamNotFound, + ErrorS 'InvalidOperation, + ErrorS 'ConvNotFound, + ErrorS 'ChannelsNotEnabled, + ErrorS 'NotAnMlsConversation, + ErrorS 'MLSLegalholdIncompatible, + ErrorS 'MLSIdentityMismatch, + ErrorS 'MLSUnsupportedMessage, + ErrorS 'MLSStaleMessage, + ErrorS 'MLSProposalNotFound, + ErrorS 'MLSCommitMissingReferences, + ErrorS 'MLSSelfRemovalNotAllowed, + ErrorS 'MLSClientSenderUserMismatch, + ErrorS 'MLSSubConvClientNotInParent, + ErrorS 'MLSInvalidLeafNodeSignature, + ErrorS 'MLSClientMismatch, + ErrorS 'MLSInvalidLeafNodeIndex, + ErrorS 'MLSUnsupportedProposal, + ErrorS 'GroupIdVersionNotSupported, + ErrorS 'ConvMemberNotFound, + ErrorS 'HistoryNotSupported, + ErrorS MLSGroupConversationMismatch, + ErrorS ('ActionDenied ConvRole.LeaveConversation), + ErrorS ('ActionDenied ConvRole.RemoveConversationMember), + ErrorS ('ActionDenied ConvRole.DeleteConversation), + ErrorS 'BroadcastLimitExceeded, + ErrorS 'MLSFederatedResetNotSupported, + ErrorS 'MLSSubConvUnsupportedConvType, + ErrorS 'TeamMemberNotFound, + ErrorS 'AccessDenied, + ErrorS 'MLSMissingGroupInfo, + ErrorS 'CodeNotFound, + ErrorS 'InvalidConversationPassword, + ErrorS 'GuestLinksDisabled, + ErrorS 'MLSFederatedOne2OneNotSupported, + ErrorS 'TooManyMembers, + ErrorS 'CreateConversationCodeConflict, + ErrorS 'InvalidTarget, + ErrorS 'MLSReadReceiptsNotAllowed, + ErrorS 'InvalidTargetAccess, + ErrorS 'ConvInvalidProtocolTransition, + ErrorS 'MLSMigrationCriteriaNotSatisfied, + ErrorS ('ActionDenied ConvRole.AddConversationMember), + ErrorS ('ActionDenied ConvRole.ModifyOtherConversationMember), + ErrorS ('ActionDenied ConvRole.ModifyConversationName), + ErrorS ('ActionDenied ConvRole.ModifyConversationMessageTimer), + ErrorS ('ActionDenied ConvRole.ModifyConversationReceiptMode), + ErrorS ('ActionDenied ConvRole.ModifyConversationAccess), + ErrorS ('ActionDenied ConvRole.ModifyAddPermission), + Error FederationError, + Error UnreachableBackends, + Error InternalError, + Error InvalidInput, + Error AuthenticationError, + Error MLSProtocolError, + Error GroupInfoDiagnostics, + Error MLSOutOfSyncError, + Error MLSProposalFailure, + Error NonFederatingBackends, + Error UnreachableBackendsLegacy + ] + +mapErrors :: + ( Member (Error ConversationSubsystemError) r, + Member (Error JSONResponse) r, + Member (Error DynError) r + ) => + InterpretersFor ConversationSubsystemErrorEffects r +mapErrors = + mapError (ConversationSubsystemErrorUnreachableBackendsLegacy) + . mapError (ConversationSubsystemErrorNonFederatingBackends) + . interpretServerEffect + . mapError (ConversationSubsystemErrorMLSOutOfSyncError) + . mapError (ConversationSubsystemErrorGroupInfoDiagnostics) + . mapError (ConversationSubsystemErrorMLSProtocolError) + . interpretServerEffect + . mapError (ConversationSubsystemErrorInvalidInput) + . mapError (ConversationSubsystemErrorInternalError) + . mapError (ConversationSubsystemErrorUnreachableBackends) + . mapError (ConversationSubsystemErrorFederationError) + . mapError (const ConversationSubsystemErrorActionDeniedModifyAddPermission) + . mapError (const ConversationSubsystemErrorActionDeniedModifyConversationAccess) + . mapError (const ConversationSubsystemErrorActionDeniedModifyConversationReceiptMode) + . mapError (const ConversationSubsystemErrorActionDeniedModifyConversationMessageTimer) + . mapError (const ConversationSubsystemErrorActionDeniedModifyConversationName) + . mapError (const ConversationSubsystemErrorActionDeniedModifyOtherConversationMember) + . mapError (const ConversationSubsystemErrorActionDeniedAddConversationMember) + . mapError (const ConversationSubsystemErrorMLSMigrationCriteriaNotSatisfied) + . mapError (const ConversationSubsystemErrorConvInvalidProtocolTransition) + . mapError (const ConversationSubsystemErrorInvalidTargetAccess) + . mapError (const ConversationSubsystemErrorMLSReadReceiptsNotAllowed) + . mapError (const ConversationSubsystemErrorInvalidTarget) + . mapError (const ConversationSubsystemErrorCreateConversationCodeConflict) + . mapError (const ConversationSubsystemErrorTooManyMembers) + . mapError (const ConversationSubsystemErrorMLSFederatedOne2OneNotSupported) + . mapError (const ConversationSubsystemErrorGuestLinksDisabled) + . mapError (const ConversationSubsystemErrorInvalidConversationPassword) + . mapError (const ConversationSubsystemErrorCodeNotFound) + . mapError (const ConversationSubsystemErrorMLSMissingGroupInfo) + . mapError (const ConversationSubsystemErrorAccessDenied) + . mapError (const ConversationSubsystemErrorTeamMemberNotFound) + . mapError (const ConversationSubsystemErrorMLSSubConvUnsupportedConvType) + . mapError (const ConversationSubsystemErrorMLSFederatedResetNotSupported) + . mapError (const ConversationSubsystemErrorBroadcastLimitExceeded) + . mapError (const ConversationSubsystemErrorActionDeniedDeleteConversation) + . mapError (const ConversationSubsystemErrorActionDeniedRemoveConversationMember) + . mapError (const ConversationSubsystemErrorActionDeniedLeaveConversation) + . mapError (const ConversationSubsystemErrorLSGroupConversationMismatch) + . mapError (const ConversationSubsystemErrorHistoryNotSupported) + . mapError (const ConversationSubsystemErrorConvMemberNotFound) + . mapError (const ConversationSubsystemErrorGroupIdVersionNotSupported) + . mapError (const ConversationSubsystemErrorMLSUnsupportedProposal) + . mapError (const ConversationSubsystemErrorMLSInvalidLeafNodeIndex) + . mapError (const ConversationSubsystemErrorMLSClientMismatch) + . mapError (const ConversationSubsystemErrorMLSInvalidLeafNodeSignature) + . mapError (const ConversationSubsystemErrorMLSSubConvClientNotInParent) + . mapError (const ConversationSubsystemErrorMLSClientSenderUserMismatch) + . mapError (const ConversationSubsystemErrorMLSSelfRemovalNotAllowed) + . mapError (const ConversationSubsystemErrorMLSCommitMissingReferences) + . mapError (const ConversationSubsystemErrorMLSProposalNotFound) + . mapError (const ConversationSubsystemErrorMLSStaleMessage) + . mapError (const ConversationSubsystemErrorMLSUnsupportedMessage) + . mapError (const ConversationSubsystemErrorMLSIdentityMismatch) + . mapError (const ConversationSubsystemErrorMLSLegalholdIncompatible) + . mapError (const ConversationSubsystemErrorNotAnMlsConversation) + . mapError (const ConversationSubsystemErrorChannelsNotEnabled) + . mapError (const ConversationSubsystemErrorConvNotFound) + . mapError (const ConversationSubsystemErrorInvalidOperation) + . mapError (const ConversationSubsystemErrorTeamNotFound) + . mapError (const ConversationSubsystemErrorNoBindingTeamMembers) + . mapError (const ConversationSubsystemErrorNonBindingTeam) + . mapError (const ConversationSubsystemErrorMissingLegalholdConsent) + . mapError (const ConversationSubsystemErrorMLSNonEmptyMemberList) + . mapError (const ConversationSubsystemErrorMLSNotEnabled) + . mapError (const ConversationSubsystemErrorNotConnected) + . mapError (const ConversationSubsystemErrorperationDenied) + . mapError (const ConversationSubsystemErrorNotATeamMember) + . mapError (const ConversationSubsystemErrorConvAccessDenied) diff --git a/services/galley/src/Galley/App.hs b/services/galley/src/Galley/App.hs index e0d3ae7876d..3e0072901a0 100644 --- a/services/galley/src/Galley/App.hs +++ b/services/galley/src/Galley/App.hs @@ -81,7 +81,6 @@ import Polysemy.Resource import Polysemy.TinyLog (TinyLog, logErrors) import Polysemy.TinyLog qualified as P import Servant qualified -import Servant.Server (Tagged (unTagged)) import Ssl.Util import System.Logger qualified as Log import System.Logger.Class (Logger) @@ -89,13 +88,11 @@ import System.Logger.Extended qualified as Logger import UnliftIO.Exception qualified as UnliftIO import Wire.API.Conversation.Config (ConversationSubsystemConfig (..)) import Wire.API.Conversation.Protocol -import Wire.API.Conversation.Role qualified as Role import Wire.API.Error -import Wire.API.Error.Galley (AuthenticationError, GalleyError (..), GroupInfoDiagnostics, MLSOutOfSyncError, MLSProposalFailure, MLSProtocolError, NonFederatingBackends, OperationDenied, UnreachableBackends, UnreachableBackendsLegacy) +import Wire.API.Error.Galley (GalleyError (..), NonFederatingBackends, OperationDenied, UnreachableBackends) import Wire.API.Federation.Client import Wire.API.Federation.Error import Wire.API.MLS.Keys (MLSKeysByPurpose, MLSPrivateKeys) -import Wire.API.Routes.API (ServerEffect (interpretServerEffect)) import Wire.API.Team.Collaborator import Wire.API.Team.Feature import Wire.API.Team.FeatureFlags @@ -112,7 +109,7 @@ import Wire.ConversationStore (ConversationStore, MLSCommitLockStore) import Wire.ConversationStore.Cassandra import Wire.ConversationStore.Postgres import Wire.ConversationSubsystem -import Wire.ConversationSubsystem.Interpreter (GroupInfoCheckEnabled (..), GuestLinkTTLSeconds, IntraListing (IntraListing), interpretConversationSubsystem) +import Wire.ConversationSubsystem.Interpreter (ConversationSubsystemError, GroupInfoCheckEnabled (..), GuestLinkTTLSeconds, IntraListing (IntraListing), interpretConversationSubsystem) import Wire.CustomBackendStore import Wire.CustomBackendStore.Cassandra import Wire.Error @@ -244,58 +241,12 @@ type GalleyEffects = Now, GE.Queue DeleteItem, Error Meeting.MeetingError, - Error AuthenticationError, - Error MLSProtocolError, - Error MLSProposalFailure, Error DynError, Error RateLimitExceeded, - Error UnreachableBackendsLegacy, - Error GroupInfoDiagnostics, - Error MLSOutOfSyncError, - ErrorS 'MLSMigrationCriteriaNotSatisfied, - ErrorS 'ConvInvalidProtocolTransition, - ErrorS 'InvalidTargetAccess, - ErrorS 'MLSReadReceiptsNotAllowed, - ErrorS 'InvalidTarget, - ErrorS 'CreateConversationCodeConflict, - ErrorS 'TooManyMembers, - ErrorS 'MLSFederatedOne2OneNotSupported, - ErrorS 'GuestLinksDisabled, - ErrorS 'InvalidConversationPassword, - ErrorS 'CodeNotFound, - ErrorS 'MLSMissingGroupInfo, + Error ConversationSubsystemError, + ErrorS OperationDenied, ErrorS 'AccessDenied, ErrorS 'TeamMemberNotFound, - ErrorS 'MLSSubConvUnsupportedConvType, - ErrorS 'MLSFederatedResetNotSupported, - ErrorS 'BroadcastLimitExceeded, - ErrorS 'MLSGroupConversationMismatch, - ErrorS 'ConvMemberNotFound, - ErrorS 'GroupIdVersionNotSupported, - ErrorS 'MLSUnsupportedProposal, - ErrorS 'MLSInvalidLeafNodeIndex, - ErrorS 'MLSClientMismatch, - ErrorS 'MLSInvalidLeafNodeSignature, - ErrorS 'MLSSubConvClientNotInParent, - ErrorS 'MLSClientSenderUserMismatch, - ErrorS 'MLSSelfRemovalNotAllowed, - ErrorS 'MLSCommitMissingReferences, - ErrorS 'MLSProposalNotFound, - ErrorS 'MLSStaleMessage, - ErrorS 'MLSUnsupportedMessage, - ErrorS 'MLSIdentityMismatch, - ErrorS MLSLegalholdIncompatible, - ErrorS ('ActionDenied Role.ModifyAddPermission), - ErrorS ('ActionDenied Role.ModifyConversationAccess), - ErrorS ('ActionDenied Role.ModifyConversationReceiptMode), - ErrorS ('ActionDenied Role.ModifyConversationMessageTimer), - ErrorS ('ActionDenied Role.ModifyConversationName), - ErrorS ('ActionDenied Role.ModifyOtherConversationMember), - ErrorS ('ActionDenied Role.AddConversationMember), - ErrorS ('ActionDenied Role.DeleteConversation), - ErrorS ('ActionDenied Role.RemoveConversationMember), - ErrorS ('ActionDenied Role.LeaveConversation), - ErrorS OperationDenied, ErrorS 'HistoryNotSupported, ErrorS 'NotATeamMember, ErrorS 'ConvAccessDenied, @@ -554,58 +505,12 @@ evalGalley e = . mapError toResponse -- ErrorS 'ConvAccessDenied . mapError toResponse -- ErrorS 'NotATeamMember . mapError toResponse -- ErrorS 'HistoryNotSupported + . mapError toResponse -- ErrorS 'TeamMemberNotFound + . mapError toResponse -- ErrorS 'AccessDenied . mapError toResponse -- ErrorS OperationDenied - . mapError toResponse -- ErrorS ('ActionDenied Role.LeaveConversation) - . mapError toResponse -- ErrorS ('ActionDenied Role.RemoveConversationMember) - . mapError toResponse -- ErrorS ('ActionDenied Role.DeleteConversation) - . mapError toResponse -- ErrorS ('ActionDenied Role.AddConversationMember) - . mapError toResponse -- ErrorS ('ActionDenied Role.ModifyOtherConversationMember) - . mapError toResponse -- ErrorS ('ActionDenied Role.ModifyConversationName) - . mapError toResponse -- ErrorS ('ActionDenied Role.ModifyConversationMessageTimer) - . mapError toResponse -- ErrorS ('ActionDenied Role.ModifyConversationReceiptMode) - . mapError toResponse -- ErrorS ('ActionDenied Role.ModifyConversationAccess) - . mapError toResponse -- ErrorS ('ActionDenied Role.ModifyAddPermission) - . mapError toResponse -- ErrorS MLSLegalholdIncompatible, - . mapError toResponse -- ErrorS 'MLSIdentityMismatch, - . mapError toResponse -- ErrorS 'MLSUnsupportedMessage, - . mapError toResponse -- ErrorS 'MLSStaleMessage, - . mapError toResponse -- ErrorS 'MLSProposalNotFound, - . mapError toResponse -- ErrorS 'MLSCommitMissingReferences, - . mapError toResponse -- ErrorS 'MLSSelfRemovalNotAllowed, - . mapError toResponse -- ErrorS 'MLSClientSenderUserMismatch, - . mapError toResponse -- ErrorS 'MLSSubConvClientNotInParent, - . mapError toResponse -- ErrorS 'MLSInvalidLeafNodeSignature, - . mapError toResponse -- ErrorS 'MLSClientMismatch, - . mapError toResponse -- ErrorS 'MLSInvalidLeafNodeIndex, - . mapError toResponse -- ErrorS 'MLSUnsupportedProposal, - . mapError toResponse -- ErrorS 'GroupIdVersionNotSupported, - . mapError toResponse -- ErrorS 'ConvMemberNotFound, - . mapError toResponse -- ErrorS 'MLSGroupConversationMismatch, - . mapError toResponse -- ErrorS 'BroadcastLimitExceeded, - . mapError toResponse -- ErrorS 'MLSFederatedResetNotSupported, - . mapError toResponse -- ErrorS 'MLSSubConvUnsupportedConvType, - . mapError toResponse -- ErrorS 'TeamMemberNotFound, - . mapError toResponse -- ErrorS 'AccessDenied, - . mapError toResponse -- ErrorS 'MLSMissingGroupInfo, - . mapError toResponse -- ErrorS 'CodeNotFound, - . mapError toResponse -- ErrorS 'InvalidConversationPassword, - . mapError toResponse -- ErrorS 'GuestLinksDisabled, - . mapError toResponse -- ErrorS 'MLSFederatedOne2OneNotSupported, - . mapError toResponse -- ErrorS 'TooManyMembers, - . mapError toResponse -- ErrorS 'CreateConversationCodeConflict, - . mapError toResponse -- ErrorS 'InvalidTarget, - . mapError toResponse -- ErrorS 'MLSReadReceiptsNotAllowed, - . mapError toResponse -- ErrorS 'InvalidTargetAccess, - . mapError toResponse -- ErrorS 'ConvInvalidProtocolTransition, - . mapError toResponse -- ErrorS 'MLSMigrationCriteriaNotSatisfied, - . mapError toResponse -- Error MLSOutOfSyncError, - . mapError toResponse -- Error GroupInfoDiagnostics, - . mapError toResponse -- Error UnreachableBackendsLegacy, + . mapError toResponse -- Error ConversationSubsystemError, . mapError rateLimitExceededToHttpError . mapError toResponse -- DynError - . interpretServerEffect -- Error MLSProposalFailure, - . mapError (\msg -> (dynError @(MapError 'MLSProtocolErrorTag)) {eMessage = unTagged msg}) -- Error MLSProtocolError, - . interpretServerEffect -- Error AuthenticationError . mapError meetingError . interpretQueue (e ^. deleteQueue) . nowToIO From 39271bc012071ce1a7bc5489fa126dddece7e422 Mon Sep 17 00:00:00 2001 From: Gautier DI FOLCO Date: Wed, 1 Apr 2026 17:03:26 +0200 Subject: [PATCH 09/19] chore: rebase --- libs/wire-subsystems/default.nix | 3 - .../src/Wire/ClientSubsystem/Interpreter.hs | 2 - .../src/Wire/ConversationSubsystem.hs | 3 - .../src/Wire/ConversationSubsystem/Clients.hs | 16 +---- .../Wire/ConversationSubsystem/Federation.hs | 2 +- .../Wire/ConversationSubsystem/Interpreter.hs | 3 - .../ConversationSubsystem/MLS/Proposal.hs | 1 + .../test/unit/Wire/MiniBackend.hs | 2 +- libs/wire-subsystems/wire-subsystems.cabal | 1 - .../Wire/BackgroundWorker/Jobs/Registry.hs | 69 ++----------------- services/galley/src/Galley/API/Internal.hs | 4 +- services/galley/src/Galley/API/LegalHold.hs | 18 +++-- services/galley/src/Galley/API/Teams.hs | 4 +- 13 files changed, 29 insertions(+), 99 deletions(-) diff --git a/libs/wire-subsystems/default.nix b/libs/wire-subsystems/default.nix index e4429af3fde..3f608b5a367 100644 --- a/libs/wire-subsystems/default.nix +++ b/libs/wire-subsystems/default.nix @@ -19,7 +19,6 @@ , bilge , bimap , bloodhound -, brig-types , bytestring , bytestring-conversion , case-insensitive @@ -158,7 +157,6 @@ mkDerivation { bilge bimap bloodhound - brig-types bytestring bytestring-conversion case-insensitive @@ -283,7 +281,6 @@ mkDerivation { base64-bytestring bilge bloodhound - brig-types bytestring bytestring-conversion case-insensitive diff --git a/libs/wire-subsystems/src/Wire/ClientSubsystem/Interpreter.hs b/libs/wire-subsystems/src/Wire/ClientSubsystem/Interpreter.hs index 5261501afba..2eabc17a810 100644 --- a/libs/wire-subsystems/src/Wire/ClientSubsystem/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/ClientSubsystem/Interpreter.hs @@ -21,8 +21,6 @@ import Data.Set ((\\)) import Data.Set qualified as Set import Data.Time.Clock import Imports hiding ((\\)) -import Data.Time.Clock (UTCTime) -import Imports import Polysemy import Polysemy.Error import Polysemy.Input diff --git a/libs/wire-subsystems/src/Wire/ConversationSubsystem.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem.hs index c110e8be58c..cd10f465eca 100644 --- a/libs/wire-subsystems/src/Wire/ConversationSubsystem.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem.hs @@ -669,9 +669,6 @@ data ConversationSubsystem m a where ConversationAction (tag :: ConversationActionTag) -> ExtraConversationData -> ConversationSubsystem m LocalConversationUpdate - QualifyLocal :: - a -> - ConversationSubsystem m (Local a) InternalUpsertOne2OneConversation :: UpsertOne2OneConversationRequest -> ConversationSubsystem m () diff --git a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Clients.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Clients.hs index 2b09f61a197..f66edcce6cb 100644 --- a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Clients.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Clients.hs @@ -16,8 +16,7 @@ -- with this program. If not, see . module Wire.ConversationSubsystem.Clients - ( getClients, - rmClient, + ( rmClient, ) where @@ -42,9 +41,9 @@ import Wire.API.Federation.Error import Wire.API.MLS.Keys (MLSKeysByPurpose, MLSPrivateKeys) import Wire.API.Routes.MultiTablePaging import Wire.BackendNotificationQueueAccess -import Wire.BrigAPIAccess import Wire.ConversationStore (ConversationStore, getConversation) -import Wire.ConversationSubsystem qualified as ConvSubsystem +import Wire.ConversationSubsystem.MLS.Removal (removeClient) +import Wire.ConversationSubsystem.Query qualified as Query import Wire.ExternalAccess (ExternalAccess) import Wire.NotificationSubsystem import Wire.ProposalStore (ProposalStore) @@ -53,15 +52,6 @@ import Wire.Sem.Random (Random) import Wire.UserClientIndexStore qualified as E import Wire.Util -getClients :: - ( Member BrigAPIAccess r, - Member E.UserClientIndexStore r, - Member (Input ConversationSubsystemConfig) r - ) => - UserId -> - Sem r [ClientId] -getClients usr = clientIds usr <$> internalGetClientIds [usr] - -- | Remove a client from conversations it is part of according to the -- conversation protocol (Proteus or MLS). In addition, remove the client from -- the "clients" table in Galley. diff --git a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Federation.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Federation.hs index ef70d8c824d..ee1f24f0de7 100644 --- a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Federation.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Federation.hs @@ -3,7 +3,7 @@ -- This file is part of the Wire Server implementation. -- --- Copyright (C) 2022 Wire Swiss GmbH +-- Copyright (C) 2026 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 diff --git a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Interpreter.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Interpreter.hs index 55683df334e..9ff7bfb1aef 100644 --- a/libs/wire-subsystems/src/Wire/ConversationSubsystem/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/Interpreter.hs @@ -71,7 +71,6 @@ import Wire.ConversationSubsystem.Notify qualified as Notify import Wire.ConversationSubsystem.One2One qualified as One2One import Wire.ConversationSubsystem.Query qualified as Query import Wire.ConversationSubsystem.Update qualified as Update -import Wire.ConversationSubsystem.Util qualified as Util import Wire.ExternalAccess (ExternalAccess) import Wire.FeaturesConfigSubsystem import Wire.FeaturesConfigSubsystem.Types (ExposeInvitationURLsAllowlist) @@ -346,8 +345,6 @@ interpretConversationSubsystem = interpretH $ \case liftT $ mapErrors $ Query.getConversations lusr mids mstart msize SearchChannels lusr tid searchString sortOrder pageSize lastName lastId discoverable -> liftT $ mapErrors $ Query.searchChannels lusr tid searchString sortOrder pageSize lastName lastId discoverable - QualifyLocal a -> - liftT $ mapErrors $ Util.qualifyLocal a InternalGetMember qcnv usr -> liftT $ mapErrors $ Query.internalGetMember qcnv usr GetConversationMeta cnv -> diff --git a/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Proposal.hs b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Proposal.hs index 6179e83b674..712fd32aee4 100644 --- a/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Proposal.hs +++ b/libs/wire-subsystems/src/Wire/ConversationSubsystem/MLS/Proposal.hs @@ -66,6 +66,7 @@ import Wire.BackendNotificationQueueAccess import Wire.BrigAPIAccess import Wire.ConversationStore (ConversationStore) import Wire.ConversationStore.MLS.Types +import Wire.ConversationSubsystem.MLS.IncomingMessage import Wire.ExternalAccess import Wire.FederationAPIAccess (FederationAPIAccess) import Wire.LegalHoldStore (LegalHoldStore) diff --git a/libs/wire-subsystems/test/unit/Wire/MiniBackend.hs b/libs/wire-subsystems/test/unit/Wire/MiniBackend.hs index 6b45b3984c6..be0fb34dfa2 100644 --- a/libs/wire-subsystems/test/unit/Wire/MiniBackend.hs +++ b/libs/wire-subsystems/test/unit/Wire/MiniBackend.hs @@ -363,7 +363,7 @@ miniBackendLowerEffectsInterpreters mb@(MiniBackendParams {..}) = . mockUserClientIndexStore . mockBrigAPIAccess . runInputConst conversationCfg - . runClientSubsystem + . runClientSubsystem undefined undefined where -- Mock BrigAPIAccess interpreter for tests mockBrigAPIAccess :: forall r'. InterpreterFor BrigAPIAccess r' diff --git a/libs/wire-subsystems/wire-subsystems.cabal b/libs/wire-subsystems/wire-subsystems.cabal index d1194d10d56..1e5df9ef314 100644 --- a/libs/wire-subsystems/wire-subsystems.cabal +++ b/libs/wire-subsystems/wire-subsystems.cabal @@ -98,7 +98,6 @@ common common-all , base64-bytestring , bilge , bloodhound - , brig-types , bytestring , bytestring-conversion , case-insensitive diff --git a/services/background-worker/src/Wire/BackgroundWorker/Jobs/Registry.hs b/services/background-worker/src/Wire/BackgroundWorker/Jobs/Registry.hs index 9fe74b14375..ba6b0f362d2 100644 --- a/services/background-worker/src/Wire/BackgroundWorker/Jobs/Registry.hs +++ b/services/background-worker/src/Wire/BackgroundWorker/Jobs/Registry.hs @@ -33,11 +33,12 @@ import Data.Qualified import Data.Tagged (Tagged) import Data.Text qualified as T import Data.Text.Lazy qualified as TL -import Galley.Types.Error (InternalError, InvalidInput, internalErrorDescription, legalHoldServiceUnavailable) +import Galley.Types.Error (InternalError, internalErrorDescription, legalHoldServiceUnavailable) import Hasql.Pool (UsageError) import Hasql.Pool qualified as Hasql import Imports import Network.HTTP.Client qualified as Http +import Network.Wai.Utilities.JSONResponse (JSONResponse (..)) import OpenSSL.Session qualified as SSL import Polysemy import Polysemy.Async (asyncToIOFinal) @@ -52,7 +53,7 @@ import System.Logger.Class qualified as Log import URI.ByteString (uriPath) import Wire.API.BackgroundJobs (Job (..)) import Wire.API.Conversation.Config (ConversationSubsystemConfig (..)) -import Wire.API.Conversation.Role qualified as Role +import Wire.API.Error (APIError (toResponse), DynError (..)) import Wire.API.Error.Galley import Wire.API.Federation.Error (FederationError) import Wire.API.MLS.Keys (MLSKeysByPurpose, MLSPrivateKeys) @@ -72,7 +73,7 @@ import Wire.CodeStore.DualWrite (interpretCodeStoreToCassandraAndPostgres) import Wire.CodeStore.Postgres (interpretCodeStoreToPostgres) import Wire.ConversationStore.Cassandra import Wire.ConversationStore.Postgres (interpretConversationStoreToPostgres) -import Wire.ConversationSubsystem.Interpreter (GroupInfoCheckEnabled (..), GuestLinkTTLSeconds (..), IntraListing (..), interpretConversationSubsystem) +import Wire.ConversationSubsystem.Interpreter (ConversationSubsystemError, GroupInfoCheckEnabled (..), GuestLinkTTLSeconds (..), IntraListing (..), interpretConversationSubsystem) import Wire.ExternalAccess.External import Wire.FeaturesConfigSubsystem (getAllTeamFeaturesForServer) import Wire.FeaturesConfigSubsystem.Interpreter (runFeaturesConfigSubsystem) @@ -202,79 +203,23 @@ dispatchJob job = do . runDelay . resourceToIOFinal . runError + . mapError @DynError (T.pack . show . (.eMessage)) + . mapError @JSONResponse (T.pack . show . (.value)) + . mapError @ConversationSubsystemError toResponse . mapError @ClientError (T.pack . displayException) . mapError @FederationError (T.pack . displayException) . mapError @UsageError (T.pack . show) . mapError @ParseException (T.pack . displayException) - . mapError (const ("Invalid input" :: Text) :: InvalidInput -> Text) . mapError @MigrationError (T.pack . show) . mapError @InternalError (TL.toStrict . internalErrorDescription) . mapError @UnreachableBackends (T.pack . show) . mapError @TeamCollaboratorsError (const ("Team collaborators error" :: Text)) . mapError @TeamFeatureStoreError (const ("Team feature store error" :: Text)) - . mapError @(Tagged HistoryNotSupported ()) (const ("History not supported" :: Text)) - . mapError @(Tagged OperationDenied ()) (const ("Operation denied" :: Text)) . mapError @(Tagged 'NotATeamMember ()) (const ("Not a team member" :: Text)) . mapError @(Tagged 'ConvAccessDenied ()) (const ("Conversation access denied" :: Text)) - . mapError @(Tagged 'NotConnected ()) (const ("Not connected" :: Text)) - . mapError @(Tagged 'MLSNotEnabled ()) (const ("MLS not enabled" :: Text)) - . mapError @(Tagged 'MLSNonEmptyMemberList ()) (const ("MLS non-empty member list" :: Text)) - . mapError @(Tagged 'MissingLegalholdConsent ()) (const ("Missing legalhold consent" :: Text)) - . mapError @(Tagged 'NonBindingTeam ()) (const ("Non-binding team" :: Text)) - . mapError @(Tagged 'NoBindingTeamMembers ()) (const ("No binding team members" :: Text)) . mapError @(Tagged 'TeamNotFound ()) (const ("Team not found" :: Text)) - . mapError @(Tagged 'InvalidOperation ()) (const ("Invalid operation" :: Text)) - . mapError @(Tagged 'ConvNotFound ()) (const ("Conversation not found" :: Text)) - . mapError @(Tagged 'ChannelsNotEnabled ()) (const ("Channels not enabled" :: Text)) - . mapError @(Tagged 'NotAnMlsConversation ()) (const ("Not an MLS conversation" :: Text)) - . mapError @(Tagged ('ActionDenied Role.DeleteConversation) ()) (const ("Cannot delete conversation" :: Text)) - . mapError @(Tagged ('ActionDenied Role.LeaveConversation) ()) (const ("Cannot leave conversation" :: Text)) - . mapError @(Tagged ('ActionDenied Role.ModifyConversationAccess) ()) (const ("Cannot modify conversation access" :: Text)) - . mapError @(Tagged ('ActionDenied Role.ModifyConversationName) ()) (const ("Cannot modify conversation name" :: Text)) - . mapError @(Tagged ('ActionDenied Role.ModifyConversationMessageTimer) ()) (const ("Cannot modify conversation message timer" :: Text)) - . mapError @(Tagged ('ActionDenied Role.ModifyConversationReceiptMode) ()) (const ("Cannot modify conversation receipt mode" :: Text)) - . mapError @(Tagged ('ActionDenied Role.ModifyAddPermission) ()) (const ("Cannot modify add permission" :: Text)) - . mapError @(Tagged ('ActionDenied Role.AddConversationMember) ()) (const ("Cannot add member to conversation" :: Text)) - . mapError @(Tagged ('ActionDenied Role.ModifyOtherConversationMember) ()) (const ("Cannot modify member from conversation" :: Text)) - . mapError @(Tagged ('ActionDenied Role.RemoveConversationMember) ()) (const ("Cannot remove member from conversation" :: Text)) - . mapError @(Tagged 'MLSLegalholdIncompatible ()) (const ("MLS - Legalhold is incompatible" :: Text)) - . mapError @(Tagged 'MLSIdentityMismatch ()) (const ("MLS - identity mismatch" :: Text)) - . mapError @(Tagged 'MLSUnsupportedMessage ()) (const ("MLS - unsupported message" :: Text)) - . mapError @(Tagged 'MLSStaleMessage ()) (const ("MLS - stale message" :: Text)) - . mapError @(Tagged 'MLSProposalNotFound ()) (const ("MLS - proposal not found" :: Text)) - . mapError @(Tagged 'MLSUnsupportedProposal ()) (const ("MLS - proposal not supported" :: Text)) - . mapError @(Tagged 'MLSCommitMissingReferences ()) (const ("MLS - commit missing references" :: Text)) - . mapError @(Tagged 'MLSSelfRemovalNotAllowed ()) (const ("MLS - self-removal is denied" :: Text)) - . mapError @(Tagged 'MLSClientSenderUserMismatch ()) (const ("MLS - client-sender user mismatch" :: Text)) - . mapError @(Tagged 'MLSSubConvClientNotInParent ()) (const ("MLS - sub-conversation client is missing in the parent conversation" :: Text)) - . mapError @(Tagged 'MLSInvalidLeafNodeSignature ()) (const ("MLS - invalid leaf node signature" :: Text)) - . mapError @(Tagged 'MLSInvalidLeafNodeIndex ()) (const ("MLS - leaf node index" :: Text)) - . mapError @(Tagged 'MLSGroupConversationMismatch ()) (const ("MLS - group-conversation mismatch" :: Text)) - . mapError @(Tagged 'MLSFederatedResetNotSupported ()) (const ("MLS - federated reset not supported" :: Text)) - . mapError @(Tagged 'MLSSubConvUnsupportedConvType ()) (const ("MLS - sub-conversation unsopported ConvType" :: Text)) - . mapError @(Tagged 'MLSClientMismatch ()) (const ("MLS - client mismatch" :: Text)) - . mapError @(Tagged 'MLSMissingGroupInfo ()) (const ("MLS - missing group info" :: Text)) - . mapError @(Tagged 'MLSFederatedOne2OneNotSupported ()) (const ("MLS - federation One2One not supported" :: Text)) - . mapError @(Tagged 'MLSReadReceiptsNotAllowed ()) (const ("MLS - read receipts not allowed" :: Text)) - . mapError @(Tagged 'MLSMigrationCriteriaNotSatisfied ()) (const ("MLS - migration criteria not satisfied" :: Text)) - . mapError @(Tagged 'GroupIdVersionNotSupported ()) (const ("groupId version not supported" :: Text)) - . mapError @(Tagged 'ConvMemberNotFound ()) (const ("Conversation member not found" :: Text)) - . mapError @(Tagged 'BroadcastLimitExceeded ()) (const ("Broadcast limit exceeded" :: Text)) . mapError @(Tagged 'TeamMemberNotFound ()) (const ("Team member not found" :: Text)) . mapError @(Tagged 'AccessDenied ()) (const ("Access denied" :: Text)) - . mapError @(Tagged 'CodeNotFound ()) (const ("Conversation not found" :: Text)) - . mapError @(Tagged 'InvalidConversationPassword ()) (const ("Invalid conversation password" :: Text)) - . mapError @(Tagged 'TooManyMembers ()) (const ("Too many members" :: Text)) - . mapError @(Tagged 'CreateConversationCodeConflict ()) (const ("Create conversation code conflict" :: Text)) - . mapError @(Tagged 'InvalidTarget ()) (const ("Invalid target" :: Text)) - . mapError @(Tagged 'GuestLinksDisabled ()) (const ("Guest links disabled" :: Text)) - . mapError @(Tagged 'InvalidTargetAccess ()) (const ("Invalid target access" :: Text)) - . mapError @(Tagged 'ConvInvalidProtocolTransition ()) (const ("Conversation invalid protocol transition" :: Text)) - . mapError @MLSProtocolError (const ("MLS - protocol error" :: Text)) - . mapError @MLSOutOfSyncError (const ("MLS - out-of-sync error" :: Text)) - . mapError @MLSProposalFailure (const ("MLS - proposal failure" :: Text)) - . mapError @AuthenticationError (const ("Authentication error" :: Text)) - . mapError @GroupInfoDiagnostics (const ("Group info diagnostics" :: Text)) . mapError @NonFederatingBackends (const ("Non federating backends" :: Text)) . mapError @UnreachableBackendsLegacy (const ("Unreachable backends legacy" :: Text)) . mapError @RateLimitExceeded (const ("Rate limit exceeded" :: Text)) diff --git a/services/galley/src/Galley/API/Internal.hs b/services/galley/src/Galley/API/Internal.hs index 27927ca5aae..4394d1d1306 100644 --- a/services/galley/src/Galley/API/Internal.hs +++ b/services/galley/src/Galley/API/Internal.hs @@ -101,7 +101,6 @@ import Wire.TeamStore import Wire.TeamStore qualified as E import Wire.TeamSubsystem (TeamSubsystem) import Wire.TeamSubsystem qualified as TeamSubsystem -import Wire.UserClientIndexStore import Wire.UserClientIndexStore as UserClientIndexStore import Wire.UserList import Wire.Util @@ -139,7 +138,8 @@ iEJPDAPI = mkNamedAPI @"get-conversations-by-user" ejpdGetConvInfo ejpdGetConvInfo :: forall r. ( Member ConversationStore r, - Member ConversationSubsystem r + Member ConversationSubsystem r, + Member (Input (Local ())) r ) => UserId -> Sem r [EJPDConvInfo] diff --git a/services/galley/src/Galley/API/LegalHold.hs b/services/galley/src/Galley/API/LegalHold.hs index 30fe7d1d693..36a27385089 100644 --- a/services/galley/src/Galley/API/LegalHold.hs +++ b/services/galley/src/Galley/API/LegalHold.hs @@ -159,7 +159,8 @@ removeSettingsInternalPaging :: Member (Embed IO) r, Member (Input (FeatureDefaults LegalholdConfig)) r, Member TeamSubsystem r, - Member FeaturesConfigSubsystem r + Member FeaturesConfigSubsystem r, + Member (Input (Local ())) r ) => Local UserId -> TeamId -> @@ -191,7 +192,8 @@ removeSettings :: Member (Input (FeatureDefaults LegalholdConfig)) r, Member TeamSubsystem r, Member FeaturesConfigSubsystem r, - Member LegalHoldData.LegalHoldStore r + Member LegalHoldData.LegalHoldStore r, + Member (Input (Local ())) r ) => UserId -> TeamId -> @@ -236,7 +238,8 @@ removeSettings' :: Member (TeamMemberStore p) r, Member TeamStore r, Member P.TinyLog r, - Member (Embed IO) r + Member (Embed IO) r, + Member (Input (Local ())) r ) => TeamId -> Sem r () @@ -314,7 +317,8 @@ requestDevice :: Member (Embed IO) r, Member (Input (FeatureDefaults LegalholdConfig)) r, Member TeamSubsystem r, - Member FeaturesConfigSubsystem r + Member FeaturesConfigSubsystem r, + Member (Input (Local ())) r ) => Local UserId -> TeamId -> @@ -397,7 +401,8 @@ approveDevice :: Member (Embed IO) r, Member (Input (FeatureDefaults LegalholdConfig)) r, Member TeamSubsystem r, - Member FeaturesConfigSubsystem r + Member FeaturesConfigSubsystem r, + Member (Input (Local ())) r ) => Local UserId -> ConnId -> @@ -462,7 +467,8 @@ disableForUser :: Member P.TinyLog r, Member TeamStore r, Member (Embed IO) r, - Member TeamSubsystem r + Member TeamSubsystem r, + Member (Input (Local ())) r ) => Local UserId -> TeamId -> diff --git a/services/galley/src/Galley/API/Teams.hs b/services/galley/src/Galley/API/Teams.hs index ed3ff69dab6..7f3bd3fbbbb 100644 --- a/services/galley/src/Galley/API/Teams.hs +++ b/services/galley/src/Galley/API/Teams.hs @@ -1223,8 +1223,8 @@ userIsTeamOwner :: ( Member (ErrorS 'TeamMemberNotFound) r, Member (ErrorS 'AccessDenied) r, Member (ErrorS 'NotATeamMember) r, - Member ConversationSubsystem r, - Member TeamSubsystem r + Member TeamSubsystem r, + Member (Input (Local ())) r ) => TeamId -> UserId -> From 53f25f92975aee198cb5e5e3ba88418006df9664 Mon Sep 17 00:00:00 2001 From: Gautier DI FOLCO Date: Wed, 1 Apr 2026 19:14:41 +0200 Subject: [PATCH 10/19] fix: test --- libs/wire-subsystems/test/unit/Wire/MiniBackend.hs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/libs/wire-subsystems/test/unit/Wire/MiniBackend.hs b/libs/wire-subsystems/test/unit/Wire/MiniBackend.hs index be0fb34dfa2..88624c7019f 100644 --- a/libs/wire-subsystems/test/unit/Wire/MiniBackend.hs +++ b/libs/wire-subsystems/test/unit/Wire/MiniBackend.hs @@ -298,8 +298,7 @@ type MiniBackendLowerEffects = Random, Now, ErrorS 'TeamMemberNotFound, - ErrorS 'TeamNotFound, - Error FederationError + ErrorS 'TeamNotFound ] `Append` InputEffects `Append` '[ Metrics @@ -324,8 +323,6 @@ miniBackendLowerEffectsInterpreters mb@(MiniBackendParams {..}) = . ignoreMetrics . inputEffectsInterpreters usrCfg appCfg localBackend.teamIdps . fmap (either (error . show) Imports.id) - . runError @FederationError - . fmap (either (error . show) Imports.id) . runError @(Tagged 'TeamNotFound ()) . fmap (either (error . show) Imports.id) . runError @(Tagged 'TeamMemberNotFound ()) From 630370a762d8f690998354c2d79ead8d2654d1da Mon Sep 17 00:00:00 2001 From: Gautier DI FOLCO Date: Wed, 1 Apr 2026 19:48:34 +0200 Subject: [PATCH 11/19] fix(leif&sven): move global params in charts --- changelog.d/5-internal/WPB-23789 | 17 ++++ .../templates/_helpers.tpl | 88 ++++++++++++------- .../background-worker/configmap.yaml | 40 ++++++++- .../templates/galley/configmap.yaml | 46 ++++++++-- charts/wire-server/values.yaml | 66 +++++++++----- 5 files changed, 192 insertions(+), 65 deletions(-) create mode 100644 changelog.d/5-internal/WPB-23789 diff --git a/changelog.d/5-internal/WPB-23789 b/changelog.d/5-internal/WPB-23789 new file mode 100644 index 00000000000..1cb8a7235eb --- /dev/null +++ b/changelog.d/5-internal/WPB-23789 @@ -0,0 +1,17 @@ +### ConversationSubsystem Migration + +* Move conversation-related operations into a unified Polysemy `ConversationSubsystem` effect across the wire-server codebase. + This consolidation improves code organization and separation of concerns for conversation logic. + +* Chart configuration changes: + - Added `global.settings` section to deduplicate shared configuration between galley and background-worker services + - Background-worker now requires either `conversationCodeURI` or `multiIngress` to be set in its configuration + - Removed duplicate settings previously defined in both galley and background-worker sections + +* Library updates: + - Introduced dedicated error types for ConversationSubsystem to improve error handling + - Consolidated conversation-related operations that were previously scattered across multiple stores and subsystems + +**Note:** The background-worker configuration now has a dependency on the shared global settings. +Deployments that previously set these values in both galley and background-worker sections +can consolidate them into the global.settings section. \ No newline at end of file diff --git a/charts/cassandra-migrations/templates/_helpers.tpl b/charts/cassandra-migrations/templates/_helpers.tpl index 0d805051ffe..3ce89ba357c 100644 --- a/charts/cassandra-migrations/templates/_helpers.tpl +++ b/charts/cassandra-migrations/templates/_helpers.tpl @@ -1,21 +1,25 @@ {{- define "cassandraGalleyHost" -}} {{ $cassandraGalley := default dict .Values.cassandraGalley }} -{{- default (.Values.cassandra.host) $cassandraGalley.host }} +{{- $cassandra := default dict .Values.cassandra }} +{{- default ($cassandra.host) $cassandraGalley.host }} {{- end -}} {{- define "cassandraBrigHost" -}} {{ $cassandraBrig := default dict .Values.cassandraBrig }} -{{- default (.Values.cassandra.host) $cassandraBrig.host }} +{{- $cassandra := default dict .Values.cassandra }} +{{- default ($cassandra.host) $cassandraBrig.host }} {{- end -}} {{- define "cassandraGundeckHost" -}} {{ $cassandraGundeck := default dict .Values.cassandraGundeck }} -{{- default (.Values.cassandra.host) $cassandraGundeck.host }} +{{- $cassandra := default dict .Values.cassandra }} +{{- default ($cassandra.host) $cassandraGundeck.host }} {{- end -}} {{- define "cassandraSparHost" -}} {{ $cassandraSpar := default dict .Values.cassandraSpar }} -{{- default (.Values.cassandra.host) $cassandraSpar.host }} +{{- $cassandra := default dict .Values.cassandra }} +{{- default ($cassandra.host) $cassandraSpar.host }} {{- end -}} {{/* @@ -34,16 +38,18 @@ Thus the order of priority is: {{- define "cassandraGalleyReplicationArg" -}} {{ $cassandraGalley := default dict .Values.cassandraGalley }} -{{- if (or .Values.cassandra.replicationMap $cassandraGalley.replicationMap) -}} -{{- default (.Values.cassandra.replicationMap) $cassandraGalley.replicationMap -}} +{{ $cassandra := default dict .Values.cassandra }} +{{- if (or $cassandra.replicationMap $cassandraGalley.replicationMap) -}} +{{- default ($cassandra.replicationMap) $cassandraGalley.replicationMap -}} {{- else -}} -{{- default (default (.Values.cassandra.replicaCount) .Values.cassandra.replicationFactor) $cassandraGalley.replicationFactor -}} +{{- default (default ($cassandra.replicaCount) $cassandra.replicationFactor) $cassandraGalley.replicationFactor -}} {{- end -}} {{- end -}} {{- define "cassandraGalleyReplicationType" -}} {{ $cassandraGalley := default dict .Values.cassandraGalley }} -{{- if (or .Values.cassandra.replicationMap $cassandraGalley.replicationMap) -}} +{{ $cassandra := default dict .Values.cassandra }} +{{- if (or $cassandra.replicationMap $cassandraGalley.replicationMap) -}} {{- printf "--replication-map" -}} {{- else -}} {{- printf "--replication-factor" -}} @@ -53,16 +59,18 @@ Thus the order of priority is: {{- define "cassandraGundeckReplicationArg" -}} {{ $cassandraGundeck := default dict .Values.cassandraGundeck }} -{{- if (or .Values.cassandra.replicationMap $cassandraGundeck.replicationMap) -}} -{{- default (.Values.cassandra.replicationMap) $cassandraGundeck.replicationMap -}} +{{ $cassandra := default dict .Values.cassandra }} +{{- if (or $cassandra.replicationMap $cassandraGundeck.replicationMap) -}} +{{- default ($cassandra.replicationMap) $cassandraGundeck.replicationMap -}} {{- else -}} -{{- default (default (.Values.cassandra.replicaCount) .Values.cassandra.replicationFactor) $cassandraGundeck.replicationFactor -}} +{{- default (default ($cassandra.replicaCount) $cassandra.replicationFactor) $cassandraGundeck.replicationFactor -}} {{- end -}} {{- end -}} {{- define "cassandraGundeckReplicationType" -}} {{ $cassandraGundeck := default dict .Values.cassandraGundeck }} -{{- if (or .Values.cassandra.replicationMap $cassandraGundeck.replicationMap) -}} +{{ $cassandra := default dict .Values.cassandra }} +{{- if (or $cassandra.replicationMap $cassandraGundeck.replicationMap) -}} {{- printf "--replication-map" -}} {{- else -}} {{- printf "--replication-factor" -}} @@ -72,16 +80,18 @@ Thus the order of priority is: {{- define "cassandraBrigReplicationArg" -}} {{ $cassandraBrig := default dict .Values.cassandraBrig }} -{{- if (or .Values.cassandra.replicationMap $cassandraBrig.replicationMap) -}} -{{- default (.Values.cassandra.replicationMap) $cassandraBrig.replicationMap -}} +{{ $cassandra := default dict .Values.cassandra }} +{{- if (or $cassandra.replicationMap $cassandraBrig.replicationMap) -}} +{{- default ($cassandra.replicationMap) $cassandraBrig.replicationMap -}} {{- else -}} -{{- default (default (.Values.cassandra.replicaCount) .Values.cassandra.replicationFactor) $cassandraBrig.replicationFactor -}} +{{- default (default ($cassandra.replicaCount) $cassandra.replicationFactor) $cassandraBrig.replicationFactor -}} {{- end -}} {{- end -}} {{- define "cassandraBrigReplicationType" -}} {{ $cassandraBrig := default dict .Values.cassandraBrig }} -{{- if (or .Values.cassandra.replicationMap $cassandraBrig.replicationMap) -}} +{{ $cassandra := default dict .Values.cassandra }} +{{- if (or $cassandra.replicationMap $cassandraBrig.replicationMap) -}} {{- printf "--replication-map" -}} {{- else -}} {{- printf "--replication-factor" -}} @@ -91,16 +101,18 @@ Thus the order of priority is: {{- define "cassandraSparReplicationArg" -}} {{ $cassandraSpar := default dict .Values.cassandraSpar }} -{{- if (or .Values.cassandra.replicationMap $cassandraSpar.replicationMap) -}} -{{- default (.Values.cassandra.replicationMap) $cassandraSpar.replicationMap -}} +{{ $cassandra := default dict .Values.cassandra }} +{{- if (or $cassandra.replicationMap $cassandraSpar.replicationMap) -}} +{{- default ($cassandra.replicationMap) $cassandraSpar.replicationMap -}} {{- else -}} -{{- default (default (.Values.cassandra.replicaCount) .Values.cassandra.replicationFactor) $cassandraSpar.replicationFactor -}} +{{- default (default ($cassandra.replicaCount) $cassandra.replicationFactor) $cassandraSpar.replicationFactor -}} {{- end -}} {{- end -}} {{- define "cassandraSparReplicationType" -}} {{ $cassandraSpar := default dict .Values.cassandraSpar }} -{{- if (or .Values.cassandra.replicationMap $cassandraSpar.replicationMap) -}} +{{ $cassandra := default dict .Values.cassandra }} +{{- if (or $cassandra.replicationMap $cassandraSpar.replicationMap) -}} {{- printf "--replication-map" -}} {{- else -}} {{- printf "--replication-factor" -}} @@ -123,7 +135,8 @@ configuration if the specific one does not exist: */}} {{- define "useTlsGalley" -}} -{{ $cassandraGalley := default .Values.cassandra .Values.cassandraGalley }} +{{ $cassandra := default dict .Values.cassandra }} +{{ $cassandraGalley := default $cassandra .Values.cassandraGalley }} {{- if or $cassandraGalley.tlsCa $cassandraGalley.tlsCaSecretRef -}} true {{- else}} @@ -132,7 +145,8 @@ false {{- end -}} {{- define "tlsCaGalley" -}} -{{ $cassandraGalley := default .Values.cassandra .Values.cassandraGalley }} +{{ $cassandra := default dict .Values.cassandra }} +{{ $cassandraGalley := default $cassandra .Values.cassandraGalley }} {{- if hasKey $cassandraGalley "tlsCa" -}} {{- $cassandraGalley.tlsCa }} {{ else }} @@ -140,7 +154,8 @@ false {{- end -}} {{- define "tlsSecretRefGalley" -}} -{{ $cassandraGalley := default .Values.cassandra .Values.cassandraGalley }} +{{ $cassandra := default dict .Values.cassandra }} +{{ $cassandraGalley := default $cassandra .Values.cassandraGalley }} {{- if $cassandraGalley.tlsCaSecretRef -}} {{ $cassandraGalley.tlsCaSecretRef | toYaml }} {{- else }} @@ -149,7 +164,8 @@ false {{- end -}} {{- define "useTlsBrig" -}} -{{ $cassandraBrig := default .Values.cassandra .Values.cassandraBrig }} +{{ $cassandra := default dict .Values.cassandra }} +{{ $cassandraBrig := default $cassandra .Values.cassandraBrig }} {{- if or $cassandraBrig.tlsCa $cassandraBrig.tlsCaSecretRef -}} true {{- else}} @@ -158,7 +174,8 @@ false {{- end -}} {{- define "tlsCaBrig" -}} -{{ $cassandraBrig := default .Values.cassandra .Values.cassandraBrig }} +{{ $cassandra := default dict .Values.cassandra }} +{{ $cassandraBrig := default $cassandra .Values.cassandraBrig }} {{- if hasKey $cassandraBrig "tlsCa" -}} {{- $cassandraBrig.tlsCa }} {{ else }} @@ -166,7 +183,8 @@ false {{- end -}} {{- define "tlsSecretRefBrig" -}} -{{ $cassandraBrig := default .Values.cassandra .Values.cassandraBrig }} +{{ $cassandra := default dict .Values.cassandra }} +{{ $cassandraBrig := default $cassandra .Values.cassandraBrig }} {{- if $cassandraBrig.tlsCaSecretRef -}} {{ $cassandraBrig.tlsCaSecretRef | toYaml }} {{- else }} @@ -175,7 +193,8 @@ false {{- end -}} {{- define "useTlsSpar" -}} -{{ $cassandraSpar := default .Values.cassandra .Values.cassandraSpar }} +{{ $cassandra := default dict .Values.cassandra }} +{{ $cassandraSpar := default $cassandra .Values.cassandraSpar }} {{- if or $cassandraSpar.tlsCa $cassandraSpar.tlsCaSecretRef -}} true {{- else}} @@ -184,7 +203,8 @@ false {{- end -}} {{- define "tlsCaSpar" -}} -{{ $cassandraSpar := default .Values.cassandra .Values.cassandraSpar }} +{{ $cassandra := default dict .Values.cassandra }} +{{ $cassandraSpar := default $cassandra .Values.cassandraSpar }} {{- if hasKey $cassandraSpar "tlsCa" -}} {{- $cassandraSpar.tlsCa }} {{ else }} @@ -192,7 +212,8 @@ false {{- end -}} {{- define "tlsSecretRefSpar" -}} -{{ $cassandraSpar := default .Values.cassandra .Values.cassandraSpar }} +{{ $cassandra := default dict .Values.cassandra }} +{{ $cassandraSpar := default $cassandra .Values.cassandraSpar }} {{- if $cassandraSpar.tlsCaSecretRef -}} {{ $cassandraSpar.tlsCaSecretRef | toYaml }} {{- else }} @@ -201,7 +222,8 @@ false {{- end -}} {{- define "useTlsGundeck" -}} -{{ $cassandraGundeck := default .Values.cassandra .Values.cassandraGundeck }} +{{ $cassandra := default dict .Values.cassandra }} +{{ $cassandraGundeck := default $cassandra .Values.cassandraGundeck }} {{- if or $cassandraGundeck.tlsCa $cassandraGundeck.tlsCaSecretRef -}} true {{- else}} @@ -210,7 +232,8 @@ false {{- end -}} {{- define "tlsCaGundeck" -}} -{{ $cassandraGundeck := default .Values.cassandra .Values.cassandraGundeck }} +{{ $cassandra := default dict .Values.cassandra }} +{{ $cassandraGundeck := default $cassandra .Values.cassandraGundeck }} {{- if hasKey $cassandraGundeck "tlsCa" -}} {{- $cassandraGundeck.tlsCa }} {{ else }} @@ -218,7 +241,8 @@ false {{- end -}} {{- define "tlsSecretRefGundeck" -}} -{{ $cassandraGundeck := default .Values.cassandra .Values.cassandraGundeck }} +{{ $cassandra := default dict .Values.cassandra }} +{{ $cassandraGundeck := default $cassandra .Values.cassandraGundeck }} {{- if $cassandraGundeck.tlsCaSecretRef -}} {{ $cassandraGundeck.tlsCaSecretRef | toYaml }} {{- else }} diff --git a/charts/wire-server/templates/background-worker/configmap.yaml b/charts/wire-server/templates/background-worker/configmap.yaml index d8b790a2349..26410b193c2 100644 --- a/charts/wire-server/templates/background-worker/configmap.yaml +++ b/charts/wire-server/templates/background-worker/configmap.yaml @@ -114,24 +114,58 @@ data: {{- end }} settings: + {{- if .Values.global.settings.maxTeamSize }} + maxTeamSize: {{ .Values.global.settings.maxTeamSize }} + {{- else }} maxTeamSize: {{ .settings.maxTeamSize }} + {{- end }} maxFanoutSize: {{ .settings.maxFanoutSize }} + {{- if .Values.global.settings.exposeInvitationURLsTeamAllowlist }} + exposeInvitationURLsTeamAllowlist: {{ .Values.global.settings.exposeInvitationURLsTeamAllowlist }} + {{- else }} exposeInvitationURLsTeamAllowlist: {{ .settings.exposeInvitationURLsTeamAllowlist }} + {{- end }} + {{- if .Values.global.settings.maxConvSize }} + maxConvSize: {{ .Values.global.settings.maxConvSize }} + {{- else }} maxConvSize: {{ .settings.maxConvSize }} + {{- end }} + {{- if .Values.global.settings.intraListing }} + intraListing: {{ .Values.global.settings.intraListing }} + {{- else }} intraListing: {{ .settings.intraListing }} - {{- if .settings.conversationCodeURI }} + {{- end }} + {{- if or .Values.global.settings.conversationCodeURI .settings.conversationCodeURI }} + {{- if .Values.global.settings.conversationCodeURI }} + conversationCodeURI: {{ .Values.global.settings.conversationCodeURI | quote }} + {{- else }} conversationCodeURI: {{ .settings.conversationCodeURI | quote }} - {{- else if .settings.multiIngress }} + {{- end }} + {{- else if or .Values.global.settings.multiIngress .settings.multiIngress }} + {{- if .Values.global.settings.multiIngress }} + multiIngress: {{- toYaml .Values.global.settings.multiIngress | nindent 8 }} + {{- else }} multiIngress: {{- toYaml .settings.multiIngress | nindent 8 }} {{- end }} + {{- end }} {{- if .settings.federationProtocols }} federationProtocols: {{ .settings.federationProtocols }} {{- end }} - {{- if .settings.guestLinkTTLSeconds }} + {{- if .Values.global.settings.guestLinkTTLSeconds }} + guestLinkTTLSeconds: {{ .Values.global.settings.guestLinkTTLSeconds }} + {{- else }} guestLinkTTLSeconds: {{ .settings.guestLinkTTLSeconds }} {{- end }} + {{- if .Values.global.settings.passwordHashingOptions }} + passwordHashingOptions: {{- toYaml .Values.global.settings.passwordHashingOptions | nindent 8 }} + {{- else }} passwordHashingOptions: {{ toYaml .settings.passwordHashingOptions | nindent 8 }} + {{- end }} + {{- if .Values.global.settings.passwordHashingRateLimit }} + passwordHashingRateLimit: {{- toYaml .Values.global.settings.passwordHashingRateLimit | nindent 8 }} + {{- else }} passwordHashingRateLimit: {{ toYaml .settings.passwordHashingRateLimit | nindent 8 }} + {{- end }} {{- if .settings.checkGroupInfo }} checkGroupInfo: {{ .settings.checkGroupInfo }} {{- end }} diff --git a/charts/wire-server/templates/galley/configmap.yaml b/charts/wire-server/templates/galley/configmap.yaml index 1afe5e88786..e24d5d74ba7 100644 --- a/charts/wire-server/templates/galley/configmap.yaml +++ b/charts/wire-server/templates/galley/configmap.yaml @@ -71,24 +71,46 @@ data: settings: httpPoolSize: {{ .settings.httpPoolSize }} + {{- if .Values.global.settings.intraListing }} + intraListing: {{ .Values.global.settings.intraListing }} + {{- else }} intraListing: {{ .settings.intraListing }} + {{- end }} + {{- if .Values.global.settings.maxTeamSize }} + maxTeamSize: {{ .Values.global.settings.maxTeamSize }} + {{- else }} maxTeamSize: {{ .settings.maxTeamSize }} + {{- end }} + {{- if .Values.global.settings.maxConvSize }} + maxConvSize: {{ .Values.global.settings.maxConvSize }} + {{- else }} maxConvSize: {{ .settings.maxConvSize }} + {{- end }} {{- if .settings.maxFanoutSize }} maxFanoutSize: {{ .settings.maxFanoutSize }} {{- end }} - {{- if .settings.exposeInvitationURLsTeamAllowlist }} + {{- if .Values.global.settings.exposeInvitationURLsTeamAllowlist }} + exposeInvitationURLsTeamAllowlist: {{ .Values.global.settings.exposeInvitationURLsTeamAllowlist }} + {{- else if .settings.exposeInvitationURLsTeamAllowlist }} exposeInvitationURLsTeamAllowlist: {{ .settings.exposeInvitationURLsTeamAllowlist }} {{- end }} - {{- if .settings.conversationCodeURI }} + {{- if or .Values.global.settings.conversationCodeURI .settings.conversationCodeURI }} + {{- if .Values.global.settings.conversationCodeURI }} + conversationCodeURI: {{ .Values.global.settings.conversationCodeURI | quote }} + {{- else }} conversationCodeURI: {{ .settings.conversationCodeURI | quote }} - {{- else if .settings.multiIngress }} + {{- end }} + {{- else if or .Values.global.settings.multiIngress .settings.multiIngress }} + {{- if .Values.global.settings.multiIngress }} + multiIngress: {{- toYaml .Values.global.settings.multiIngress | nindent 8 }} + {{- else }} multiIngress: {{- toYaml .settings.multiIngress | nindent 8 }} + {{- end }} {{- else }} - {{ fail "Either settings.conversationCodeURI or settings.multiIngress have to be set"}} + {{ fail "Either global.settings.conversationCodeURI, global.settings.multiIngress, galley.config.settings.conversationCodeURI, or galley.config.settings.multiIngress have to be set"}} {{- end }} - {{- if (and .settings.conversationCodeURI .settings.multiIngress) }} - {{ fail "settings.conversationCodeURI and settings.multiIngress are mutually exclusive" }} + {{- if (or (and .Values.global.settings.conversationCodeURI .Values.global.settings.multiIngress) (and .settings.conversationCodeURI .settings.multiIngress)) }} + {{ fail "conversationCodeURI and multiIngress are mutually exclusive in both global.settings and galley.config.settings" }} {{- end }} federationDomain: {{ $.Values.galley.config.settings.federationDomain }} {{- if .settings.federationProtocols }} @@ -104,11 +126,21 @@ data: {{- end }} disabledAPIVersions: {{ toJson .settings.disabledAPIVersions }} {{- if .settings.featureFlags }} - {{- if .settings.guestLinkTTLSeconds }} + {{- if .Values.global.settings.guestLinkTTLSeconds }} + guestLinkTTLSeconds: {{ .Values.global.settings.guestLinkTTLSeconds }} + {{- else if .settings.guestLinkTTLSeconds }} guestLinkTTLSeconds: {{ .settings.guestLinkTTLSeconds }} {{- end }} + {{- if .Values.global.settings.passwordHashingOptions }} + passwordHashingOptions: {{- toYaml .Values.global.settings.passwordHashingOptions | nindent 8 }} + {{- else }} passwordHashingOptions: {{ toYaml .settings.passwordHashingOptions | nindent 8 }} + {{- end }} + {{- if .Values.global.settings.passwordHashingRateLimit }} + passwordHashingRateLimit: {{- toYaml .Values.global.settings.passwordHashingRateLimit | nindent 8 }} + {{- else }} passwordHashingRateLimit: {{ toYaml .settings.passwordHashingRateLimit | nindent 8 }} + {{- end }} {{- if .settings.checkGroupInfo }} checkGroupInfo: {{ .settings.checkGroupInfo }} {{- end }} diff --git a/charts/wire-server/values.yaml b/charts/wire-server/values.yaml index 0cea842d6c5..0491cabba4c 100644 --- a/charts/wire-server/values.yaml +++ b/charts/wire-server/values.yaml @@ -12,6 +12,49 @@ tags: mlsstats: false integration: false +# Global settings shared across multiple services +global: + settings: + maxTeamSize: 10000 + maxConvSize: 500 + intraListing: true + exposeInvitationURLsTeamAllowlist: [] + guestLinkTTLSeconds: 31536000 + passwordHashingOptions: + algorithm: scrypt # or argon2id + # When algorithm is argon2id, these can be configured: + # iterations: + # parallelism: + # memory: + passwordHashingRateLimit: + ipAddrLimit: + burst: 5 + inverseRate: 300000000 # 5 mins, makes it 12 reqs/hour + userLimit: + burst: 5 + inverseRate: 60000000 # 1 min, makes it 60 req/hour + internalLimit: + burst: 10 + inverseRate: 0 # No rate limiting for internal use + ipv4CidrBlock: 32 # Only block individual IP addresses + ipv6CidrBlock: 64 # Block /64 range at a time. + ipAddressExceptions: [] + maxRateLimitedKeys: 100000 # Estimated memory usage: 4 MB + # Either `conversationCodeURI` or `multiIngress` must be set + # + # `conversationCodeURI` is the URI prefix for conversation invitation links + # It should be of form https://{ACCOUNT_PAGES}/conversation-join/ + conversationCodeURI: null + # + # `multiIngress` is a `Z-Host` depended setting of conversationCodeURI. + # Use this only if you want to expose the instance on multiple ingresses. + # If set it must a map from `Z-Host` to URI prefix + # Example: + # multiIngress: + # wire.example: https://accounts.wire.example/conversation-join/ + # example.net: https://accounts.example.net/conversation-join/ + multiIngress: null + galley: replicaCount: 3 image: @@ -1041,31 +1084,8 @@ background-worker: teamFeatures: cassandra settings: - maxTeamSize: 10000 maxFanoutSize: 500 - exposeInvitationURLsTeamAllowlist: [] - maxConvSize: 500 - intraListing: true - conversationCodeURI: null - multiIngress: null federationProtocols: null - guestLinkTTLSeconds: 31536000 - passwordHashingOptions: - algorithm: scrypt # or argon2id - passwordHashingRateLimit: - ipAddrLimit: - burst: 5 - inverseRate: 300000000 # 5 mins, makes it 12 reqs/hour - userLimit: - burst: 5 - inverseRate: 60000000 # 1 min, makes it 60 req/hour - internalLimit: - burst: 10 - inverseRate: 0 # No rate limiting for internal use - ipv4CidrBlock: 32 # Only block individual IP addresses - ipv6CidrBlock: 64 # Block /64 range at a time. - ipAddressExceptions: [] - maxRateLimitedKeys: 100000 # Estimated memory usage: 4 MB secrets: {} From 9c937611a31a637ff73a1eb2b8477911ad6423dd Mon Sep 17 00:00:00 2001 From: Gautier DI FOLCO Date: Thu, 2 Apr 2026 11:44:02 +0200 Subject: [PATCH 12/19] fix: charts --- .../background-worker/configmap.yaml | 40 ++++++++--------- .../templates/galley/configmap.yaml | 44 +++++++++---------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/charts/wire-server/templates/background-worker/configmap.yaml b/charts/wire-server/templates/background-worker/configmap.yaml index 26410b193c2..78a277d58e2 100644 --- a/charts/wire-server/templates/background-worker/configmap.yaml +++ b/charts/wire-server/templates/background-worker/configmap.yaml @@ -114,36 +114,36 @@ data: {{- end }} settings: - {{- if .Values.global.settings.maxTeamSize }} - maxTeamSize: {{ .Values.global.settings.maxTeamSize }} + {{- if $.Values.global.settings.maxTeamSize }} + maxTeamSize: {{ $.Values.global.settings.maxTeamSize }} {{- else }} maxTeamSize: {{ .settings.maxTeamSize }} {{- end }} maxFanoutSize: {{ .settings.maxFanoutSize }} - {{- if .Values.global.settings.exposeInvitationURLsTeamAllowlist }} - exposeInvitationURLsTeamAllowlist: {{ .Values.global.settings.exposeInvitationURLsTeamAllowlist }} + {{- if $.Values.global.settings.exposeInvitationURLsTeamAllowlist }} + exposeInvitationURLsTeamAllowlist: {{ $.Values.global.settings.exposeInvitationURLsTeamAllowlist }} {{- else }} exposeInvitationURLsTeamAllowlist: {{ .settings.exposeInvitationURLsTeamAllowlist }} {{- end }} - {{- if .Values.global.settings.maxConvSize }} - maxConvSize: {{ .Values.global.settings.maxConvSize }} + {{- if $.Values.global.settings.maxConvSize }} + maxConvSize: {{ $.Values.global.settings.maxConvSize }} {{- else }} maxConvSize: {{ .settings.maxConvSize }} {{- end }} - {{- if .Values.global.settings.intraListing }} - intraListing: {{ .Values.global.settings.intraListing }} + {{- if $.Values.global.settings.intraListing }} + intraListing: {{ $.Values.global.settings.intraListing }} {{- else }} intraListing: {{ .settings.intraListing }} {{- end }} - {{- if or .Values.global.settings.conversationCodeURI .settings.conversationCodeURI }} - {{- if .Values.global.settings.conversationCodeURI }} - conversationCodeURI: {{ .Values.global.settings.conversationCodeURI | quote }} + {{- if or $.Values.global.settings.conversationCodeURI .settings.conversationCodeURI }} + {{- if $.Values.global.settings.conversationCodeURI }} + conversationCodeURI: {{ $.Values.global.settings.conversationCodeURI | quote }} {{- else }} conversationCodeURI: {{ .settings.conversationCodeURI | quote }} {{- end }} - {{- else if or .Values.global.settings.multiIngress .settings.multiIngress }} - {{- if .Values.global.settings.multiIngress }} - multiIngress: {{- toYaml .Values.global.settings.multiIngress | nindent 8 }} + {{- else if or $.Values.global.settings.multiIngress .settings.multiIngress }} + {{- if $.Values.global.settings.multiIngress }} + multiIngress: {{- toYaml $.Values.global.settings.multiIngress | nindent 8 }} {{- else }} multiIngress: {{- toYaml .settings.multiIngress | nindent 8 }} {{- end }} @@ -151,18 +151,18 @@ data: {{- if .settings.federationProtocols }} federationProtocols: {{ .settings.federationProtocols }} {{- end }} - {{- if .Values.global.settings.guestLinkTTLSeconds }} - guestLinkTTLSeconds: {{ .Values.global.settings.guestLinkTTLSeconds }} + {{- if $.Values.global.settings.guestLinkTTLSeconds }} + guestLinkTTLSeconds: {{ $.Values.global.settings.guestLinkTTLSeconds }} {{- else }} guestLinkTTLSeconds: {{ .settings.guestLinkTTLSeconds }} {{- end }} - {{- if .Values.global.settings.passwordHashingOptions }} - passwordHashingOptions: {{- toYaml .Values.global.settings.passwordHashingOptions | nindent 8 }} + {{- if $.Values.global.settings.passwordHashingOptions }} + passwordHashingOptions: {{- toYaml $.Values.global.settings.passwordHashingOptions | nindent 8 }} {{- else }} passwordHashingOptions: {{ toYaml .settings.passwordHashingOptions | nindent 8 }} {{- end }} - {{- if .Values.global.settings.passwordHashingRateLimit }} - passwordHashingRateLimit: {{- toYaml .Values.global.settings.passwordHashingRateLimit | nindent 8 }} + {{- if $.Values.global.settings.passwordHashingRateLimit }} + passwordHashingRateLimit: {{- toYaml $.Values.global.settings.passwordHashingRateLimit | nindent 8 }} {{- else }} passwordHashingRateLimit: {{ toYaml .settings.passwordHashingRateLimit | nindent 8 }} {{- end }} diff --git a/charts/wire-server/templates/galley/configmap.yaml b/charts/wire-server/templates/galley/configmap.yaml index e24d5d74ba7..79c3c9e3b24 100644 --- a/charts/wire-server/templates/galley/configmap.yaml +++ b/charts/wire-server/templates/galley/configmap.yaml @@ -71,48 +71,48 @@ data: settings: httpPoolSize: {{ .settings.httpPoolSize }} - {{- if .Values.global.settings.intraListing }} - intraListing: {{ .Values.global.settings.intraListing }} + {{- if $.Values.global.settings.intraListing }} + intraListing: {{ $.Values.global.settings.intraListing }} {{- else }} intraListing: {{ .settings.intraListing }} {{- end }} - {{- if .Values.global.settings.maxTeamSize }} - maxTeamSize: {{ .Values.global.settings.maxTeamSize }} + {{- if $.Values.global.settings.maxTeamSize }} + maxTeamSize: {{ $.Values.global.settings.maxTeamSize }} {{- else }} maxTeamSize: {{ .settings.maxTeamSize }} {{- end }} - {{- if .Values.global.settings.maxConvSize }} - maxConvSize: {{ .Values.global.settings.maxConvSize }} + {{- if $.Values.global.settings.maxConvSize }} + maxConvSize: {{ $.Values.global.settings.maxConvSize }} {{- else }} maxConvSize: {{ .settings.maxConvSize }} {{- end }} {{- if .settings.maxFanoutSize }} maxFanoutSize: {{ .settings.maxFanoutSize }} {{- end }} - {{- if .Values.global.settings.exposeInvitationURLsTeamAllowlist }} - exposeInvitationURLsTeamAllowlist: {{ .Values.global.settings.exposeInvitationURLsTeamAllowlist }} + {{- if $.Values.global.settings.exposeInvitationURLsTeamAllowlist }} + exposeInvitationURLsTeamAllowlist: {{ $.Values.global.settings.exposeInvitationURLsTeamAllowlist }} {{- else if .settings.exposeInvitationURLsTeamAllowlist }} exposeInvitationURLsTeamAllowlist: {{ .settings.exposeInvitationURLsTeamAllowlist }} {{- end }} - {{- if or .Values.global.settings.conversationCodeURI .settings.conversationCodeURI }} - {{- if .Values.global.settings.conversationCodeURI }} - conversationCodeURI: {{ .Values.global.settings.conversationCodeURI | quote }} + {{- if or $.Values.global.settings.conversationCodeURI .settings.conversationCodeURI }} + {{- if $.Values.global.settings.conversationCodeURI }} + conversationCodeURI: {{ $.Values.global.settings.conversationCodeURI | quote }} {{- else }} conversationCodeURI: {{ .settings.conversationCodeURI | quote }} {{- end }} - {{- else if or .Values.global.settings.multiIngress .settings.multiIngress }} - {{- if .Values.global.settings.multiIngress }} - multiIngress: {{- toYaml .Values.global.settings.multiIngress | nindent 8 }} + {{- else if or $.Values.global.settings.multiIngress .settings.multiIngress }} + {{- if $.Values.global.settings.multiIngress }} + multiIngress: {{- toYaml $.Values.global.settings.multiIngress | nindent 8 }} {{- else }} multiIngress: {{- toYaml .settings.multiIngress | nindent 8 }} {{- end }} {{- else }} {{ fail "Either global.settings.conversationCodeURI, global.settings.multiIngress, galley.config.settings.conversationCodeURI, or galley.config.settings.multiIngress have to be set"}} {{- end }} - {{- if (or (and .Values.global.settings.conversationCodeURI .Values.global.settings.multiIngress) (and .settings.conversationCodeURI .settings.multiIngress)) }} + {{- if (or (and $.Values.global.settings.conversationCodeURI $.Values.global.settings.multiIngress) (and .settings.conversationCodeURI .settings.multiIngress)) }} {{ fail "conversationCodeURI and multiIngress are mutually exclusive in both global.settings and galley.config.settings" }} {{- end }} - federationDomain: {{ $.Values.galley.config.settings.federationDomain }} + federationDomain: {{ .settings.federationDomain }} {{- if .settings.federationProtocols }} federationProtocols: {{ .settings.federationProtocols }} {{- end }} @@ -126,18 +126,18 @@ data: {{- end }} disabledAPIVersions: {{ toJson .settings.disabledAPIVersions }} {{- if .settings.featureFlags }} - {{- if .Values.global.settings.guestLinkTTLSeconds }} - guestLinkTTLSeconds: {{ .Values.global.settings.guestLinkTTLSeconds }} + {{- if $.Values.global.settings.guestLinkTTLSeconds }} + guestLinkTTLSeconds: {{ $.Values.global.settings.guestLinkTTLSeconds }} {{- else if .settings.guestLinkTTLSeconds }} guestLinkTTLSeconds: {{ .settings.guestLinkTTLSeconds }} {{- end }} - {{- if .Values.global.settings.passwordHashingOptions }} - passwordHashingOptions: {{- toYaml .Values.global.settings.passwordHashingOptions | nindent 8 }} + {{- if $.Values.global.settings.passwordHashingOptions }} + passwordHashingOptions: {{- toYaml $.Values.global.settings.passwordHashingOptions | nindent 8 }} {{- else }} passwordHashingOptions: {{ toYaml .settings.passwordHashingOptions | nindent 8 }} {{- end }} - {{- if .Values.global.settings.passwordHashingRateLimit }} - passwordHashingRateLimit: {{- toYaml .Values.global.settings.passwordHashingRateLimit | nindent 8 }} + {{- if $.Values.global.settings.passwordHashingRateLimit }} + passwordHashingRateLimit: {{- toYaml $.Values.global.settings.passwordHashingRateLimit | nindent 8 }} {{- else }} passwordHashingRateLimit: {{ toYaml .settings.passwordHashingRateLimit | nindent 8 }} {{- end }} From 056e6f5356feb7a15563aa6df61851ef09839f77 Mon Sep 17 00:00:00 2001 From: Gautier DI FOLCO Date: Thu, 2 Apr 2026 18:31:37 +0200 Subject: [PATCH 13/19] fix: charts --- .../cassandra-migrations/templates/_helpers.tpl | 16 ++++++++-------- .../templates/migrate-schema.yaml | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/charts/cassandra-migrations/templates/_helpers.tpl b/charts/cassandra-migrations/templates/_helpers.tpl index 3ce89ba357c..e0396236e59 100644 --- a/charts/cassandra-migrations/templates/_helpers.tpl +++ b/charts/cassandra-migrations/templates/_helpers.tpl @@ -38,7 +38,7 @@ Thus the order of priority is: {{- define "cassandraGalleyReplicationArg" -}} {{ $cassandraGalley := default dict .Values.cassandraGalley }} -{{ $cassandra := default dict .Values.cassandra }} +{{- $cassandra := default dict .Values.cassandra }} {{- if (or $cassandra.replicationMap $cassandraGalley.replicationMap) -}} {{- default ($cassandra.replicationMap) $cassandraGalley.replicationMap -}} {{- else -}} @@ -48,7 +48,7 @@ Thus the order of priority is: {{- define "cassandraGalleyReplicationType" -}} {{ $cassandraGalley := default dict .Values.cassandraGalley }} -{{ $cassandra := default dict .Values.cassandra }} +{{- $cassandra := default dict .Values.cassandra }} {{- if (or $cassandra.replicationMap $cassandraGalley.replicationMap) -}} {{- printf "--replication-map" -}} {{- else -}} @@ -59,7 +59,7 @@ Thus the order of priority is: {{- define "cassandraGundeckReplicationArg" -}} {{ $cassandraGundeck := default dict .Values.cassandraGundeck }} -{{ $cassandra := default dict .Values.cassandra }} +{{- $cassandra := default dict .Values.cassandra }} {{- if (or $cassandra.replicationMap $cassandraGundeck.replicationMap) -}} {{- default ($cassandra.replicationMap) $cassandraGundeck.replicationMap -}} {{- else -}} @@ -69,7 +69,7 @@ Thus the order of priority is: {{- define "cassandraGundeckReplicationType" -}} {{ $cassandraGundeck := default dict .Values.cassandraGundeck }} -{{ $cassandra := default dict .Values.cassandra }} +{{- $cassandra := default dict .Values.cassandra }} {{- if (or $cassandra.replicationMap $cassandraGundeck.replicationMap) -}} {{- printf "--replication-map" -}} {{- else -}} @@ -80,7 +80,7 @@ Thus the order of priority is: {{- define "cassandraBrigReplicationArg" -}} {{ $cassandraBrig := default dict .Values.cassandraBrig }} -{{ $cassandra := default dict .Values.cassandra }} +{{- $cassandra := default dict .Values.cassandra }} {{- if (or $cassandra.replicationMap $cassandraBrig.replicationMap) -}} {{- default ($cassandra.replicationMap) $cassandraBrig.replicationMap -}} {{- else -}} @@ -90,7 +90,7 @@ Thus the order of priority is: {{- define "cassandraBrigReplicationType" -}} {{ $cassandraBrig := default dict .Values.cassandraBrig }} -{{ $cassandra := default dict .Values.cassandra }} +{{- $cassandra := default dict .Values.cassandra }} {{- if (or $cassandra.replicationMap $cassandraBrig.replicationMap) -}} {{- printf "--replication-map" -}} {{- else -}} @@ -101,7 +101,7 @@ Thus the order of priority is: {{- define "cassandraSparReplicationArg" -}} {{ $cassandraSpar := default dict .Values.cassandraSpar }} -{{ $cassandra := default dict .Values.cassandra }} +{{- $cassandra := default dict .Values.cassandra }} {{- if (or $cassandra.replicationMap $cassandraSpar.replicationMap) -}} {{- default ($cassandra.replicationMap) $cassandraSpar.replicationMap -}} {{- else -}} @@ -111,7 +111,7 @@ Thus the order of priority is: {{- define "cassandraSparReplicationType" -}} {{ $cassandraSpar := default dict .Values.cassandraSpar }} -{{ $cassandra := default dict .Values.cassandra }} +{{- $cassandra := default dict .Values.cassandra }} {{- if (or $cassandra.replicationMap $cassandraSpar.replicationMap) -}} {{- printf "--replication-map" -}} {{- else -}} diff --git a/charts/cassandra-migrations/templates/migrate-schema.yaml b/charts/cassandra-migrations/templates/migrate-schema.yaml index af51d187145..810b799bf84 100644 --- a/charts/cassandra-migrations/templates/migrate-schema.yaml +++ b/charts/cassandra-migrations/templates/migrate-schema.yaml @@ -162,7 +162,7 @@ spec: - name: spar-cassandra-cert mountPath: "/certs/spar" {{- end }} - {{- end }} + {{- end }} containers: - name: job-done From 5ea131442ab1a62f37642fd9c8473e73848c6c05 Mon Sep 17 00:00:00 2001 From: Gautier DI FOLCO Date: Thu, 2 Apr 2026 19:13:42 +0200 Subject: [PATCH 14/19] Hello CI From 48226e718b6b8831806e2d110754c1304166599c Mon Sep 17 00:00:00 2001 From: Gautier DI FOLCO Date: Thu, 2 Apr 2026 19:45:45 +0200 Subject: [PATCH 15/19] fix: remove helm config (fresh start) --- .../templates/_helpers.tpl | 88 +++++++------------ .../templates/migrate-schema.yaml | 2 +- .../background-worker/configmap.yaml | 58 ------------ .../templates/galley/configmap.yaml | 48 ++-------- charts/wire-server/values.yaml | 47 ---------- hack/helm_vars/wire-server/values.yaml.gotmpl | 41 --------- .../background-worker.integration.yaml | 31 ------- 7 files changed, 41 insertions(+), 274 deletions(-) diff --git a/charts/cassandra-migrations/templates/_helpers.tpl b/charts/cassandra-migrations/templates/_helpers.tpl index e0396236e59..0d805051ffe 100644 --- a/charts/cassandra-migrations/templates/_helpers.tpl +++ b/charts/cassandra-migrations/templates/_helpers.tpl @@ -1,25 +1,21 @@ {{- define "cassandraGalleyHost" -}} {{ $cassandraGalley := default dict .Values.cassandraGalley }} -{{- $cassandra := default dict .Values.cassandra }} -{{- default ($cassandra.host) $cassandraGalley.host }} +{{- default (.Values.cassandra.host) $cassandraGalley.host }} {{- end -}} {{- define "cassandraBrigHost" -}} {{ $cassandraBrig := default dict .Values.cassandraBrig }} -{{- $cassandra := default dict .Values.cassandra }} -{{- default ($cassandra.host) $cassandraBrig.host }} +{{- default (.Values.cassandra.host) $cassandraBrig.host }} {{- end -}} {{- define "cassandraGundeckHost" -}} {{ $cassandraGundeck := default dict .Values.cassandraGundeck }} -{{- $cassandra := default dict .Values.cassandra }} -{{- default ($cassandra.host) $cassandraGundeck.host }} +{{- default (.Values.cassandra.host) $cassandraGundeck.host }} {{- end -}} {{- define "cassandraSparHost" -}} {{ $cassandraSpar := default dict .Values.cassandraSpar }} -{{- $cassandra := default dict .Values.cassandra }} -{{- default ($cassandra.host) $cassandraSpar.host }} +{{- default (.Values.cassandra.host) $cassandraSpar.host }} {{- end -}} {{/* @@ -38,18 +34,16 @@ Thus the order of priority is: {{- define "cassandraGalleyReplicationArg" -}} {{ $cassandraGalley := default dict .Values.cassandraGalley }} -{{- $cassandra := default dict .Values.cassandra }} -{{- if (or $cassandra.replicationMap $cassandraGalley.replicationMap) -}} -{{- default ($cassandra.replicationMap) $cassandraGalley.replicationMap -}} +{{- if (or .Values.cassandra.replicationMap $cassandraGalley.replicationMap) -}} +{{- default (.Values.cassandra.replicationMap) $cassandraGalley.replicationMap -}} {{- else -}} -{{- default (default ($cassandra.replicaCount) $cassandra.replicationFactor) $cassandraGalley.replicationFactor -}} +{{- default (default (.Values.cassandra.replicaCount) .Values.cassandra.replicationFactor) $cassandraGalley.replicationFactor -}} {{- end -}} {{- end -}} {{- define "cassandraGalleyReplicationType" -}} {{ $cassandraGalley := default dict .Values.cassandraGalley }} -{{- $cassandra := default dict .Values.cassandra }} -{{- if (or $cassandra.replicationMap $cassandraGalley.replicationMap) -}} +{{- if (or .Values.cassandra.replicationMap $cassandraGalley.replicationMap) -}} {{- printf "--replication-map" -}} {{- else -}} {{- printf "--replication-factor" -}} @@ -59,18 +53,16 @@ Thus the order of priority is: {{- define "cassandraGundeckReplicationArg" -}} {{ $cassandraGundeck := default dict .Values.cassandraGundeck }} -{{- $cassandra := default dict .Values.cassandra }} -{{- if (or $cassandra.replicationMap $cassandraGundeck.replicationMap) -}} -{{- default ($cassandra.replicationMap) $cassandraGundeck.replicationMap -}} +{{- if (or .Values.cassandra.replicationMap $cassandraGundeck.replicationMap) -}} +{{- default (.Values.cassandra.replicationMap) $cassandraGundeck.replicationMap -}} {{- else -}} -{{- default (default ($cassandra.replicaCount) $cassandra.replicationFactor) $cassandraGundeck.replicationFactor -}} +{{- default (default (.Values.cassandra.replicaCount) .Values.cassandra.replicationFactor) $cassandraGundeck.replicationFactor -}} {{- end -}} {{- end -}} {{- define "cassandraGundeckReplicationType" -}} {{ $cassandraGundeck := default dict .Values.cassandraGundeck }} -{{- $cassandra := default dict .Values.cassandra }} -{{- if (or $cassandra.replicationMap $cassandraGundeck.replicationMap) -}} +{{- if (or .Values.cassandra.replicationMap $cassandraGundeck.replicationMap) -}} {{- printf "--replication-map" -}} {{- else -}} {{- printf "--replication-factor" -}} @@ -80,18 +72,16 @@ Thus the order of priority is: {{- define "cassandraBrigReplicationArg" -}} {{ $cassandraBrig := default dict .Values.cassandraBrig }} -{{- $cassandra := default dict .Values.cassandra }} -{{- if (or $cassandra.replicationMap $cassandraBrig.replicationMap) -}} -{{- default ($cassandra.replicationMap) $cassandraBrig.replicationMap -}} +{{- if (or .Values.cassandra.replicationMap $cassandraBrig.replicationMap) -}} +{{- default (.Values.cassandra.replicationMap) $cassandraBrig.replicationMap -}} {{- else -}} -{{- default (default ($cassandra.replicaCount) $cassandra.replicationFactor) $cassandraBrig.replicationFactor -}} +{{- default (default (.Values.cassandra.replicaCount) .Values.cassandra.replicationFactor) $cassandraBrig.replicationFactor -}} {{- end -}} {{- end -}} {{- define "cassandraBrigReplicationType" -}} {{ $cassandraBrig := default dict .Values.cassandraBrig }} -{{- $cassandra := default dict .Values.cassandra }} -{{- if (or $cassandra.replicationMap $cassandraBrig.replicationMap) -}} +{{- if (or .Values.cassandra.replicationMap $cassandraBrig.replicationMap) -}} {{- printf "--replication-map" -}} {{- else -}} {{- printf "--replication-factor" -}} @@ -101,18 +91,16 @@ Thus the order of priority is: {{- define "cassandraSparReplicationArg" -}} {{ $cassandraSpar := default dict .Values.cassandraSpar }} -{{- $cassandra := default dict .Values.cassandra }} -{{- if (or $cassandra.replicationMap $cassandraSpar.replicationMap) -}} -{{- default ($cassandra.replicationMap) $cassandraSpar.replicationMap -}} +{{- if (or .Values.cassandra.replicationMap $cassandraSpar.replicationMap) -}} +{{- default (.Values.cassandra.replicationMap) $cassandraSpar.replicationMap -}} {{- else -}} -{{- default (default ($cassandra.replicaCount) $cassandra.replicationFactor) $cassandraSpar.replicationFactor -}} +{{- default (default (.Values.cassandra.replicaCount) .Values.cassandra.replicationFactor) $cassandraSpar.replicationFactor -}} {{- end -}} {{- end -}} {{- define "cassandraSparReplicationType" -}} {{ $cassandraSpar := default dict .Values.cassandraSpar }} -{{- $cassandra := default dict .Values.cassandra }} -{{- if (or $cassandra.replicationMap $cassandraSpar.replicationMap) -}} +{{- if (or .Values.cassandra.replicationMap $cassandraSpar.replicationMap) -}} {{- printf "--replication-map" -}} {{- else -}} {{- printf "--replication-factor" -}} @@ -135,8 +123,7 @@ configuration if the specific one does not exist: */}} {{- define "useTlsGalley" -}} -{{ $cassandra := default dict .Values.cassandra }} -{{ $cassandraGalley := default $cassandra .Values.cassandraGalley }} +{{ $cassandraGalley := default .Values.cassandra .Values.cassandraGalley }} {{- if or $cassandraGalley.tlsCa $cassandraGalley.tlsCaSecretRef -}} true {{- else}} @@ -145,8 +132,7 @@ false {{- end -}} {{- define "tlsCaGalley" -}} -{{ $cassandra := default dict .Values.cassandra }} -{{ $cassandraGalley := default $cassandra .Values.cassandraGalley }} +{{ $cassandraGalley := default .Values.cassandra .Values.cassandraGalley }} {{- if hasKey $cassandraGalley "tlsCa" -}} {{- $cassandraGalley.tlsCa }} {{ else }} @@ -154,8 +140,7 @@ false {{- end -}} {{- define "tlsSecretRefGalley" -}} -{{ $cassandra := default dict .Values.cassandra }} -{{ $cassandraGalley := default $cassandra .Values.cassandraGalley }} +{{ $cassandraGalley := default .Values.cassandra .Values.cassandraGalley }} {{- if $cassandraGalley.tlsCaSecretRef -}} {{ $cassandraGalley.tlsCaSecretRef | toYaml }} {{- else }} @@ -164,8 +149,7 @@ false {{- end -}} {{- define "useTlsBrig" -}} -{{ $cassandra := default dict .Values.cassandra }} -{{ $cassandraBrig := default $cassandra .Values.cassandraBrig }} +{{ $cassandraBrig := default .Values.cassandra .Values.cassandraBrig }} {{- if or $cassandraBrig.tlsCa $cassandraBrig.tlsCaSecretRef -}} true {{- else}} @@ -174,8 +158,7 @@ false {{- end -}} {{- define "tlsCaBrig" -}} -{{ $cassandra := default dict .Values.cassandra }} -{{ $cassandraBrig := default $cassandra .Values.cassandraBrig }} +{{ $cassandraBrig := default .Values.cassandra .Values.cassandraBrig }} {{- if hasKey $cassandraBrig "tlsCa" -}} {{- $cassandraBrig.tlsCa }} {{ else }} @@ -183,8 +166,7 @@ false {{- end -}} {{- define "tlsSecretRefBrig" -}} -{{ $cassandra := default dict .Values.cassandra }} -{{ $cassandraBrig := default $cassandra .Values.cassandraBrig }} +{{ $cassandraBrig := default .Values.cassandra .Values.cassandraBrig }} {{- if $cassandraBrig.tlsCaSecretRef -}} {{ $cassandraBrig.tlsCaSecretRef | toYaml }} {{- else }} @@ -193,8 +175,7 @@ false {{- end -}} {{- define "useTlsSpar" -}} -{{ $cassandra := default dict .Values.cassandra }} -{{ $cassandraSpar := default $cassandra .Values.cassandraSpar }} +{{ $cassandraSpar := default .Values.cassandra .Values.cassandraSpar }} {{- if or $cassandraSpar.tlsCa $cassandraSpar.tlsCaSecretRef -}} true {{- else}} @@ -203,8 +184,7 @@ false {{- end -}} {{- define "tlsCaSpar" -}} -{{ $cassandra := default dict .Values.cassandra }} -{{ $cassandraSpar := default $cassandra .Values.cassandraSpar }} +{{ $cassandraSpar := default .Values.cassandra .Values.cassandraSpar }} {{- if hasKey $cassandraSpar "tlsCa" -}} {{- $cassandraSpar.tlsCa }} {{ else }} @@ -212,8 +192,7 @@ false {{- end -}} {{- define "tlsSecretRefSpar" -}} -{{ $cassandra := default dict .Values.cassandra }} -{{ $cassandraSpar := default $cassandra .Values.cassandraSpar }} +{{ $cassandraSpar := default .Values.cassandra .Values.cassandraSpar }} {{- if $cassandraSpar.tlsCaSecretRef -}} {{ $cassandraSpar.tlsCaSecretRef | toYaml }} {{- else }} @@ -222,8 +201,7 @@ false {{- end -}} {{- define "useTlsGundeck" -}} -{{ $cassandra := default dict .Values.cassandra }} -{{ $cassandraGundeck := default $cassandra .Values.cassandraGundeck }} +{{ $cassandraGundeck := default .Values.cassandra .Values.cassandraGundeck }} {{- if or $cassandraGundeck.tlsCa $cassandraGundeck.tlsCaSecretRef -}} true {{- else}} @@ -232,8 +210,7 @@ false {{- end -}} {{- define "tlsCaGundeck" -}} -{{ $cassandra := default dict .Values.cassandra }} -{{ $cassandraGundeck := default $cassandra .Values.cassandraGundeck }} +{{ $cassandraGundeck := default .Values.cassandra .Values.cassandraGundeck }} {{- if hasKey $cassandraGundeck "tlsCa" -}} {{- $cassandraGundeck.tlsCa }} {{ else }} @@ -241,8 +218,7 @@ false {{- end -}} {{- define "tlsSecretRefGundeck" -}} -{{ $cassandra := default dict .Values.cassandra }} -{{ $cassandraGundeck := default $cassandra .Values.cassandraGundeck }} +{{ $cassandraGundeck := default .Values.cassandra .Values.cassandraGundeck }} {{- if $cassandraGundeck.tlsCaSecretRef -}} {{ $cassandraGundeck.tlsCaSecretRef | toYaml }} {{- else }} diff --git a/charts/cassandra-migrations/templates/migrate-schema.yaml b/charts/cassandra-migrations/templates/migrate-schema.yaml index 810b799bf84..af51d187145 100644 --- a/charts/cassandra-migrations/templates/migrate-schema.yaml +++ b/charts/cassandra-migrations/templates/migrate-schema.yaml @@ -162,7 +162,7 @@ spec: - name: spar-cassandra-cert mountPath: "/certs/spar" {{- end }} - {{- end }} + {{- end }} containers: - name: job-done diff --git a/charts/wire-server/templates/background-worker/configmap.yaml b/charts/wire-server/templates/background-worker/configmap.yaml index 78a277d58e2..49c0c3d38d7 100644 --- a/charts/wire-server/templates/background-worker/configmap.yaml +++ b/charts/wire-server/templates/background-worker/configmap.yaml @@ -112,64 +112,6 @@ data: backgroundJobs: {{ toYaml . | indent 6 }} {{- end }} - - settings: - {{- if $.Values.global.settings.maxTeamSize }} - maxTeamSize: {{ $.Values.global.settings.maxTeamSize }} - {{- else }} - maxTeamSize: {{ .settings.maxTeamSize }} - {{- end }} - maxFanoutSize: {{ .settings.maxFanoutSize }} - {{- if $.Values.global.settings.exposeInvitationURLsTeamAllowlist }} - exposeInvitationURLsTeamAllowlist: {{ $.Values.global.settings.exposeInvitationURLsTeamAllowlist }} - {{- else }} - exposeInvitationURLsTeamAllowlist: {{ .settings.exposeInvitationURLsTeamAllowlist }} - {{- end }} - {{- if $.Values.global.settings.maxConvSize }} - maxConvSize: {{ $.Values.global.settings.maxConvSize }} - {{- else }} - maxConvSize: {{ .settings.maxConvSize }} - {{- end }} - {{- if $.Values.global.settings.intraListing }} - intraListing: {{ $.Values.global.settings.intraListing }} - {{- else }} - intraListing: {{ .settings.intraListing }} - {{- end }} - {{- if or $.Values.global.settings.conversationCodeURI .settings.conversationCodeURI }} - {{- if $.Values.global.settings.conversationCodeURI }} - conversationCodeURI: {{ $.Values.global.settings.conversationCodeURI | quote }} - {{- else }} - conversationCodeURI: {{ .settings.conversationCodeURI | quote }} - {{- end }} - {{- else if or $.Values.global.settings.multiIngress .settings.multiIngress }} - {{- if $.Values.global.settings.multiIngress }} - multiIngress: {{- toYaml $.Values.global.settings.multiIngress | nindent 8 }} - {{- else }} - multiIngress: {{- toYaml .settings.multiIngress | nindent 8 }} - {{- end }} - {{- end }} - {{- if .settings.federationProtocols }} - federationProtocols: {{ .settings.federationProtocols }} - {{- end }} - {{- if $.Values.global.settings.guestLinkTTLSeconds }} - guestLinkTTLSeconds: {{ $.Values.global.settings.guestLinkTTLSeconds }} - {{- else }} - guestLinkTTLSeconds: {{ .settings.guestLinkTTLSeconds }} - {{- end }} - {{- if $.Values.global.settings.passwordHashingOptions }} - passwordHashingOptions: {{- toYaml $.Values.global.settings.passwordHashingOptions | nindent 8 }} - {{- else }} - passwordHashingOptions: {{ toYaml .settings.passwordHashingOptions | nindent 8 }} - {{- end }} - {{- if $.Values.global.settings.passwordHashingRateLimit }} - passwordHashingRateLimit: {{- toYaml $.Values.global.settings.passwordHashingRateLimit | nindent 8 }} - {{- else }} - passwordHashingRateLimit: {{ toYaml .settings.passwordHashingRateLimit | nindent 8 }} - {{- end }} - {{- if .settings.checkGroupInfo }} - checkGroupInfo: {{ .settings.checkGroupInfo }} - {{- end }} - {{- if .postgresMigration }} postgresMigration: {{- toYaml .postgresMigration | nindent 6 }} {{- end }} diff --git a/charts/wire-server/templates/galley/configmap.yaml b/charts/wire-server/templates/galley/configmap.yaml index 79c3c9e3b24..1afe5e88786 100644 --- a/charts/wire-server/templates/galley/configmap.yaml +++ b/charts/wire-server/templates/galley/configmap.yaml @@ -71,48 +71,26 @@ data: settings: httpPoolSize: {{ .settings.httpPoolSize }} - {{- if $.Values.global.settings.intraListing }} - intraListing: {{ $.Values.global.settings.intraListing }} - {{- else }} intraListing: {{ .settings.intraListing }} - {{- end }} - {{- if $.Values.global.settings.maxTeamSize }} - maxTeamSize: {{ $.Values.global.settings.maxTeamSize }} - {{- else }} maxTeamSize: {{ .settings.maxTeamSize }} - {{- end }} - {{- if $.Values.global.settings.maxConvSize }} - maxConvSize: {{ $.Values.global.settings.maxConvSize }} - {{- else }} maxConvSize: {{ .settings.maxConvSize }} - {{- end }} {{- if .settings.maxFanoutSize }} maxFanoutSize: {{ .settings.maxFanoutSize }} {{- end }} - {{- if $.Values.global.settings.exposeInvitationURLsTeamAllowlist }} - exposeInvitationURLsTeamAllowlist: {{ $.Values.global.settings.exposeInvitationURLsTeamAllowlist }} - {{- else if .settings.exposeInvitationURLsTeamAllowlist }} + {{- if .settings.exposeInvitationURLsTeamAllowlist }} exposeInvitationURLsTeamAllowlist: {{ .settings.exposeInvitationURLsTeamAllowlist }} {{- end }} - {{- if or $.Values.global.settings.conversationCodeURI .settings.conversationCodeURI }} - {{- if $.Values.global.settings.conversationCodeURI }} - conversationCodeURI: {{ $.Values.global.settings.conversationCodeURI | quote }} - {{- else }} + {{- if .settings.conversationCodeURI }} conversationCodeURI: {{ .settings.conversationCodeURI | quote }} - {{- end }} - {{- else if or $.Values.global.settings.multiIngress .settings.multiIngress }} - {{- if $.Values.global.settings.multiIngress }} - multiIngress: {{- toYaml $.Values.global.settings.multiIngress | nindent 8 }} - {{- else }} + {{- else if .settings.multiIngress }} multiIngress: {{- toYaml .settings.multiIngress | nindent 8 }} - {{- end }} {{- else }} - {{ fail "Either global.settings.conversationCodeURI, global.settings.multiIngress, galley.config.settings.conversationCodeURI, or galley.config.settings.multiIngress have to be set"}} + {{ fail "Either settings.conversationCodeURI or settings.multiIngress have to be set"}} {{- end }} - {{- if (or (and $.Values.global.settings.conversationCodeURI $.Values.global.settings.multiIngress) (and .settings.conversationCodeURI .settings.multiIngress)) }} - {{ fail "conversationCodeURI and multiIngress are mutually exclusive in both global.settings and galley.config.settings" }} + {{- if (and .settings.conversationCodeURI .settings.multiIngress) }} + {{ fail "settings.conversationCodeURI and settings.multiIngress are mutually exclusive" }} {{- end }} - federationDomain: {{ .settings.federationDomain }} + federationDomain: {{ $.Values.galley.config.settings.federationDomain }} {{- if .settings.federationProtocols }} federationProtocols: {{ .settings.federationProtocols }} {{- end }} @@ -126,21 +104,11 @@ data: {{- end }} disabledAPIVersions: {{ toJson .settings.disabledAPIVersions }} {{- if .settings.featureFlags }} - {{- if $.Values.global.settings.guestLinkTTLSeconds }} - guestLinkTTLSeconds: {{ $.Values.global.settings.guestLinkTTLSeconds }} - {{- else if .settings.guestLinkTTLSeconds }} + {{- if .settings.guestLinkTTLSeconds }} guestLinkTTLSeconds: {{ .settings.guestLinkTTLSeconds }} {{- end }} - {{- if $.Values.global.settings.passwordHashingOptions }} - passwordHashingOptions: {{- toYaml $.Values.global.settings.passwordHashingOptions | nindent 8 }} - {{- else }} passwordHashingOptions: {{ toYaml .settings.passwordHashingOptions | nindent 8 }} - {{- end }} - {{- if $.Values.global.settings.passwordHashingRateLimit }} - passwordHashingRateLimit: {{- toYaml $.Values.global.settings.passwordHashingRateLimit | nindent 8 }} - {{- else }} passwordHashingRateLimit: {{ toYaml .settings.passwordHashingRateLimit | nindent 8 }} - {{- end }} {{- if .settings.checkGroupInfo }} checkGroupInfo: {{ .settings.checkGroupInfo }} {{- end }} diff --git a/charts/wire-server/values.yaml b/charts/wire-server/values.yaml index 0491cabba4c..5b6f0c99b0e 100644 --- a/charts/wire-server/values.yaml +++ b/charts/wire-server/values.yaml @@ -12,49 +12,6 @@ tags: mlsstats: false integration: false -# Global settings shared across multiple services -global: - settings: - maxTeamSize: 10000 - maxConvSize: 500 - intraListing: true - exposeInvitationURLsTeamAllowlist: [] - guestLinkTTLSeconds: 31536000 - passwordHashingOptions: - algorithm: scrypt # or argon2id - # When algorithm is argon2id, these can be configured: - # iterations: - # parallelism: - # memory: - passwordHashingRateLimit: - ipAddrLimit: - burst: 5 - inverseRate: 300000000 # 5 mins, makes it 12 reqs/hour - userLimit: - burst: 5 - inverseRate: 60000000 # 1 min, makes it 60 req/hour - internalLimit: - burst: 10 - inverseRate: 0 # No rate limiting for internal use - ipv4CidrBlock: 32 # Only block individual IP addresses - ipv6CidrBlock: 64 # Block /64 range at a time. - ipAddressExceptions: [] - maxRateLimitedKeys: 100000 # Estimated memory usage: 4 MB - # Either `conversationCodeURI` or `multiIngress` must be set - # - # `conversationCodeURI` is the URI prefix for conversation invitation links - # It should be of form https://{ACCOUNT_PAGES}/conversation-join/ - conversationCodeURI: null - # - # `multiIngress` is a `Z-Host` depended setting of conversationCodeURI. - # Use this only if you want to expose the instance on multiple ingresses. - # If set it must a map from `Z-Host` to URI prefix - # Example: - # multiIngress: - # wire.example: https://accounts.wire.example/conversation-join/ - # example.net: https://accounts.example.net/conversation-join/ - multiIngress: null - galley: replicaCount: 3 image: @@ -1083,10 +1040,6 @@ background-worker: conversationCodes: cassandra teamFeatures: cassandra - settings: - maxFanoutSize: 500 - federationProtocols: null - secrets: {} # pgPassword: diff --git a/hack/helm_vars/wire-server/values.yaml.gotmpl b/hack/helm_vars/wire-server/values.yaml.gotmpl index 5818028f563..5255592075c 100644 --- a/hack/helm_vars/wire-server/values.yaml.gotmpl +++ b/hack/helm_vars/wire-server/values.yaml.gotmpl @@ -321,20 +321,8 @@ galley: parallelism: 4 memory: 32 # This needs to be at least 8 * parallelism. passwordHashingRateLimit: - ipAddrLimit: - burst: 5 - inverseRate: 300000000 # 5 mins, makes it 12 reqs/hour - userLimit: - burst: 5 - inverseRate: 60000000 # 1 min, makes it 60 req/hour - internalLimit: - burst: 10 - inverseRate: 0 # No rate limiting for internal use - ipv4CidrBlock: 32 # Only block individual IP addresses - ipv6CidrBlock: 64 # Block /64 range at a time. ipAddressExceptions: - 127.0.0.1/8 - maxRateLimitedKeys: 100000 featureFlags: sso: disabled-by-default # this needs to be the default; tests can enable it when needed. @@ -694,35 +682,6 @@ background-worker: tlsCaSecretRef: name: "rabbitmq-certificate" key: "ca.crt" - settings: - maxTeamSize: 32 - maxFanoutSize: 18 - exposeInvitationURLsTeamAllowlist: [] - maxConvSize: 16 - intraListing: true - conversationCodeURI: https://kube-staging-nginz-https.zinfra.io/conversation-join/ - guestLinkTTLSeconds: 31536000 - passwordHashingOptions: - algorithm: argon2id - iterations: 1 - parallelism: 4 - memory: 32 # This needs to be at least 8 * parallelism. - passwordHashingRateLimit: - ipAddrLimit: - burst: 5 - inverseRate: 300000000 # 5 mins, makes it 12 reqs/hour - userLimit: - burst: 5 - inverseRate: 60000000 # 1 min, makes it 60 req/hour - internalLimit: - burst: 10 - inverseRate: 0 # No rate limiting for internal use - ipv4CidrBlock: 32 # Only block individual IP addresses - ipv6CidrBlock: 64 # Block /64 range at a time. - ipAddressExceptions: - - 127.0.0.1/8 - maxRateLimitedKeys: 100000 - checkGroupInfo: false secrets: pgPassword: "posty-the-gres" rabbitmq: diff --git a/services/background-worker/background-worker.integration.yaml b/services/background-worker/background-worker.integration.yaml index 4b3978cab45..167dfc498b9 100644 --- a/services/background-worker/background-worker.integration.yaml +++ b/services/background-worker/background-worker.integration.yaml @@ -79,37 +79,6 @@ backgroundJobs: jobTimeout: 5s maxAttempts: 3 -settings: - maxTeamSize: 32 - maxFanoutSize: 18 - exposeInvitationURLsTeamAllowlist: [] - maxConvSize: 16 - intraListing: false - conversationCodeURI: https://account.wire.com/conversation-join/ - federationProtocols: ["mls", "proteus"] - guestLinkTTLSeconds: 604800 - passwordHashingOptions: - algorithm: argon2id - iterations: 1 - memory: 128 - parallelism: 1 - passwordHashingRateLimit: - ipAddrLimit: - burst: 5 - inverseRate: 300000000 - userLimit: - burst: 5 - inverseRate: 60000000 - internalLimit: - burst: 10 - inverseRate: 0 - ipv4CidrBlock: 32 - ipv6CidrBlock: 64 - ipAddressExceptions: - - 127.0.0.1/8 - maxRateLimitedKeys: 100000 - checkGroupInfo: false - postgresMigration: conversation: postgresql conversationCodes: postgresql From 418f85ff2153313723b00420c76f590101cc66da Mon Sep 17 00:00:00 2001 From: Gautier DI FOLCO Date: Fri, 3 Apr 2026 12:25:04 +0200 Subject: [PATCH 16/19] fix: hardcode configs --- .../background-worker/configmap.yaml | 32 +++++++++++++++++++ charts/wire-server/values.yaml | 23 +++++++++++++ hack/helm_vars/wire-server/values.yaml.gotmpl | 19 +++++++++++ .../background-worker.integration.yaml | 28 ++++++++++++++++ 4 files changed, 102 insertions(+) diff --git a/charts/wire-server/templates/background-worker/configmap.yaml b/charts/wire-server/templates/background-worker/configmap.yaml index 49c0c3d38d7..1d6ff162d16 100644 --- a/charts/wire-server/templates/background-worker/configmap.yaml +++ b/charts/wire-server/templates/background-worker/configmap.yaml @@ -115,4 +115,36 @@ data: {{- if .postgresMigration }} postgresMigration: {{- toYaml .postgresMigration | nindent 6 }} {{- end }} + + settings: + maxTeamSize: {{ .settings.maxTeamSize }} + {{- if .settings.maxFanoutSize }} + maxFanoutSize: {{ .settings.maxFanoutSize }} + {{- end }} + {{- if .settings.exposeInvitationURLsTeamAllowlist }} + exposeInvitationURLsTeamAllowlist: {{ toYaml .settings.exposeInvitationURLsTeamAllowlist | nindent 8 }} + {{- end }} + maxConvSize: {{ .settings.maxConvSize }} + intraListing: {{ .settings.intraListing }} + {{- if .settings.conversationCodeURI }} + conversationCodeURI: {{ .settings.conversationCodeURI | quote }} + {{- else if .settings.multiIngress }} + multiIngress: {{- toYaml .settings.multiIngress | nindent 8 }} + {{- else }} + {{ fail "Either settings.conversationCodeURI or settings.multiIngress have to be set"}} + {{- end }} + {{- if (and .settings.conversationCodeURI .settings.multiIngress) }} + {{ fail "settings.conversationCodeURI and settings.multiIngress are mutually exclusive" }} + {{- end }} + {{- if .settings.federationProtocols }} + federationProtocols: {{ .settings.federationProtocols }} + {{- end }} + {{- if .settings.guestLinkTTLSeconds }} + guestLinkTTLSeconds: {{ .settings.guestLinkTTLSeconds }} + {{- end }} + passwordHashingOptions: {{ toYaml .settings.passwordHashingOptions | nindent 8 }} + passwordHashingRateLimit: {{ toYaml .settings.passwordHashingRateLimit | nindent 8 }} + {{- if .settings.checkGroupInfo }} + checkGroupInfo: {{ .settings.checkGroupInfo }} + {{- end }} {{- end }} diff --git a/charts/wire-server/values.yaml b/charts/wire-server/values.yaml index 5b6f0c99b0e..1b4deb1b476 100644 --- a/charts/wire-server/values.yaml +++ b/charts/wire-server/values.yaml @@ -1040,6 +1040,29 @@ background-worker: conversationCodes: cassandra teamFeatures: cassandra + settings: + maxTeamSize: 10000 + maxConvSize: 500 + intraListing: true + conversationCodeURI: null + multiIngress: null + passwordHashingOptions: + algorithm: scrypt + passwordHashingRateLimit: + ipAddrLimit: + burst: 5 + inverseRate: 300000000 + userLimit: + burst: 5 + inverseRate: 60000000 + internalLimit: + burst: 10 + inverseRate: 0 + ipv4CidrBlock: 32 + ipv6CidrBlock: 64 + ipAddressExceptions: [] + maxRateLimitedKeys: 100000 + secrets: {} # pgPassword: diff --git a/hack/helm_vars/wire-server/values.yaml.gotmpl b/hack/helm_vars/wire-server/values.yaml.gotmpl index 5255592075c..6fa77c04b46 100644 --- a/hack/helm_vars/wire-server/values.yaml.gotmpl +++ b/hack/helm_vars/wire-server/values.yaml.gotmpl @@ -682,6 +682,25 @@ background-worker: tlsCaSecretRef: name: "rabbitmq-certificate" key: "ca.crt" + settings: + maxTeamSize: 32 + maxConvSize: 16 + intraListing: false + conversationCodeURI: https://account.wire.com/conversation-join/ + federationProtocols: ["mls", "proteus"] + guestLinkTTLSeconds: 604800 + + # These values are insecure, against anyone getting hold of the hash, + # but its not a concern for the integration tests. + passwordHashingOptions: + algorithm: argon2id + iterations: 1 + parallelism: 4 + memory: 32 # This needs to be at least 8 * parallelism. + passwordHashingRateLimit: + ipAddressExceptions: + - 127.0.0.1/8 + maxRateLimitedKeys: 100000 secrets: pgPassword: "posty-the-gres" rabbitmq: diff --git a/services/background-worker/background-worker.integration.yaml b/services/background-worker/background-worker.integration.yaml index 167dfc498b9..3a88788891f 100644 --- a/services/background-worker/background-worker.integration.yaml +++ b/services/background-worker/background-worker.integration.yaml @@ -83,3 +83,31 @@ postgresMigration: conversation: postgresql conversationCodes: postgresql teamFeatures: postgresql + +settings: + maxTeamSize: 32 + maxConvSize: 16 + intraListing: false + conversationCodeURI: https://account.wire.com/conversation-join/ + federationProtocols: ["mls", "proteus"] + guestLinkTTLSeconds: 604800 + passwordHashingOptions: + algorithm: argon2id + iterations: 1 + memory: 128 + parallelism: 1 + passwordHashingRateLimit: + ipAddrLimit: + burst: 5 + inverseRate: 300000000 + userLimit: + burst: 5 + inverseRate: 60000000 + internalLimit: + burst: 10 + inverseRate: 0 + ipv4CidrBlock: 32 + ipv6CidrBlock: 64 + ipAddressExceptions: + - 127.0.0.1/8 + maxRateLimitedKeys: 100000 From 78f2ab2116fe2a8bbb493757955dcd59033c4fde Mon Sep 17 00:00:00 2001 From: Gautier DI FOLCO Date: Fri, 3 Apr 2026 14:27:13 +0200 Subject: [PATCH 17/19] fix: hardcode configs (fix) --- charts/wire-server/templates/background-worker/configmap.yaml | 2 +- charts/wire-server/templates/galley/configmap.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/wire-server/templates/background-worker/configmap.yaml b/charts/wire-server/templates/background-worker/configmap.yaml index 1d6ff162d16..2b59a825bba 100644 --- a/charts/wire-server/templates/background-worker/configmap.yaml +++ b/charts/wire-server/templates/background-worker/configmap.yaml @@ -137,7 +137,7 @@ data: {{ fail "settings.conversationCodeURI and settings.multiIngress are mutually exclusive" }} {{- end }} {{- if .settings.federationProtocols }} - federationProtocols: {{ .settings.federationProtocols }} + federationProtocols: {{ .settings.federationProtocols | toJson }} {{- end }} {{- if .settings.guestLinkTTLSeconds }} guestLinkTTLSeconds: {{ .settings.guestLinkTTLSeconds }} diff --git a/charts/wire-server/templates/galley/configmap.yaml b/charts/wire-server/templates/galley/configmap.yaml index 1afe5e88786..8d6ad5a7669 100644 --- a/charts/wire-server/templates/galley/configmap.yaml +++ b/charts/wire-server/templates/galley/configmap.yaml @@ -92,7 +92,7 @@ data: {{- end }} federationDomain: {{ $.Values.galley.config.settings.federationDomain }} {{- if .settings.federationProtocols }} - federationProtocols: {{ .settings.federationProtocols }} + federationProtocols: {{ .settings.federationProtocols | toJson }} {{- end }} {{- if $.Values.galley.secrets.mlsPrivateKeys }} mlsPrivateKeyPaths: From 7d7f918a31ccf5ebf19d614d828f0b31608871b9 Mon Sep 17 00:00:00 2001 From: Gautier DI FOLCO Date: Fri, 3 Apr 2026 17:50:45 +0200 Subject: [PATCH 18/19] refactor: helm charts --- charts/wire-server/templates/_helpers.tpl | 174 ++++++++++++++++++ .../background-worker/configmap.yaml | 31 +--- .../templates/galley/configmap.yaml | 162 +--------------- charts/wire-server/values.yaml | 6 + hack/helm_vars/wire-server/values.yaml.gotmpl | 6 + 5 files changed, 188 insertions(+), 191 deletions(-) diff --git a/charts/wire-server/templates/_helpers.tpl b/charts/wire-server/templates/_helpers.tpl index 0cd6d5ede53..7a4152d86f2 100644 --- a/charts/wire-server/templates/_helpers.tpl +++ b/charts/wire-server/templates/_helpers.tpl @@ -163,6 +163,180 @@ {{- end -}} {{- end -}} +{{/* SHARED SETTINGS */}} +{{- define "wire-server.settings.common" -}} +maxTeamSize: {{ .maxTeamSize }} +maxConvSize: {{ .maxConvSize }} +intraListing: {{ .intraListing }} +{{- if .maxFanoutSize }} +maxFanoutSize: {{ .maxFanoutSize }} +{{- end }} +{{- if .exposeInvitationURLsTeamAllowlist }} +exposeInvitationURLsTeamAllowlist: {{ toYaml .exposeInvitationURLsTeamAllowlist | nindent 8 }} +{{- end }} +{{- if .conversationCodeURI }} +conversationCodeURI: {{ .conversationCodeURI | quote }} +{{- else if .multiIngress }} +multiIngress: {{- toYaml .multiIngress | nindent 8 }} +{{- else }} +{{ fail "Either settings.conversationCodeURI or settings.multiIngress have to be set" }} +{{- end }} +{{- if (and .conversationCodeURI .multiIngress) }} +{{ fail "settings.conversationCodeURI and settings.multiIngress are mutually exclusive" }} +{{- end }} +{{- if .httpPoolSize }} +httpPoolSize: {{ .httpPoolSize }} +{{- end }} +{{- if .federationDomain }} +federationDomain: {{ .federationDomain }} +{{- end }} +{{- if .federationProtocols }} +federationProtocols: {{ .federationProtocols | toJson }} +{{- end }} +{{- if .mlsPrivateKeyPaths }} +mlsPrivateKeyPaths: {{- toYaml .mlsPrivateKeyPaths | nindent 8 }} +{{- end }} +{{- if .concurrentDeletionEvents }} +concurrentDeletionEvents: {{ .concurrentDeletionEvents }} +{{- end }} +{{- if .deleteConvThrottleMillis }} +deleteConvThrottleMillis: {{ .deleteConvThrottleMillis }} +{{- end }} +{{- if .disabledAPIVersions }} +disabledAPIVersions: {{ toJson .disabledAPIVersions }} +{{- end }} +{{- if .guestLinkTTLSeconds }} +guestLinkTTLSeconds: {{ .guestLinkTTLSeconds }} +{{- end }} +passwordHashingOptions: {{ toYaml .passwordHashingOptions | nindent 8 }} +passwordHashingRateLimit: {{ toYaml .passwordHashingRateLimit | nindent 8 }} +{{- if .checkGroupInfo }} +checkGroupInfo: {{ .checkGroupInfo }} +{{- end }} +{{- if .meetings }} +meetings: + {{- toYaml .meetings | nindent 8 }} +{{- end }} +{{- if .featureFlags }} +featureFlags: + sso: {{ .featureFlags.sso }} + legalhold: {{ .featureFlags.legalhold }} + teamSearchVisibility: {{ .featureFlags.teamSearchVisibility }} + classifiedDomains: + {{- toYaml .featureFlags.classifiedDomains | nindent 10 }} + {{- if .featureFlags.fileSharing }} + fileSharing: + {{- toYaml .featureFlags.fileSharing | nindent 10 }} + {{- end }} + {{- if .featureFlags.enforceFileDownloadLocation }} + enforceFileDownloadLocation: + {{- toYaml .featureFlags.enforceFileDownloadLocation | nindent 10 }} + {{- end }} + {{- if .featureFlags.sndFactorPasswordChallenge }} + sndFactorPasswordChallenge: + {{- toYaml .featureFlags.sndFactorPasswordChallenge | nindent 10 }} + {{- end }} + {{- if .featureFlags.searchVisibilityInbound }} + searchVisibilityInbound: + {{- toYaml .featureFlags.searchVisibilityInbound | nindent 10 }} + {{- end }} + {{- /* Accept the legacy typo in Helm values, but always render the canonical Galley key. */}} + {{- $validateSAMLemails := .featureFlags.validateSAMLemails | default .featureFlags.validateSAMLEmails }} + {{- if $validateSAMLemails }} + validateSAMLemails: + {{- toYaml $validateSAMLemails | nindent 10 }} + {{- end }} + {{- if .featureFlags.appLock }} + appLock: + {{- toYaml .featureFlags.appLock | nindent 10 }} + {{- end }} + {{- if .featureFlags.conferenceCalling }} + conferenceCalling: + {{- toYaml .featureFlags.conferenceCalling | nindent 10 }} + {{- end }} + {{- if .featureFlags.selfDeletingMessages }} + selfDeletingMessages: + {{- toYaml .featureFlags.selfDeletingMessages | nindent 10 }} + {{- end }} + {{- if .featureFlags.conversationGuestLinks }} + conversationGuestLinks: + {{- toYaml .featureFlags.conversationGuestLinks | nindent 10 }} + {{- end }} + {{- if .featureFlags.mls }} + mls: + {{- toYaml .featureFlags.mls | nindent 10 }} + {{- end }} + {{- if .featureFlags.outlookCalIntegration }} + outlookCalIntegration: + {{- toYaml .featureFlags.outlookCalIntegration | nindent 10 }} + {{- end }} + {{- if .featureFlags.mlsE2EId }} + mlsE2EId: + {{- toYaml .featureFlags.mlsE2EId | nindent 10 }} + {{- end }} + {{- if .featureFlags.mlsMigration }} + mlsMigration: + {{- toYaml .featureFlags.mlsMigration | nindent 10 }} + {{- end }} + {{- if .featureFlags.limitedEventFanout }} + limitedEventFanout: + {{- toYaml .featureFlags.limitedEventFanout | nindent 10 }} + {{- end }} + {{- if .featureFlags.domainRegistration }} + domainRegistration: + {{- toYaml .featureFlags.domainRegistration | nindent 10 }} + {{- end }} + {{- if .featureFlags.channels }} + channels: + {{- toYaml .featureFlags.channels | nindent 10 }} + {{- end }} + {{- if .featureFlags.cells }} + cells: + {{- toYaml .featureFlags.cells | nindent 10 }} + {{- end }} + {{- if .featureFlags.cellsInternal }} + cellsInternal: + {{- toYaml .featureFlags.cellsInternal | nindent 10 }} + {{- end }} + {{- if .featureFlags.allowedGlobalOperations }} + allowedGlobalOperations: + {{- toYaml .featureFlags.allowedGlobalOperations | nindent 10 }} + {{- end }} + {{- if .featureFlags.assetAuditLog }} + assetAuditLog: + {{- toYaml .featureFlags.assetAuditLog | nindent 10 }} + {{- end }} + {{- if .featureFlags.consumableNotifications }} + consumableNotifications: + {{- toYaml .featureFlags.consumableNotifications | nindent 10 }} + {{- end }} + {{- if .featureFlags.chatBubbles }} + chatBubbles: + {{- toYaml .featureFlags.chatBubbles | nindent 10 }} + {{- end }} + {{- if .featureFlags.apps }} + apps: + {{- toYaml .featureFlags.apps | nindent 10 }} + {{- end }} + {{- if .featureFlags.simplifiedUserConnectionRequestQRCode }} + simplifiedUserConnectionRequestQRCode: + {{- toYaml .featureFlags.simplifiedUserConnectionRequestQRCode | nindent 10 }} + {{- end }} + {{- if .featureFlags.stealthUsers }} + stealthUsers: + {{- toYaml .featureFlags.stealthUsers | nindent 10 }} + {{- end }} + {{- if .featureFlags.meetings }} + meetings: + {{- toYaml .featureFlags.meetings | nindent 10 }} + {{- end }} + {{- if .featureFlags.meetingsPremium }} + meetingsPremium: + {{- toYaml .featureFlags.meetingsPremium | nindent 10 }} + {{- end }} +{{- end }} +{{- end -}} + {{/* Compute the SCIM base URI The rules are: diff --git a/charts/wire-server/templates/background-worker/configmap.yaml b/charts/wire-server/templates/background-worker/configmap.yaml index 2b59a825bba..9fb893ebc17 100644 --- a/charts/wire-server/templates/background-worker/configmap.yaml +++ b/charts/wire-server/templates/background-worker/configmap.yaml @@ -117,34 +117,5 @@ data: {{- end }} settings: - maxTeamSize: {{ .settings.maxTeamSize }} - {{- if .settings.maxFanoutSize }} - maxFanoutSize: {{ .settings.maxFanoutSize }} - {{- end }} - {{- if .settings.exposeInvitationURLsTeamAllowlist }} - exposeInvitationURLsTeamAllowlist: {{ toYaml .settings.exposeInvitationURLsTeamAllowlist | nindent 8 }} - {{- end }} - maxConvSize: {{ .settings.maxConvSize }} - intraListing: {{ .settings.intraListing }} - {{- if .settings.conversationCodeURI }} - conversationCodeURI: {{ .settings.conversationCodeURI | quote }} - {{- else if .settings.multiIngress }} - multiIngress: {{- toYaml .settings.multiIngress | nindent 8 }} - {{- else }} - {{ fail "Either settings.conversationCodeURI or settings.multiIngress have to be set"}} - {{- end }} - {{- if (and .settings.conversationCodeURI .settings.multiIngress) }} - {{ fail "settings.conversationCodeURI and settings.multiIngress are mutually exclusive" }} - {{- end }} - {{- if .settings.federationProtocols }} - federationProtocols: {{ .settings.federationProtocols | toJson }} - {{- end }} - {{- if .settings.guestLinkTTLSeconds }} - guestLinkTTLSeconds: {{ .settings.guestLinkTTLSeconds }} - {{- end }} - passwordHashingOptions: {{ toYaml .settings.passwordHashingOptions | nindent 8 }} - passwordHashingRateLimit: {{ toYaml .settings.passwordHashingRateLimit | nindent 8 }} - {{- if .settings.checkGroupInfo }} - checkGroupInfo: {{ .settings.checkGroupInfo }} - {{- end }} +{{ include "wire-server.settings.common" .settings | indent 6 }} {{- end }} diff --git a/charts/wire-server/templates/galley/configmap.yaml b/charts/wire-server/templates/galley/configmap.yaml index 8d6ad5a7669..47bcffaafbf 100644 --- a/charts/wire-server/templates/galley/configmap.yaml +++ b/charts/wire-server/templates/galley/configmap.yaml @@ -70,165 +70,5 @@ data: postgresMigration: {{- toYaml .postgresMigration | nindent 6 }} settings: - httpPoolSize: {{ .settings.httpPoolSize }} - intraListing: {{ .settings.intraListing }} - maxTeamSize: {{ .settings.maxTeamSize }} - maxConvSize: {{ .settings.maxConvSize }} - {{- if .settings.maxFanoutSize }} - maxFanoutSize: {{ .settings.maxFanoutSize }} - {{- end }} - {{- if .settings.exposeInvitationURLsTeamAllowlist }} - exposeInvitationURLsTeamAllowlist: {{ .settings.exposeInvitationURLsTeamAllowlist }} - {{- end }} - {{- if .settings.conversationCodeURI }} - conversationCodeURI: {{ .settings.conversationCodeURI | quote }} - {{- else if .settings.multiIngress }} - multiIngress: {{- toYaml .settings.multiIngress | nindent 8 }} - {{- else }} - {{ fail "Either settings.conversationCodeURI or settings.multiIngress have to be set"}} - {{- end }} - {{- if (and .settings.conversationCodeURI .settings.multiIngress) }} - {{ fail "settings.conversationCodeURI and settings.multiIngress are mutually exclusive" }} - {{- end }} - federationDomain: {{ $.Values.galley.config.settings.federationDomain }} - {{- if .settings.federationProtocols }} - federationProtocols: {{ .settings.federationProtocols | toJson }} - {{- end }} - {{- if $.Values.galley.secrets.mlsPrivateKeys }} - mlsPrivateKeyPaths: - removal: - ed25519: "/etc/wire/galley/secrets/removal_ed25519.pem" - ecdsa_secp256r1_sha256: "/etc/wire/galley/secrets/removal_ecdsa_secp256r1_sha256.pem" - ecdsa_secp384r1_sha384: "/etc/wire/galley/secrets/removal_ecdsa_secp384r1_sha384.pem" - ecdsa_secp521r1_sha512: "/etc/wire/galley/secrets/removal_ecdsa_secp521r1_sha512.pem" - {{- end }} - disabledAPIVersions: {{ toJson .settings.disabledAPIVersions }} - {{- if .settings.featureFlags }} - {{- if .settings.guestLinkTTLSeconds }} - guestLinkTTLSeconds: {{ .settings.guestLinkTTLSeconds }} - {{- end }} - passwordHashingOptions: {{ toYaml .settings.passwordHashingOptions | nindent 8 }} - passwordHashingRateLimit: {{ toYaml .settings.passwordHashingRateLimit | nindent 8 }} - {{- if .settings.checkGroupInfo }} - checkGroupInfo: {{ .settings.checkGroupInfo }} - {{- end }} - meetings: - {{- toYaml .settings.meetings | nindent 8 }} - featureFlags: - sso: {{ .settings.featureFlags.sso }} - legalhold: {{ .settings.featureFlags.legalhold }} - teamSearchVisibility: {{ .settings.featureFlags.teamSearchVisibility }} - classifiedDomains: - {{- toYaml .settings.featureFlags.classifiedDomains | nindent 10 }} - {{- if .settings.featureFlags.fileSharing }} - fileSharing: - {{- toYaml .settings.featureFlags.fileSharing | nindent 10 }} - {{- end }} - {{- if .settings.featureFlags.enforceFileDownloadLocation }} - enforceFileDownloadLocation: - {{- toYaml .settings.featureFlags.enforceFileDownloadLocation | nindent 10 }} - {{- end }} - {{- if .settings.featureFlags.sndFactorPasswordChallenge }} - sndFactorPasswordChallenge: - {{- toYaml .settings.featureFlags.sndFactorPasswordChallenge | nindent 10 }} - {{- end }} - {{- if .settings.featureFlags.searchVisibilityInbound }} - searchVisibilityInbound: - {{- toYaml .settings.featureFlags.searchVisibilityInbound | nindent 10 }} - {{- end }} - {{- /* Accept the legacy typo in Helm values, but always render the canonical Galley key. */}} - {{- $validateSAMLemails := .settings.featureFlags.validateSAMLemails | default .settings.featureFlags.validateSAMLEmails }} - {{- if $validateSAMLemails }} - validateSAMLemails: - {{- toYaml $validateSAMLemails | nindent 10 }} - {{- end }} - {{- if .settings.featureFlags.appLock }} - appLock: - {{- toYaml .settings.featureFlags.appLock | nindent 10 }} - {{- end }} - {{- if .settings.featureFlags.conferenceCalling }} - conferenceCalling: - {{- toYaml .settings.featureFlags.conferenceCalling | nindent 10 }} - {{- end }} - {{- if .settings.featureFlags.selfDeletingMessages }} - selfDeletingMessages: - {{- toYaml .settings.featureFlags.selfDeletingMessages | nindent 10 }} - {{- end }} - {{- if .settings.featureFlags.conversationGuestLinks }} - conversationGuestLinks: - {{- toYaml .settings.featureFlags.conversationGuestLinks | nindent 10 }} - {{- end }} - {{- if .settings.featureFlags.mls }} - mls: - {{- toYaml .settings.featureFlags.mls | nindent 10 }} - {{- end }} - {{- if .settings.featureFlags.outlookCalIntegration }} - outlookCalIntegration: - {{- toYaml .settings.featureFlags.outlookCalIntegration | nindent 10 }} - {{- end }} - {{- if .settings.featureFlags.mlsE2EId }} - mlsE2EId: - {{- toYaml .settings.featureFlags.mlsE2EId | nindent 10 }} - {{- end }} - {{- if .settings.featureFlags.mlsMigration }} - mlsMigration: - {{- toYaml .settings.featureFlags.mlsMigration | nindent 10 }} - {{- end }} - {{- if .settings.featureFlags.limitedEventFanout }} - limitedEventFanout: - {{- toYaml .settings.featureFlags.limitedEventFanout | nindent 10 }} - {{- end }} - {{- if .settings.featureFlags.domainRegistration }} - domainRegistration: - {{- toYaml .settings.featureFlags.domainRegistration | nindent 10 }} - {{- end }} - {{- if .settings.featureFlags.channels }} - channels: - {{- toYaml .settings.featureFlags.channels | nindent 10 }} - {{- end }} - {{- if .settings.featureFlags.cells }} - cells: - {{- toYaml .settings.featureFlags.cells | nindent 10 }} - {{- end }} - {{- if .settings.featureFlags.cellsInternal }} - cellsInternal: - {{- toYaml .settings.featureFlags.cellsInternal | nindent 10 }} - {{- end }} - {{- if .settings.featureFlags.allowedGlobalOperations }} - allowedGlobalOperations: - {{- toYaml .settings.featureFlags.allowedGlobalOperations | nindent 10 }} - {{- end }} - {{- if .settings.featureFlags.assetAuditLog }} - assetAuditLog: - {{- toYaml .settings.featureFlags.assetAuditLog | nindent 10 }} - {{- end }} - {{- if .settings.featureFlags.consumableNotifications }} - consumableNotifications: - {{- toYaml .settings.featureFlags.consumableNotifications | nindent 10 }} - {{- end }} - {{- if .settings.featureFlags.chatBubbles }} - chatBubbles: - {{- toYaml .settings.featureFlags.chatBubbles | nindent 10 }} - {{- end }} - {{- if .settings.featureFlags.apps }} - apps: - {{- toYaml .settings.featureFlags.apps | nindent 10 }} - {{- end }} - {{- if .settings.featureFlags.simplifiedUserConnectionRequestQRCode }} - simplifiedUserConnectionRequestQRCode: - {{- toYaml .settings.featureFlags.simplifiedUserConnectionRequestQRCode | nindent 10 }} - {{- end }} - {{- if .settings.featureFlags.stealthUsers }} - stealthUsers: - {{- toYaml .settings.featureFlags.stealthUsers | nindent 10 }} - {{- end }} - {{- if .settings.featureFlags.meetings }} - meetings: - {{- toYaml .settings.featureFlags.meetings | nindent 10 }} - {{- end }} - {{- if .settings.featureFlags.meetingsPremium }} - meetingsPremium: - {{- toYaml .settings.featureFlags.meetingsPremium | nindent 10 }} - {{- end }} - {{- end }} +{{ include "wire-server.settings.common" .settings | indent 6 }} {{- end }} diff --git a/charts/wire-server/values.yaml b/charts/wire-server/values.yaml index 1b4deb1b476..34ff62066aa 100644 --- a/charts/wire-server/values.yaml +++ b/charts/wire-server/values.yaml @@ -316,6 +316,12 @@ galley: defaults: status: disabled lockStatus: locked + mlsPrivateKeyPaths: + removal: + ed25519: "/etc/wire/galley/secrets/removal_ed25519.pem" + ecdsa_secp256r1_sha256: "/etc/wire/galley/secrets/removal_ecdsa_secp256r1_sha256.pem" + ecdsa_secp384r1_sha384: "/etc/wire/galley/secrets/removal_ecdsa_secp384r1_sha384.pem" + ecdsa_secp521r1_sha512: "/etc/wire/galley/secrets/removal_ecdsa_secp521r1_sha512.pem" aws: region: "eu-west-1" proxy: {} diff --git a/hack/helm_vars/wire-server/values.yaml.gotmpl b/hack/helm_vars/wire-server/values.yaml.gotmpl index 6fa77c04b46..ea48b50a08f 100644 --- a/hack/helm_vars/wire-server/values.yaml.gotmpl +++ b/hack/helm_vars/wire-server/values.yaml.gotmpl @@ -409,6 +409,12 @@ galley: defaults: status: disabled lockStatus: locked + mlsPrivateKeyPaths: + removal: + ed25519: "/etc/wire/galley/secrets/removal_ed25519.pem" + ecdsa_secp256r1_sha256: "/etc/wire/galley/secrets/removal_ecdsa_secp256r1_sha256.pem" + ecdsa_secp384r1_sha384: "/etc/wire/galley/secrets/removal_ecdsa_secp384r1_sha384.pem" + ecdsa_secp521r1_sha512: "/etc/wire/galley/secrets/removal_ecdsa_secp521r1_sha512.pem" journal: endpoint: http://fake-aws-sqs:4568 queueName: integration-team-events.fifo From be612558434e484029b9c1b657fdc60a5de5d7ce Mon Sep 17 00:00:00 2001 From: Gautier DI FOLCO Date: Fri, 3 Apr 2026 18:40:01 +0200 Subject: [PATCH 19/19] refactor: helm charts (fix) --- charts/wire-server/templates/_helpers.tpl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/charts/wire-server/templates/_helpers.tpl b/charts/wire-server/templates/_helpers.tpl index 7a4152d86f2..661828fef7b 100644 --- a/charts/wire-server/templates/_helpers.tpl +++ b/charts/wire-server/templates/_helpers.tpl @@ -184,10 +184,10 @@ multiIngress: {{- toYaml .multiIngress | nindent 8 }} {{- if (and .conversationCodeURI .multiIngress) }} {{ fail "settings.conversationCodeURI and settings.multiIngress are mutually exclusive" }} {{- end }} -{{- if .httpPoolSize }} +{{- if hasKey . "httpPoolSize" }} httpPoolSize: {{ .httpPoolSize }} {{- end }} -{{- if .federationDomain }} +{{- if hasKey . "federationDomain" }} federationDomain: {{ .federationDomain }} {{- end }} {{- if .federationProtocols }} @@ -202,7 +202,7 @@ concurrentDeletionEvents: {{ .concurrentDeletionEvents }} {{- if .deleteConvThrottleMillis }} deleteConvThrottleMillis: {{ .deleteConvThrottleMillis }} {{- end }} -{{- if .disabledAPIVersions }} +{{- if hasKey . "disabledAPIVersions" }} disabledAPIVersions: {{ toJson .disabledAPIVersions }} {{- end }} {{- if .guestLinkTTLSeconds }} @@ -213,7 +213,7 @@ passwordHashingRateLimit: {{ toYaml .passwordHashingRateLimit | nindent 8 }} {{- if .checkGroupInfo }} checkGroupInfo: {{ .checkGroupInfo }} {{- end }} -{{- if .meetings }} +{{- if hasKey . "meetings" }} meetings: {{- toYaml .meetings | nindent 8 }} {{- end }}