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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/1-api-changes/get-mls-self-conversation
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support MLS self-conversations via a new endpoint `GET /conversations/mls-self`. This removes the `PUT` counterpart introduced in #2730
3 changes: 3 additions & 0 deletions libs/wire-api/src/Wire/API/Error/Galley.hs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ data GalleyError
| MLSClientSenderUserMismatch
| MLSWelcomeMismatch
| MLSMissingGroupInfo
| MLSMissingSenderClient
| --
NoBindingTeamMembers
| NoBindingTeam
Expand Down Expand Up @@ -206,6 +207,8 @@ type instance MapError 'MLSWelcomeMismatch = 'StaticError 400 "mls-welcome-misma

type instance MapError 'MLSMissingGroupInfo = 'StaticError 404 "mls-missing-group-info" "The conversation has no group information"

type instance MapError 'MLSMissingSenderClient = 'StaticError 403 "mls-missing-sender-client" "The client has to refresh their access token and provide their client ID"

type instance MapError 'NoBindingTeamMembers = 'StaticError 403 "non-binding-team-members" "Both users must be members of the same binding team"

type instance MapError 'NoBindingTeam = 'StaticError 403 "no-binding-team" "Operation allowed only on binding teams"
Expand Down
22 changes: 12 additions & 10 deletions libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ type ConversationResponse = ResponseForExistedCreated Conversation

type ConversationHeaders = '[DescHeader "Location" "Conversation ID" ConvId]

type ConversationVerbWithMethod (m :: StdMethod) =
type ConversationVerb =
MultiVerb
m
'POST
'[JSON]
'[ WithHeaders
ConversationHeaders
Expand All @@ -58,10 +58,6 @@ type ConversationVerbWithMethod (m :: StdMethod) =
]
ConversationResponse

type ConversationVerb = ConversationVerbWithMethod 'POST

type ConversationPutVerb = ConversationVerbWithMethod 'PUT

type CreateConversationCodeVerb =
MultiVerb
'POST
Expand Down Expand Up @@ -275,13 +271,19 @@ type ConversationAPI =
:> ConversationVerb
)
:<|> Named
"create-mls-self-conversation"
( Summary "Create the user's MLS self-conversation"
"get-mls-self-conversation"
( Summary "Get the user's MLS self-conversation"
:> ZLocalUser
:> "conversations"
:> "mls-self"
:> ZClient
:> ConversationPutVerb
:> MultiVerb1
'GET
'[JSON]
( Respond
200
"The MLS self-conversation"
Conversation
)
)
-- This endpoint can lead to the following events being sent:
-- - ConvCreate event to members
Expand Down
3 changes: 3 additions & 0 deletions libs/wire-api/src/Wire/API/Routes/Public/Galley/MLS.hs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ type MLSMessagingAPI =
:> CanThrow 'MLSUnsupportedProposal
:> CanThrow 'MLSClientSenderUserMismatch
:> CanThrow 'MLSGroupConversationMismatch
:> CanThrow 'MLSMissingSenderClient
:> CanThrow 'MissingLegalholdConsent
:> CanThrow MLSProposalFailure
:> "messages"
Expand Down Expand Up @@ -89,6 +90,7 @@ type MLSMessagingAPI =
:> CanThrow 'MLSUnsupportedProposal
:> CanThrow 'MLSClientSenderUserMismatch
:> CanThrow 'MLSGroupConversationMismatch
:> CanThrow 'MLSMissingSenderClient
:> CanThrow 'MissingLegalholdConsent
:> CanThrow MLSProposalFailure
:> "messages"
Expand Down Expand Up @@ -116,6 +118,7 @@ type MLSMessagingAPI =
:> CanThrow 'MLSUnsupportedProposal
:> CanThrow 'MLSClientSenderUserMismatch
:> CanThrow 'MLSGroupConversationMismatch
:> CanThrow 'MLSMissingSenderClient
:> CanThrow 'MLSWelcomeMismatch
:> CanThrow 'MissingLegalholdConsent
:> CanThrow MLSProposalFailure
Expand Down
37 changes: 0 additions & 37 deletions services/galley/src/Galley/API/Create.hs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
module Galley.API.Create
( createGroupConversation,
createProteusSelfConversation,
createMLSSelfConversation,
createOne2OneConversation,
createConnectConversation,
)
Expand Down Expand Up @@ -214,42 +213,6 @@ createProteusSelfConversation lusr = do
c <- E.createConversation lcnv nc
conversationCreated lusr c

createMLSSelfConversation ::
forall r.
Members
'[ ConversationStore,
Error InternalError,
MemberStore,
P.TinyLog,
Input Env
]
r =>
Local UserId ->
ClientId ->
Sem r ConversationResponse
createMLSSelfConversation lusr clientId = do
let selfConvId = mlsSelfConvId <$> lusr
mconv <- E.getConversation (tUnqualified selfConvId)
maybe (create selfConvId) (conversationExisted lusr) mconv
where
create :: Local ConvId -> Sem r ConversationResponse
create lcnv = do
unlessM (isJust <$> getMLSRemovalKey) $
throw (InternalErrorWithDescription "No backend removal key is configured (See 'mlsPrivateKeyPaths' in galley's config). Refusing to create MLS conversation.")
let nc =
NewConversation
{ ncMetadata =
(defConversationMetadata (tUnqualified lusr))
{ cnvmType = SelfConv
},
ncUsers = ulFromLocals [toUserRole (tUnqualified lusr)],
ncProtocol = ProtocolMLSTag
}
conv <- E.createConversation lcnv nc
-- FUTUREWORK: remove this. we are planning to remove the need for a nullKeyPackageRef
E.addMLSClients lcnv (qUntagged lusr) (Set.singleton (clientId, nullKeyPackageRef))
conversationCreated lusr conv

createOne2OneConversation ::
forall r.
Members
Expand Down
42 changes: 34 additions & 8 deletions services/galley/src/Galley/API/MLS/Message.hs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ type MLSMessageStaticErrors =
ErrorS 'MLSCommitMissingReferences,
ErrorS 'MLSSelfRemovalNotAllowed,
ErrorS 'MLSClientSenderUserMismatch,
ErrorS 'MLSGroupConversationMismatch
ErrorS 'MLSGroupConversationMismatch,
ErrorS 'MLSMissingSenderClient
]

type MLSBundleStaticErrors =
Expand All @@ -125,6 +126,7 @@ postMLSMessageFromLocalUserV1 ::
ErrorS 'MLSClientSenderUserMismatch,
ErrorS 'MLSCommitMissingReferences,
ErrorS 'MLSGroupConversationMismatch,
ErrorS 'MLSMissingSenderClient,
ErrorS 'MLSProposalNotFound,
ErrorS 'MLSSelfRemovalNotAllowed,
ErrorS 'MLSStaleMessage,
Expand Down Expand Up @@ -159,6 +161,7 @@ postMLSMessageFromLocalUser ::
ErrorS 'MLSClientSenderUserMismatch,
ErrorS 'MLSCommitMissingReferences,
ErrorS 'MLSGroupConversationMismatch,
ErrorS 'MLSMissingSenderClient,
ErrorS 'MLSProposalNotFound,
ErrorS 'MLSSelfRemovalNotAllowed,
ErrorS 'MLSStaleMessage,
Expand Down Expand Up @@ -366,6 +369,7 @@ postMLSMessage ::
ErrorS 'MLSClientSenderUserMismatch,
ErrorS 'MLSCommitMissingReferences,
ErrorS 'MLSGroupConversationMismatch,
ErrorS 'MLSMissingSenderClient,
ErrorS 'MLSProposalNotFound,
ErrorS 'MLSSelfRemovalNotAllowed,
ErrorS 'MLSStaleMessage,
Expand Down Expand Up @@ -441,7 +445,7 @@ getSenderIdentity qusr mc fmt msg = do
-- one contained in the message. We throw an error if the two don't match.
when (((==) <$> mc <*> mSender) == Just False) $
throwS @'MLSClientSenderUserMismatch
pure (mkClientIdentity qusr <$> mSender)
pure (mkClientIdentity qusr <$> (mc <|> mSender))

postMLSMessageToLocalConv ::
( HasProposalEffects r,
Expand All @@ -452,6 +456,7 @@ postMLSMessageToLocalConv ::
ErrorS 'MissingLegalholdConsent,
ErrorS 'MLSClientSenderUserMismatch,
ErrorS 'MLSCommitMissingReferences,
ErrorS 'MLSMissingSenderClient,
ErrorS 'MLSProposalNotFound,
ErrorS 'MLSSelfRemovalNotAllowed,
ErrorS 'MLSStaleMessage,
Expand Down Expand Up @@ -618,6 +623,7 @@ processCommit ::
Member (ErrorS 'ConvNotFound) r,
Member (ErrorS 'MLSClientSenderUserMismatch) r,
Member (ErrorS 'MLSCommitMissingReferences) r,
Member (ErrorS 'MLSMissingSenderClient) r,
Member (ErrorS 'MLSProposalNotFound) r,
Member (ErrorS 'MLSSelfRemovalNotAllowed) r,
Member (ErrorS 'MLSStaleMessage) r,
Expand Down Expand Up @@ -752,6 +758,7 @@ processCommitWithAction ::
Member (ErrorS 'ConvNotFound) r,
Member (ErrorS 'MLSClientSenderUserMismatch) r,
Member (ErrorS 'MLSCommitMissingReferences) r,
Member (ErrorS 'MLSMissingSenderClient) r,
Member (ErrorS 'MLSProposalNotFound) r,
Member (ErrorS 'MLSSelfRemovalNotAllowed) r,
Member (ErrorS 'MLSStaleMessage) r,
Expand Down Expand Up @@ -786,6 +793,7 @@ processInternalCommit ::
Member (ErrorS 'ConvNotFound) r,
Member (ErrorS 'MLSClientSenderUserMismatch) r,
Member (ErrorS 'MLSCommitMissingReferences) r,
Member (ErrorS 'MLSMissingSenderClient) r,
Member (ErrorS 'MLSProposalNotFound) r,
Member (ErrorS 'MLSSelfRemovalNotAllowed) r,
Member (ErrorS 'MLSStaleMessage) r,
Expand Down Expand Up @@ -813,10 +821,28 @@ processInternalCommit qusr senderClient con lconv cm epoch groupId action sender
postponedKeyPackageRefUpdate <-
if epoch == Epoch 0
then do
-- this is a newly created conversation, and it should contain exactly one
-- client (the creator)
case (self, cmAssocs cm) of
(Left lm, [(qu, (creatorClient, _))])
let cType = cnvmType . convMetadata . tUnqualified $ lconv
case (self, cType, cmAssocs cm) of
(Left _, SelfConv, []) -> do
creatorClient <- noteS @'MLSMissingSenderClient senderClient
creatorRef <-
maybe
(pure senderRef)
( note (mlsProtocolError "Could not compute key package ref")
. kpRef'
. upLeaf
)
$ cPath commit
addMLSClients
(convId <$> lconv)
qusr
(Set.singleton (creatorClient, creatorRef))
(Left _, SelfConv, _) ->
throw . InternalErrorWithDescription $
"Unexpected creator client set in a self-conversation"
-- this is a newly created conversation, and it should contain exactly one
-- client (the creator)
(Left lm, _, [(qu, (creatorClient, _))])
| qu == qUntagged (qualifyAs lconv (lmId lm)) -> do
-- use update path as sender reference and if not existing fall back to sender
senderRef' <-
Expand All @@ -830,9 +856,9 @@ processInternalCommit qusr senderClient con lconv cm epoch groupId action sender
-- register the creator client
updateKeyPackageMapping lconv qusr creatorClient Nothing senderRef'
-- remote clients cannot send the first commit
(Right _, _) -> throwS @'MLSStaleMessage
(Right _, _, _) -> throwS @'MLSStaleMessage
-- uninitialised conversations should contain exactly one client
(_, _) ->
(_, _, _) ->
throw (InternalErrorWithDescription "Unexpected creator client set")
pure $ pure () -- no key package ref update necessary
else case upLeaf <$> cPath commit of
Expand Down
2 changes: 1 addition & 1 deletion services/galley/src/Galley/API/Public/Conversation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ conversationAPI =
<@> mkNamedAPI @"get-conversation-by-reusable-code" (getConversationByReusableCode @Cassandra)
<@> mkNamedAPI @"create-group-conversation" createGroupConversation
<@> mkNamedAPI @"create-self-conversation" createProteusSelfConversation
<@> mkNamedAPI @"create-mls-self-conversation" createMLSSelfConversation
<@> mkNamedAPI @"get-mls-self-conversation" getMLSSelfConversation
<@> mkNamedAPI @"create-one-to-one-conversation" createOne2OneConversation
<@> mkNamedAPI @"add-members-to-conversation-unqualified" addMembersUnqualified
<@> mkNamedAPI @"add-members-to-conversation-unqualified2" addMembersUnqualifiedV2
Expand Down
37 changes: 37 additions & 0 deletions services/galley/src/Galley/API/Query.hs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ module Galley.API.Query
ensureGuestLinksEnabled,
getConversationGuestLinksStatus,
ensureConvAdmin,
getMLSSelfConversation,
)
where

Expand All @@ -66,6 +67,8 @@ import Data.Qualified
import Data.Range
import qualified Data.Set as Set
import Galley.API.Error
import Galley.API.MLS.Keys
import Galley.API.Mapping
import qualified Galley.API.Mapping as Mapping
import Galley.API.Util
import qualified Galley.Data.Conversation as Data
Expand All @@ -77,6 +80,7 @@ import qualified Galley.Effects.ListItems as E
import qualified Galley.Effects.MemberStore as E
import Galley.Effects.TeamFeatureStore (FeaturePersistentConstraint)
import qualified Galley.Effects.TeamFeatureStore as TeamFeatures
import Galley.Env
import Galley.Options
import Galley.Types.Conversations.Members
import Galley.Types.Teams
Expand Down Expand Up @@ -605,6 +609,39 @@ getConversationGuestLinksFeatureStatus mbTid = do
mbLockStatus <- TeamFeatures.getFeatureLockStatus @db (Proxy @GuestLinksConfig) tid
pure $ computeFeatureConfigForTeamUser mbConfigNoLock mbLockStatus defaultStatus

-- | Get an MLS self conversation. In case it does not exist, it is partially
-- created in the database. The part that is not written is the epoch number;
-- the number is inserted only upon the first commit. With this we avoid race
-- conditions where two clients concurrently try to create or update the self
-- conversation, where the only thing that can be updated is bumping the epoch
-- number.
getMLSSelfConversation ::
forall r.
Members
'[ ConversationStore,
Error InternalError,
P.TinyLog,
Input Env
]
r =>
Local UserId ->
Sem r Conversation
getMLSSelfConversation lusr = do
let selfConvId = mlsSelfConvId usr
mconv <- E.getConversation selfConvId
cnv <- maybe create pure mconv
conversationView lusr cnv
where
usr = tUnqualified lusr
create :: Sem r Data.Conversation
create = do
unlessM (isJust <$> getMLSRemovalKey) $
throw (InternalErrorWithDescription noKeyMsg)
E.createMLSSelfConversation lusr
noKeyMsg =
"No backend removal key is configured (See 'mlsPrivateKeyPaths'"
<> "in galley's config). Refusing to create MLS conversation."

-------------------------------------------------------------------------------
-- Helpers

Expand Down
Loading