diff --git a/README.md b/README.md index 9ac41294e7..8c8c7fcdb7 100644 --- a/README.md +++ b/README.md @@ -95,4 +95,4 @@ You have two options: * Option 1. (recommended) Install wire-server on kubernetes using the configuration and instructions provided in [wire-server-deploy](https://github.com/wireapp/wire-server-deploy). This is the best option to run it on a server and recommended if you want to self-host wire-server. -* Option 2. Compile everything in this repo, then you can use the `services/start-services-only.sh`. This option is intended as a way to try out wire-server on your local development machine and not suited for production. +* Option 2. Compile everything in this repo, then you can use the `services/run-services`. This option is intended as a way to try out wire-server on your local development machine and not suited for production. diff --git a/changelog.d/2-features/mixed-protocol b/changelog.d/2-features/mixed-protocol new file mode 100644 index 0000000000..507a8a7d58 --- /dev/null +++ b/changelog.d/2-features/mixed-protocol @@ -0,0 +1 @@ +Introduce a "mixed" conversation protocol type. A conversation of "mixed" protocol functions as a Proteus converation as well as a MLS conversations. It's intended to be used for migrating conversations from Proteus to MLS. diff --git a/charts/nginz/values.yaml b/charts/nginz/values.yaml index cf574b2de3..1834258a40 100644 --- a/charts/nginz/values.yaml +++ b/charts/nginz/values.yaml @@ -427,6 +427,9 @@ nginx_conf: - all max_body_size: 40m body_buffer_size: 256k + - path: /conversations/([^/]*)/([^/]*)/protocol + envs: + - all - path: /broadcast envs: - all diff --git a/docs/src/developer/developer/how-to.md b/docs/src/developer/developer/how-to.md index 71d12c0851..d202f519f4 100644 --- a/docs/src/developer/developer/how-to.md +++ b/docs/src/developer/developer/how-to.md @@ -16,7 +16,7 @@ Terminal 1: Terminal 2: * Compile all services: `make c` -* Run services including nginz: `./services/start-services-only.sh`. +* Run services including nginz: `./services/run-services`. Open your browser at: [http://localhost:8080/api/swagger-ui](http://localhost:8080/api/swagger-ui) for diff --git a/libs/api-client/src/Network/Wire/Client/API/Conversation.hs b/libs/api-client/src/Network/Wire/Client/API/Conversation.hs index 3dc4ace781..280d096dc5 100644 --- a/libs/api-client/src/Network/Wire/Client/API/Conversation.hs +++ b/libs/api-client/src/Network/Wire/Client/API/Conversation.hs @@ -141,6 +141,6 @@ createConv users name = sessionRequest req rsc readBody method POST . path "conversations" . acceptJson - . json (NewConv users [] (name >>= checked) mempty Nothing Nothing Nothing Nothing roleNameWireAdmin M.ProtocolProteusTag) + . json (NewConv users [] (name >>= checked) mempty Nothing Nothing Nothing Nothing roleNameWireAdmin M.ProtocolCreateProteusTag) $ empty rsc = status201 :| [] diff --git a/libs/wire-api/src/Wire/API/Conversation.hs b/libs/wire-api/src/Wire/API/Conversation.hs index e1d444b29e..cee39c64c8 100644 --- a/libs/wire-api/src/Wire/API/Conversation.hs +++ b/libs/wire-api/src/Wire/API/Conversation.hs @@ -62,6 +62,8 @@ module Wire.API.Conversation maybeRole, -- * create + ProtocolCreateTag (..), + protocolCreateToProtocolTag, NewConv (..), ConvTeamInfo (..), @@ -632,6 +634,26 @@ instance ToSchema ReceiptMode where -------------------------------------------------------------------------------- -- create +-- | This is distinct from 'ProtocolTag', which also include ProtocolMixedTag +data ProtocolCreateTag = ProtocolCreateProteusTag | ProtocolCreateMLSTag + deriving stock (Eq, Show, Enum, Bounded, Generic) + deriving (Arbitrary) via GenericUniform ProtocolCreateTag + +instance ToSchema ProtocolCreateTag where + schema = + enum @Text "ProtocolCreateTag" $ + mconcat + [ element "proteus" ProtocolCreateProteusTag, + element "mls" ProtocolCreateMLSTag + ] + +protocolCreateToProtocolTag :: ProtocolCreateTag -> ProtocolTag +protocolCreateToProtocolTag ProtocolCreateProteusTag = ProtocolProteusTag +protocolCreateToProtocolTag ProtocolCreateMLSTag = ProtocolMLSTag + +protocolCreateTagSchema :: ObjectSchema SwaggerDoc ProtocolCreateTag +protocolCreateTagSchema = fmap (fromMaybe ProtocolCreateProteusTag) (optField "protocol" schema) + data NewConv = NewConv { newConvUsers :: [UserId], -- | A list of qualified users, which can include some local qualified users @@ -646,7 +668,7 @@ data NewConv = NewConv -- | Every member except for the creator will have this role newConvUsersRole :: RoleName, -- | The protocol of the conversation. It can be Proteus or MLS (1.0). - newConvProtocol :: ProtocolTag + newConvProtocol :: ProtocolCreateTag } deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform NewConv) @@ -705,7 +727,7 @@ newConvSchema sch = .= ( fieldWithDocModifier "conversation_role" (description ?~ usersRoleDesc) schema <|> pure roleNameWireAdmin ) - <*> newConvProtocol .= protocolTagSchema + <*> newConvProtocol .= protocolCreateTagSchema where usersDesc = "List of user IDs (excluding the requestor) to be \ diff --git a/libs/wire-api/src/Wire/API/Conversation/Protocol.hs b/libs/wire-api/src/Wire/API/Conversation/Protocol.hs index f72e45ea67..4ee31fc2cd 100644 --- a/libs/wire-api/src/Wire/API/Conversation/Protocol.hs +++ b/libs/wire-api/src/Wire/API/Conversation/Protocol.hs @@ -26,9 +26,11 @@ module Wire.API.Conversation.Protocol Epoch (..), Protocol (..), _ProtocolMLS, + _ProtocolMixed, _ProtocolProteus, protocolSchema, ConversationMLSData (..), + ProtocolUpdate (..), ) where @@ -36,6 +38,7 @@ import Control.Arrow import Control.Lens (makePrisms, (?~)) import Data.Aeson (FromJSON (..), ToJSON (..)) import Data.Schema +import qualified Data.Swagger as S import Data.Time.Clock import Imports import Wire.API.Conversation.Action.Tag @@ -45,7 +48,7 @@ import Wire.API.MLS.Group import Wire.API.MLS.SubConversation import Wire.Arbitrary -data ProtocolTag = ProtocolProteusTag | ProtocolMLSTag +data ProtocolTag = ProtocolProteusTag | ProtocolMLSTag | ProtocolMixedTag deriving stock (Eq, Show, Enum, Bounded, Generic) deriving (Arbitrary) via GenericUniform ProtocolTag @@ -94,6 +97,7 @@ instance ToSchema ConversationMLSData where data Protocol = ProtocolProteus | ProtocolMLS ConversationMLSData + | ProtocolMixed ConversationMLSData deriving (Eq, Show, Generic) deriving (Arbitrary) via GenericUniform Protocol @@ -102,6 +106,7 @@ $(makePrisms ''Protocol) protocolTag :: Protocol -> ProtocolTag protocolTag ProtocolProteus = ProtocolProteusTag protocolTag (ProtocolMLS _) = ProtocolMLSTag +protocolTag (ProtocolMixed _) = ProtocolMixedTag -- | Certain actions need to be performed at the level of the underlying -- protocol (MLS, mostly) before being applied to conversations. This function @@ -109,6 +114,7 @@ protocolTag (ProtocolMLS _) = ProtocolMLSTag -- with the given protocol. protocolValidAction :: Protocol -> ConversationActionTag -> Bool protocolValidAction ProtocolProteus _ = True +protocolValidAction (ProtocolMixed _) _ = True protocolValidAction (ProtocolMLS _) ConversationJoinTag = False protocolValidAction (ProtocolMLS _) ConversationLeaveTag = True protocolValidAction (ProtocolMLS _) ConversationRemoveMembersTag = False @@ -120,9 +126,14 @@ instance ToSchema ProtocolTag where enum @Text "Protocol" $ mconcat [ element "proteus" ProtocolProteusTag, - element "mls" ProtocolMLSTag + element "mls" ProtocolMLSTag, + element "mixed" ProtocolMixedTag ] +deriving via (Schema ProtocolTag) instance FromJSON ProtocolTag + +deriving via (Schema ProtocolTag) instance ToJSON ProtocolTag + protocolTagSchema :: ObjectSchema SwaggerDoc ProtocolTag protocolTagSchema = fmap (fromMaybe ProtocolProteusTag) (optField "protocol" schema) @@ -144,3 +155,15 @@ deriving via (Schema Protocol) instance ToJSON Protocol protocolDataSchema :: ProtocolTag -> ObjectSchema SwaggerDoc Protocol protocolDataSchema ProtocolProteusTag = tag _ProtocolProteus (pure ()) protocolDataSchema ProtocolMLSTag = tag _ProtocolMLS mlsDataSchema +protocolDataSchema ProtocolMixedTag = tag _ProtocolMixed mlsDataSchema + +newtype ProtocolUpdate = ProtocolUpdate {unProtocolUpdate :: ProtocolTag} + +instance ToSchema ProtocolUpdate where + schema = object "ProtocolUpdate" (ProtocolUpdate <$> unProtocolUpdate .= protocolTagSchema) + +deriving via (Schema ProtocolUpdate) instance FromJSON ProtocolUpdate + +deriving via (Schema ProtocolUpdate) instance ToJSON ProtocolUpdate + +deriving via (Schema ProtocolUpdate) instance S.ToSchema ProtocolUpdate diff --git a/libs/wire-api/src/Wire/API/Error/Galley.hs b/libs/wire-api/src/Wire/API/Error/Galley.hs index 8bc3062b7a..b06efe0300 100644 --- a/libs/wire-api/src/Wire/API/Error/Galley.hs +++ b/libs/wire-api/src/Wire/API/Error/Galley.hs @@ -67,6 +67,7 @@ data GalleyError | InvalidTarget | ConvNotFound | ConvAccessDenied + | ConvInvalidProtocolTransition | -- MLS Errors MLSNotEnabled | MLSNonEmptyMemberList @@ -185,6 +186,8 @@ type instance MapError 'ConvNotFound = 'StaticError 404 "no-conversation" "Conve type instance MapError 'ConvAccessDenied = 'StaticError 403 "access-denied" "Conversation access denied" +type instance MapError 'ConvInvalidProtocolTransition = 'StaticError 403 "invalid-protocol-transition" "Protocol transition is invalid" + type instance MapError 'InvalidTeamNotificationId = 'StaticError 400 "invalid-notification-id" "Could not parse notification id (must be UUIDv1)." type instance diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs b/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs index ae8781d927..7519255d91 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs @@ -27,6 +27,7 @@ import Servant hiding (WithStatus) import Servant.Swagger.Internal.Orphans () import Wire.API.Conversation import Wire.API.Conversation.Code +import Wire.API.Conversation.Protocol import Wire.API.Conversation.Role import Wire.API.Conversation.Typing import Wire.API.Error @@ -1252,3 +1253,23 @@ type ConversationAPI = '[RespondEmpty 200 "Update successful"] () ) + :<|> Named + "update-conversation-protocol" + ( Summary "Update the protocol of the conversation" + :> Description "**Note**: Only proteus->mixed upgrade is supported." + :> CanThrow 'ConvNotFound + :> CanThrow 'ConvInvalidProtocolTransition + :> CanThrow 'ConvMemberNotFound + :> ZLocalUser + :> ZClient + :> ZConn + :> "conversations" + :> QualifiedCapture' '[Description "Conversation ID"] "cnv" ConvId + :> "protocol" + :> ReqBody '[JSON] ProtocolUpdate + :> MultiVerb + 'PUT + '[JSON] + '[RespondEmpty 200 "Update successful"] + () + ) diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewConv_user.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewConv_user.hs index cbd89a8260..e4a4deca6a 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewConv_user.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewConv_user.hs @@ -26,7 +26,6 @@ import qualified Data.Set as Set (fromList) import qualified Data.UUID as UUID (fromString) import Imports import Wire.API.Conversation -import Wire.API.Conversation.Protocol import Wire.API.Conversation.Role testDomain :: Domain @@ -52,7 +51,7 @@ testObject_NewConv_user_1 = newConvMessageTimer = Just (Ms {ms = 3320987366258987}), newConvReceiptMode = Just (ReceiptMode {unReceiptMode = 1}), newConvUsersRole = fromJust (parseRoleName "8tp2gs7b6"), - newConvProtocol = ProtocolProteusTag + newConvProtocol = ProtocolCreateProteusTag } testObject_NewConv_user_3 :: NewConv @@ -71,5 +70,5 @@ testObject_NewConv_user_3 = ( parseRoleName "y3otpiwu615lvvccxsq0315jj75jquw01flhtuf49t6mzfurvwe3_sh51f4s257e2x47zo85rif_xyiyfldpan3g4r6zr35rbwnzm0k" ), - newConvProtocol = ProtocolMLSTag + newConvProtocol = ProtocolCreateMLSTag } diff --git a/services/brig/test/integration/API/OAuth.hs b/services/brig/test/integration/API/OAuth.hs index 3efdd15fa4..dd3276ca21 100644 --- a/services/brig/test/integration/API/OAuth.hs +++ b/services/brig/test/integration/API/OAuth.hs @@ -53,10 +53,9 @@ import Text.RawString.QQ import URI.ByteString import Util import Web.FormUrlEncoded -import Wire.API.Conversation (Access (..), Conversation (cnvQualifiedId)) +import Wire.API.Conversation (Access (..), Conversation (cnvQualifiedId), ProtocolCreateTag (..)) import qualified Wire.API.Conversation as Conv import Wire.API.Conversation.Code (CreateConversationCodeRequest (CreateConversationCodeRequest)) -import Wire.API.Conversation.Protocol (ProtocolTag (ProtocolProteusTag)) import qualified Wire.API.Conversation.Role as Role import Wire.API.OAuth import Wire.API.Routes.Bearer (Bearer (Bearer, unBearer)) @@ -703,7 +702,7 @@ createTeamConv :: Http ResponseLBS createTeamConv svc mkHeader token tid name = do let tinfo = Conv.ConvTeamInfo tid - let conv = Conv.NewConv [] [] (checked name) (Set.fromList [CodeAccess]) Nothing (Just tinfo) Nothing Nothing Role.roleNameWireAdmin ProtocolProteusTag + let conv = Conv.NewConv [] [] (checked name) (Set.fromList [CodeAccess]) Nothing (Just tinfo) Nothing Nothing Role.roleNameWireAdmin ProtocolCreateProteusTag post $ svc . path "conversations" diff --git a/services/brig/test/integration/API/Provider.hs b/services/brig/test/integration/API/Provider.hs index bf5b7db8fd..0674f61d96 100644 --- a/services/brig/test/integration/API/Provider.hs +++ b/services/brig/test/integration/API/Provider.hs @@ -84,7 +84,6 @@ import Wire.API.Asset hiding (Asset) import Wire.API.Connection import Wire.API.Conversation import Wire.API.Conversation.Bot -import Wire.API.Conversation.Protocol import Wire.API.Conversation.Role import Wire.API.Event.Conversation import Wire.API.Internal.Notification @@ -1415,7 +1414,7 @@ createConvWithAccessRoles ars g u us = . contentJson . body (RequestBodyLBS (encode conv)) where - conv = NewConv us [] Nothing Set.empty ars Nothing Nothing Nothing roleNameWireAdmin ProtocolProteusTag + conv = NewConv us [] Nothing Set.empty ars Nothing Nothing Nothing roleNameWireAdmin ProtocolCreateProteusTag postMessage :: Galley -> diff --git a/services/brig/test/integration/API/Team/Util.hs b/services/brig/test/integration/API/Team/Util.hs index ff2f9c7ab4..4463394196 100644 --- a/services/brig/test/integration/API/Team/Util.hs +++ b/services/brig/test/integration/API/Team/Util.hs @@ -40,7 +40,6 @@ import Test.Tasty.HUnit import Util import Web.Cookie (parseSetCookie, setCookieName) import Wire.API.Conversation -import Wire.API.Conversation.Protocol import Wire.API.Conversation.Role import qualified Wire.API.Routes.Internal.Galley.TeamsIntra as Team import Wire.API.Team hiding (newTeam) @@ -235,7 +234,7 @@ createTeamConvWithRole role g tid u us mtimer = do mtimer Nothing role - ProtocolProteusTag + ProtocolCreateProteusTag r <- post ( g diff --git a/services/brig/test/integration/Federation/End2end.hs b/services/brig/test/integration/Federation/End2end.hs index 9a0d0a5c4f..353516b10e 100644 --- a/services/brig/test/integration/Federation/End2end.hs +++ b/services/brig/test/integration/Federation/End2end.hs @@ -281,7 +281,7 @@ testAddRemoteUsersToLocalConv brig1 galley1 brig2 galley2 = do Nothing Nothing roleNameWireAdmin - ProtocolProteusTag + ProtocolCreateProteusTag convId <- fmap cnvQualifiedId . responseJsonError =<< post @@ -803,6 +803,7 @@ testSendMLSMessage brig1 brig2 galley1 galley2 cannon1 cannon2 = do groupId <- case cnvProtocol conv of ProtocolMLS p -> pure (unGroupId (cnvmlsGroupId p)) ProtocolProteus -> liftIO $ assertFailure "Expected MLS conversation" + ProtocolMixed _ -> liftIO $ assertFailure "Expected MLS conversation" let qconvId = cnvQualifiedId conv groupJSON <- liftIO $ @@ -1066,6 +1067,7 @@ testSendMLSMessageToSubConversation brig1 brig2 galley1 galley2 cannon1 cannon2 groupId <- case cnvProtocol conv of ProtocolMLS p -> pure (unGroupId (cnvmlsGroupId p)) ProtocolProteus -> liftIO $ assertFailure "Expected MLS conversation" + ProtocolMixed _ -> liftIO $ assertFailure "Expected MLS conversation" let qconvId = cnvQualifiedId conv groupJSON <- liftIO $ diff --git a/services/brig/test/integration/Util.hs b/services/brig/test/integration/Util.hs index 8b9487238c..88d5bfcc71 100644 --- a/services/brig/test/integration/Util.hs +++ b/services/brig/test/integration/Util.hs @@ -107,7 +107,6 @@ import Util.Options import Web.Internal.HttpApiData import Wire.API.Connection import Wire.API.Conversation -import Wire.API.Conversation.Protocol import Wire.API.Conversation.Role (roleNameWireAdmin) import Wire.API.Federation.API import Wire.API.Federation.Domain @@ -735,7 +734,7 @@ createMLSConversation galley zusr c = do Nothing Nothing roleNameWireAdmin - ProtocolMLSTag + ProtocolCreateMLSTag post $ galley . path "/conversations" @@ -776,7 +775,7 @@ createConversation galley zusr usersToAdd = do Nothing Nothing roleNameWireAdmin - ProtocolProteusTag + ProtocolCreateProteusTag post $ galley . path "/conversations" diff --git a/services/galley/src/Galley/API/Create.hs b/services/galley/src/Galley/API/Create.hs index 7ddb0b3fbc..58ed1273dd 100644 --- a/services/galley/src/Galley/API/Create.hs +++ b/services/galley/src/Galley/API/Create.hs @@ -203,12 +203,12 @@ createGroupConversationGeneric lusr mCreatorClient conn newConv convCreated = do ensureNoLegalholdConflicts allUsers case newConvProtocol newConv of - ProtocolMLSTag -> do + ProtocolCreateMLSTag -> do -- Here we fail early in order to notify users of this misconfiguration assertMLSEnabled unlessM (isJust <$> getMLSRemovalKey) $ throw (InternalErrorWithDescription "No backend removal key is configured (See 'mlsPrivateKeyPaths' in galley's config). Refusing to create MLS conversation.") - ProtocolProteusTag -> pure () + ProtocolCreateProteusTag -> pure () lcnv <- traverse (const E.createConversationId) lusr -- FUTUREWORK: Invoke the creating a conversation action only once @@ -224,6 +224,7 @@ createGroupConversationGeneric lusr mCreatorClient conn newConv convCreated = do (ProtocolMLS mlsMeta, Just c) -> E.addMLSClients (cnvmlsGroupId mlsMeta) (tUntagged lusr) (Set.singleton (c, nullKeyPackageRef)) (ProtocolMLS _mlsMeta, Nothing) -> throwS @'MLSMissingSenderClient + (ProtocolMixed _mlsMeta, _) -> pure () -- NOTE: We only send (conversation) events to members of the conversation failedToNotify <- notifyCreatedConversation lusr conn conv @@ -312,7 +313,7 @@ createProteusSelfConversation lusr = do NewConversation { ncMetadata = (defConversationMetadata (tUnqualified lusr)) {cnvmType = SelfConv}, ncUsers = ulFromLocals [toUserRole (tUnqualified lusr)], - ncProtocol = ProtocolProteusTag + ncProtocol = ProtocolCreateProteusTag } c <- E.createConversation lcnv nc conversationCreated lusr c @@ -407,7 +408,7 @@ createLegacyOne2OneConversationUnchecked self zcon name mtid other = do let nc = NewConversation { ncUsers = ulFromLocals (map (toUserRole . tUnqualified) [self, other]), - ncProtocol = ProtocolProteusTag, + ncProtocol = ProtocolCreateProteusTag, ncMetadata = meta } mc <- E.getConversation (tUnqualified lcnv) @@ -472,7 +473,7 @@ createOne2OneConversationLocally lcnv self zcon name mtid other = do NewConversation { ncMetadata = meta, ncUsers = fmap toUserRole (toUserList lcnv [tUntagged self, other]), - ncProtocol = ProtocolProteusTag + ncProtocol = ProtocolCreateProteusTag } c <- E.createConversation lcnv nc void $ notifyCreatedConversation self (Just zcon) c @@ -521,7 +522,7 @@ createConnectConversation lusr conn j = do { -- We add only one member, second one gets added later, -- when the other user accepts the connection request. ncUsers = ulFromLocals (map (toUserRole . tUnqualified) [lusr]), - ncProtocol = ProtocolProteusTag, + ncProtocol = ProtocolCreateProteusTag, ncMetadata = meta } E.getConversation (tUnqualified lcnv) @@ -595,8 +596,8 @@ newRegularConversation lusr newConv = do o <- input let uncheckedUsers = newConvMembers lusr newConv users <- case newConvProtocol newConv of - ProtocolProteusTag -> checkedConvSize o uncheckedUsers - ProtocolMLSTag -> do + ProtocolCreateProteusTag -> checkedConvSize o uncheckedUsers + ProtocolCreateMLSTag -> do unless (null uncheckedUsers) $ throwS @'MLSNonEmptyMemberList pure mempty let nc = diff --git a/services/galley/src/Galley/API/Message.hs b/services/galley/src/Galley/API/Message.hs index 585c721cb9..511098b563 100644 --- a/services/galley/src/Galley/API/Message.hs +++ b/services/galley/src/Galley/API/Message.hs @@ -393,7 +393,7 @@ postQualifiedOtrMessage senderType sender mconn lcnv msg = let senderClient = qualifiedNewOtrSender msg conv <- getConversation (tUnqualified lcnv) >>= noteS @'ConvNotFound - unless (protocolTag (convProtocol conv) == ProtocolProteusTag) $ + unless (protocolTag (convProtocol conv) `elem` [ProtocolProteusTag, ProtocolMixedTag]) $ throwS @'InvalidOperation let localMemberIds = lmId <$> convLocalMembers conv diff --git a/services/galley/src/Galley/API/One2One.hs b/services/galley/src/Galley/API/One2One.hs index c2a98414ad..8bd2b4d9d1 100644 --- a/services/galley/src/Galley/API/One2One.hs +++ b/services/galley/src/Galley/API/One2One.hs @@ -35,7 +35,6 @@ import Galley.Types.UserList import Imports import Polysemy import Wire.API.Conversation hiding (Member) -import Wire.API.Conversation.Protocol import Wire.API.Routes.Internal.Galley.ConversationsIntra (Actor (..), DesiredMembership (..), UpsertOne2OneConversationRequest (..), UpsertOne2OneConversationResponse (..)) newConnectConversationWithRemote :: @@ -49,7 +48,7 @@ newConnectConversationWithRemote creator users = { cnvmType = One2OneConv }, ncUsers = fmap toUserRole users, - ncProtocol = ProtocolProteusTag + ncProtocol = ProtocolCreateProteusTag } iUpsertOne2OneConversation :: diff --git a/services/galley/src/Galley/API/Public/Conversation.hs b/services/galley/src/Galley/API/Public/Conversation.hs index 0ef1c8b66a..3efe3445ca 100644 --- a/services/galley/src/Galley/API/Public/Conversation.hs +++ b/services/galley/src/Galley/API/Public/Conversation.hs @@ -88,3 +88,4 @@ conversationAPI = <@> mkNamedAPI @"get-conversation-self-unqualified" getLocalSelf <@> mkNamedAPI @"update-conversation-self-unqualified" updateUnqualifiedSelfMember <@> mkNamedAPI @"update-conversation-self" updateSelfMember + <@> mkNamedAPI @"update-conversation-protocol" updateConversationProtocolWithLocalUser diff --git a/services/galley/src/Galley/API/Update.hs b/services/galley/src/Galley/API/Update.hs index 6b8c2bc8ce..45c36abf16 100644 --- a/services/galley/src/Galley/API/Update.hs +++ b/services/galley/src/Galley/API/Update.hs @@ -1,4 +1,3 @@ -{-# LANGUAGE OverloadedRecordDot #-} -- This file is part of the Wire Server implementation. -- -- Copyright (C) 2022 Wire Swiss GmbH @@ -15,6 +14,7 @@ -- -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . +{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE RecordWildCards #-} module Galley.API.Update @@ -39,6 +39,7 @@ module Galley.API.Update updateConversationAccess, deleteLocalConversation, updateRemoteConversation, + updateConversationProtocolWithLocalUser, -- * Managing Members addMembersUnqualified, @@ -85,6 +86,7 @@ import Data.Time import Galley.API.Action import Galley.API.Error import Galley.API.Federation (onConversationUpdated) +import Galley.API.MLS.KeyPackage (nullKeyPackageRef) import Galley.API.Mapping import Galley.API.Message import qualified Galley.API.Query as Query @@ -123,6 +125,7 @@ import System.Logger (Msg) import Wire.API.Conversation hiding (Member) import Wire.API.Conversation.Action import Wire.API.Conversation.Code +import Wire.API.Conversation.Protocol (ProtocolTag (..), ProtocolUpdate (ProtocolUpdate), protocolTag) import Wire.API.Conversation.Role import Wire.API.Conversation.Typing import Wire.API.Error @@ -131,6 +134,8 @@ import Wire.API.Event.Conversation import Wire.API.Federation.API import Wire.API.Federation.API.Galley import Wire.API.Federation.Error +import Wire.API.MLS.CipherSuite +import Wire.API.MLS.Group import Wire.API.Message import Wire.API.Password (mkSafePassword) import Wire.API.Provider.Service (ServiceRef) @@ -679,6 +684,57 @@ checkReusableCode convCode = do mapErrorS @'GuestLinksDisabled @'CodeNotFound $ Query.ensureGuestLinksEnabled @db (Data.convTeam conv) +updateConversationProtocolWithLocalUser :: + forall r. + ( Member (ErrorS 'ConvNotFound) r, + Member (ErrorS 'ConvInvalidProtocolTransition) r, + Member (ErrorS 'ConvMemberNotFound) r, + Member (Error FederationError) r, + Member MemberStore r, + Member ConversationStore r + ) => + Local UserId -> + ClientId -> + ConnId -> + Qualified ConvId -> + ProtocolUpdate -> + Sem r () +updateConversationProtocolWithLocalUser lusr client conn qcnv update = + foldQualified + lusr + (\lcnv -> updateLocalConversationProtocol (tUntagged lusr) client (Just conn) lcnv update) + (\_rcnv -> throw FederationNotImplemented) + qcnv + +updateLocalConversationProtocol :: + forall r. + ( Member (ErrorS 'ConvNotFound) r, + Member (ErrorS 'ConvInvalidProtocolTransition) r, + Member (ErrorS 'ConvMemberNotFound) r, + Member MemberStore r, + Member ConversationStore r + ) => + Qualified UserId -> + ClientId -> + Maybe ConnId -> + Local ConvId -> + ProtocolUpdate -> + Sem r () +updateLocalConversationProtocol qusr client _mconn lcnv (ProtocolUpdate newProtocol) = do + conv <- E.getConversation (tUnqualified lcnv) >>= noteS @'ConvNotFound + void $ ensureOtherMember lcnv qusr conv + case (protocolTag (convProtocol conv), newProtocol) of + (ProtocolProteusTag, ProtocolMixedTag) -> do + E.updateToMixedProtocol lcnv MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 + E.addMLSClients (convToGroupId lcnv) qusr (Set.singleton (client, nullKeyPackageRef)) + (ProtocolProteusTag, ProtocolProteusTag) -> + pure () + (ProtocolMixedTag, ProtocolMixedTag) -> + pure () + (ProtocolMLSTag, ProtocolMLSTag) -> + pure () + (_, _) -> throwS @'ConvInvalidProtocolTransition + joinConversationByReusableCode :: forall db r. ( Member BrigAccess r, diff --git a/services/galley/src/Galley/Cassandra/Conversation.hs b/services/galley/src/Galley/Cassandra/Conversation.hs index 4d7c09bc06..520cd3ea9c 100644 --- a/services/galley/src/Galley/Cassandra/Conversation.hs +++ b/services/galley/src/Galley/Cassandra/Conversation.hs @@ -72,7 +72,7 @@ createMLSSelfConversation lusr = do { ncMetadata = (defConversationMetadata usr) {cnvmType = SelfConv}, ncUsers = ulFromLocals [toUserRole usr], - ncProtocol = ProtocolMLSTag + ncProtocol = ProtocolCreateMLSTag } meta = ncMetadata nc gid = convToGroupId . qualifyAs lusr $ cnv @@ -123,8 +123,8 @@ createConversation :: Local ConvId -> NewConversation -> Client Conversation createConversation lcnv nc = do let meta = ncMetadata nc (proto, mgid, mep, mcs) = case ncProtocol nc of - ProtocolProteusTag -> (ProtocolProteus, Nothing, Nothing, Nothing) - ProtocolMLSTag -> + ProtocolCreateProteusTag -> (ProtocolProteus, Nothing, Nothing, Nothing) + ProtocolCreateMLSTag -> let gid = convToGroupId lcnv ep = Epoch 0 cs = MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 @@ -158,7 +158,7 @@ createConversation lcnv nc = do cnvmTeam meta, cnvmMessageTimer meta, cnvmReceiptMode meta, - ncProtocol nc, + protocolCreateToProtocolTag (ncProtocol nc), mgid, mep, mcs @@ -337,15 +337,17 @@ toProtocol :: Maybe Protocol toProtocol Nothing _ _ _ _ = Just ProtocolProteus toProtocol (Just ProtocolProteusTag) _ _ _ _ = Just ProtocolProteus -toProtocol (Just ProtocolMLSTag) mgid mepoch mtimestamp mcs = - ProtocolMLS - <$> ( ConversationMLSData - <$> mgid - -- If there is no epoch in the database, assume the epoch is 0 - <*> (mepoch <|> Just (Epoch 0)) - <*> pure (mepoch `toTimestamp` mtimestamp) - <*> mcs - ) +toProtocol (Just ProtocolMLSTag) mgid mepoch mtimestamp mcs = ProtocolMLS <$> toConversationMLSData mgid mepoch mtimestamp mcs +toProtocol (Just ProtocolMixedTag) mgid mepoch mtimestamp mcs = ProtocolMixed <$> toConversationMLSData mgid mepoch mtimestamp mcs + +toConversationMLSData :: Maybe GroupId -> Maybe Epoch -> Maybe UTCTime -> Maybe CipherSuiteTag -> Maybe ConversationMLSData +toConversationMLSData mgid mepoch mtimestamp mcs = + ConversationMLSData + <$> mgid + -- If there is no epoch in the database, assume the epoch is 0 + <*> (mepoch <|> Just (Epoch 0)) + <*> pure (mepoch `toTimestamp` mtimestamp) + <*> mcs where toTimestamp :: Maybe Epoch -> Maybe UTCTime -> Maybe UTCTime toTimestamp Nothing _ = Nothing @@ -429,6 +431,23 @@ deleteGroupIds :: deleteGroupIds = embedClient . UnliftIO.pooledMapConcurrentlyN_ 8 deleteGroupIdForConversation +updateToMixedProtocol :: + Members + '[ Embed IO, + Input ClientState + ] + r => + Local ConvId -> + CipherSuiteTag -> + Sem r () +updateToMixedProtocol lcnv cs = + embedClient . retry x5 . batch $ do + setType BatchLogged + setConsistency LocalQuorum + let gid = convToGroupId lcnv + addPrepQuery Cql.insertGroupIdForConversation (gid, tUnqualified lcnv, tDomain lcnv) + addPrepQuery Cql.updateToMixedConv (tUnqualified lcnv, ProtocolMixedTag, gid, Epoch 0, cs) + interpretConversationStoreToCassandra :: ( Member (Embed IO) r, Member (Input ClientState) r, @@ -461,3 +480,4 @@ interpretConversationStoreToCassandra = interpret $ \case AcquireCommitLock gId epoch ttl -> embedClient $ acquireCommitLock gId epoch ttl ReleaseCommitLock gId epoch -> embedClient $ releaseCommitLock gId epoch DeleteGroupIds gIds -> deleteGroupIds gIds + UpdateToMixedProtocol cid cs -> updateToMixedProtocol cid cs diff --git a/services/galley/src/Galley/Cassandra/Queries.hs b/services/galley/src/Galley/Cassandra/Queries.hs index 35c8ae7c32..c838ddd00f 100644 --- a/services/galley/src/Galley/Cassandra/Queries.hs +++ b/services/galley/src/Galley/Cassandra/Queries.hs @@ -256,6 +256,11 @@ insertMLSSelfConv = <> show (fromEnum ProtocolMLSTag) <> ", ?, ?)" +updateToMixedConv :: PrepQuery W (ConvId, ProtocolTag, GroupId, Epoch, CipherSuiteTag) () +updateToMixedConv = + fromString $ + "insert into conversation (conv, protocol, group_id, epoch, cipher_suite) values (?, ?, ?, ?, ?)" + updateConvAccess :: PrepQuery W (C.Set Access, C.Set AccessRole, ConvId) () updateConvAccess = "update conversation set access = ?, access_roles_v2 = ? where conv = ?" diff --git a/services/galley/src/Galley/Data/Conversation/Types.hs b/services/galley/src/Galley/Data/Conversation/Types.hs index a8ad662a21..edff99fa5c 100644 --- a/services/galley/src/Galley/Data/Conversation/Types.hs +++ b/services/galley/src/Galley/Data/Conversation/Types.hs @@ -41,7 +41,7 @@ data Conversation = Conversation data NewConversation = NewConversation { ncMetadata :: ConversationMetadata, ncUsers :: UserList (UserId, RoleName), - ncProtocol :: ProtocolTag + ncProtocol :: ProtocolCreateTag } mlsMetadata :: Conversation -> Maybe ConversationMLSData @@ -49,3 +49,4 @@ mlsMetadata conv = case convProtocol conv of ProtocolProteus -> Nothing ProtocolMLS meta -> pure meta + ProtocolMixed meta -> pure meta diff --git a/services/galley/src/Galley/Effects/ConversationStore.hs b/services/galley/src/Galley/Effects/ConversationStore.hs index 0e0763e6af..5d9fa1d51c 100644 --- a/services/galley/src/Galley/Effects/ConversationStore.hs +++ b/services/galley/src/Galley/Effects/ConversationStore.hs @@ -48,6 +48,7 @@ module Galley.Effects.ConversationStore deleteGroupIdForConversation, setPublicGroupState, deleteGroupIds, + updateToMixedProtocol, -- * Delete conversation deleteConversation, @@ -69,6 +70,7 @@ import Galley.Types.Conversations.Members import Imports import Polysemy import Wire.API.Conversation hiding (Conversation, Member) +import Wire.API.MLS.CipherSuite (CipherSuiteTag) import Wire.API.MLS.Epoch import Wire.API.MLS.PublicGroupState import Wire.API.MLS.SubConversation @@ -108,6 +110,7 @@ data ConversationStore m a where AcquireCommitLock :: GroupId -> Epoch -> NominalDiffTime -> ConversationStore m LockAcquired ReleaseCommitLock :: GroupId -> Epoch -> ConversationStore m () DeleteGroupIds :: [GroupId] -> ConversationStore m () + UpdateToMixedProtocol :: Local ConvId -> CipherSuiteTag -> ConversationStore m () makeSem ''ConversationStore diff --git a/services/galley/test/integration/API.hs b/services/galley/test/integration/API.hs index 6c54caee22..fd179818c6 100644 --- a/services/galley/test/integration/API.hs +++ b/services/galley/test/integration/API.hs @@ -2396,7 +2396,7 @@ postConvQualifiedFederationNotEnabled = do -- FUTUREWORK: figure out how to use functions in the TestM monad inside withSettingsOverrides and remove this duplication postConvHelper :: MonadHttp m => (Request -> Request) -> UserId -> [Qualified UserId] -> m ResponseLBS postConvHelper g zusr newUsers = do - let conv = NewConv [] newUsers (checked "gossip") (Set.fromList []) Nothing Nothing Nothing Nothing roleNameWireAdmin ProtocolProteusTag + let conv = NewConv [] newUsers (checked "gossip") (Set.fromList []) Nothing Nothing Nothing Nothing roleNameWireAdmin ProtocolCreateProteusTag post $ g . path "/conversations" . zUser zusr . zConn "conn" . zType "access" . json conv postSelfConvOk :: TestM () @@ -2424,7 +2424,7 @@ postConvO2OFailWithSelf :: TestM () postConvO2OFailWithSelf = do g <- viewGalley alice <- randomUser - let inv = NewConv [alice] [] Nothing mempty Nothing Nothing Nothing Nothing roleNameWireAdmin ProtocolProteusTag + let inv = NewConv [alice] [] Nothing mempty Nothing Nothing Nothing Nothing roleNameWireAdmin ProtocolCreateProteusTag post (g . path "/conversations/one2one" . zUser alice . zConn "conn" . zType "access" . json inv) !!! do const 403 === statusCode const (Just "invalid-op") === fmap label . responseJsonUnsafe diff --git a/services/galley/test/integration/API/MLS.hs b/services/galley/test/integration/API/MLS.hs index acf54e5279..b6c6bd1023 100644 --- a/services/galley/test/integration/API/MLS.hs +++ b/services/galley/test/integration/API/MLS.hs @@ -259,6 +259,11 @@ tests s = test s "delete subconversation as a remote non-member" (testRemoteMemberDeleteSubConv False), test s "delete parent conversation of a remote subconveration" testDeleteRemoteParentOfSubConv ] + ], + testGroup + "MixedProtocol" + [ test s "Upgrade a conv from proteus to mixed" testMixedUpgrade, + test s "Add clients to a mixed conversation and send proteus message" testMixedAddClients ] ] @@ -3526,3 +3531,78 @@ testCreatorRemovesUserFromParent = do ) (sort [alice1, charlie1, charlie2]) (sort $ pscMembers sub2) + +testMixedUpgrade :: TestM () +testMixedUpgrade = do + [alice, bob] <- createAndConnectUsers (replicate 2 Nothing) + + runMLSTest $ do + [alice1] <- traverse createMLSClient [alice] + + qcnv <- + cnvQualifiedId + <$> liftTest + ( postConvQualified (qUnqualified alice) Nothing defNewProteusConv {newConvQualifiedUsers = [bob]} + >>= responseJsonError + ) + + putConversationProtocol (qUnqualified alice) (ciClient alice1) qcnv ProtocolMixedTag + !!! const 200 === statusCode + + conv <- + responseJsonError + =<< getConvQualified (qUnqualified alice) qcnv + liftTest + ( postConvQualified (qUnqualified alice) Nothing defNewProteusConv {newConvQualifiedUsers = [bob, charlie]} + >>= responseJsonError + ) + + -- bob upgrades to mixed + putConversationProtocol (qUnqualified bob) (ciClient bob1) qcnv ProtocolMixedTag + !!! const 200 === statusCode + + conv <- + responseJsonError + =<< getConvQualified (qUnqualified alice) qcnv + do + void $ sendAndConsumeCommitBundle commit + for_ (zip [alice1, charlie1] wss) $ \(c, ws) -> + WS.assertMatch (5 # Second) ws $ + wsAssertMLSWelcome (cidQualifiedUser c) welcome + + -- charlie sends a Proteus message + let msgs = + [ (qUnqualified alice, ciClient alice1, toBase64Text "ciphertext-to-alice"), + (qUnqualified bob, ciClient bob1, toBase64Text "ciphertext-to-bob") + ] + liftTest $ + postOtrMessage id (qUnqualified charlie) (ciClient charlie1) (qUnqualified qcnv) msgs !!! do + const 201 === statusCode + assertMismatch [] [] [] diff --git a/services/galley/test/integration/API/MLS/Util.hs b/services/galley/test/integration/API/MLS/Util.hs index 07d2e510e5..bb59cb8cdb 100644 --- a/services/galley/test/integration/API/MLS/Util.hs +++ b/services/galley/test/integration/API/MLS/Util.hs @@ -476,8 +476,11 @@ setupMLSGroupWithConv convAction creator = do conv <- convAction let groupId = fromJust - (preview (to cnvProtocol . _ProtocolMLS . to cnvmlsGroupId) conv) - + ( asum + [ preview (to cnvProtocol . _ProtocolMLS . to cnvmlsGroupId) conv, + preview (to cnvProtocol . _ProtocolMixed . to cnvmlsGroupId) conv + ] + ) let qcnv = cnvQualifiedId conv createGroup creator (fmap Conv qcnv) groupId pure (groupId, qcnv) diff --git a/services/galley/test/integration/API/Util.hs b/services/galley/test/integration/API/Util.hs index a0de4e36bd..0d35e8fafe 100644 --- a/services/galley/test/integration/API/Util.hs +++ b/services/galley/test/integration/API/Util.hs @@ -623,7 +623,7 @@ createTeamConvAccessRaw u tid us name acc role mtimer convRole = do g <- viewGalley let tinfo = ConvTeamInfo tid let conv = - NewConv us [] (name >>= checked) (fromMaybe (Set.fromList []) acc) role (Just tinfo) mtimer Nothing (fromMaybe roleNameWireAdmin convRole) ProtocolProteusTag + NewConv us [] (name >>= checked) (fromMaybe (Set.fromList []) acc) role (Just tinfo) mtimer Nothing (fromMaybe roleNameWireAdmin convRole) ProtocolCreateProteusTag post ( g . path "/conversations" @@ -659,7 +659,7 @@ createMLSTeamConv lusr c tid users name access role timer convRole = do newConvMessageTimer = timer, newConvUsersRole = fromMaybe roleNameWireAdmin convRole, newConvReceiptMode = Nothing, - newConvProtocol = ProtocolMLSTag + newConvProtocol = ProtocolCreateMLSTag } r <- post @@ -690,7 +690,7 @@ createOne2OneTeamConv :: UserId -> UserId -> Maybe Text -> TeamId -> TestM Respo createOne2OneTeamConv u1 u2 n tid = do g <- viewGalley let conv = - NewConv [u2] [] (n >>= checked) mempty Nothing (Just $ ConvTeamInfo tid) Nothing Nothing roleNameWireAdmin ProtocolProteusTag + NewConv [u2] [] (n >>= checked) mempty Nothing (Just $ ConvTeamInfo tid) Nothing Nothing roleNameWireAdmin ProtocolCreateProteusTag post $ g . path "/conversations/one2one" . zUser u1 . zConn "conn" . zType "access" . json conv postConv :: @@ -704,12 +704,12 @@ postConv :: postConv u us name a r mtimer = postConvWithRole u us name a r mtimer roleNameWireAdmin defNewProteusConv :: NewConv -defNewProteusConv = NewConv [] [] Nothing mempty Nothing Nothing Nothing Nothing roleNameWireAdmin ProtocolProteusTag +defNewProteusConv = NewConv [] [] Nothing mempty Nothing Nothing Nothing Nothing roleNameWireAdmin ProtocolCreateProteusTag defNewMLSConv :: NewConv defNewMLSConv = defNewProteusConv - { newConvProtocol = ProtocolMLSTag + { newConvProtocol = ProtocolCreateMLSTag } postConvQualified :: @@ -748,7 +748,7 @@ postConvWithRemoteUsers u c n = postTeamConv :: TeamId -> UserId -> [UserId] -> Maybe Text -> [Access] -> Maybe (Set AccessRole) -> Maybe Milliseconds -> TestM ResponseLBS postTeamConv tid u us name a r mtimer = do g <- viewGalley - let conv = NewConv us [] (name >>= checked) (Set.fromList a) r (Just (ConvTeamInfo tid)) mtimer Nothing roleNameWireAdmin ProtocolProteusTag + let conv = NewConv us [] (name >>= checked) (Set.fromList a) r (Just (ConvTeamInfo tid)) mtimer Nothing roleNameWireAdmin ProtocolCreateProteusTag post $ g . path "/conversations" . zUser u . zConn "conn" . zType "access" . json conv deleteTeamConv :: (HasGalley m, MonadIO m, MonadHttp m) => TeamId -> ConvId -> UserId -> m ResponseLBS @@ -786,7 +786,7 @@ postConvWithRole u members name access arole timer role = postConvWithReceipt :: UserId -> [UserId] -> Maybe Text -> [Access] -> Maybe (Set AccessRole) -> Maybe Milliseconds -> ReceiptMode -> TestM ResponseLBS postConvWithReceipt u us name a r mtimer rcpt = do g <- viewGalley - let conv = NewConv us [] (name >>= checked) (Set.fromList a) r Nothing mtimer (Just rcpt) roleNameWireAdmin ProtocolProteusTag + let conv = NewConv us [] (name >>= checked) (Set.fromList a) r Nothing mtimer (Just rcpt) roleNameWireAdmin ProtocolCreateProteusTag post $ g . path "/conversations" . zUser u . zConn "conn" . zType "access" . json conv postSelfConv :: UserId -> TestM ResponseLBS @@ -797,7 +797,7 @@ postSelfConv u = do postO2OConv :: UserId -> UserId -> Maybe Text -> TestM ResponseLBS postO2OConv u1 u2 n = do g <- viewGalley - let conv = NewConv [u2] [] (n >>= checked) mempty Nothing Nothing Nothing Nothing roleNameWireAdmin ProtocolProteusTag + let conv = NewConv [u2] [] (n >>= checked) mempty Nothing Nothing Nothing Nothing roleNameWireAdmin ProtocolCreateProteusTag post $ g . path "/conversations/one2one" . zUser u1 . zConn "conn" . zType "access" . json conv postConnectConv :: UserId -> UserId -> Text -> Text -> Maybe Text -> TestM ResponseLBS @@ -2980,3 +2980,21 @@ createAndConnectUsers domains = do (False, True) -> connectWithRemoteUser (qUnqualified b) a (False, False) -> pure () pure users + +putConversationProtocol :: (MonadIO m, MonadHttp m, HasGalley m, HasCallStack) => UserId -> ClientId -> Qualified ConvId -> ProtocolTag -> m ResponseLBS +putConversationProtocol uid client (Qualified conv domain) protocol = do + galley <- viewGalley + put + ( galley + . paths ["conversations", toByteString' domain, toByteString' conv, "protocol"] + . zUser uid + . zConn "conn" + . zClient client + . Bilge.json (object ["protocol" .= protocol]) + ) + +assertMixedProtocol :: (MonadIO m, HasCallStack) => Conversation -> m ConversationMLSData +assertMixedProtocol conv = do + case cnvProtocol conv of + ProtocolMixed mlsData -> pure mlsData + _ -> liftIO $ assertFailure "Unexpected protocol" diff --git a/services/nginz/integration-test/conf/nginz/nginx.conf b/services/nginz/integration-test/conf/nginz/nginx.conf index a2449bbbd5..d4128443c4 100644 --- a/services/nginz/integration-test/conf/nginz/nginx.conf +++ b/services/nginz/integration-test/conf/nginz/nginx.conf @@ -360,6 +360,11 @@ http { proxy_pass http://galley; } + location ~* ^/conversations/([^/]*)/([^/]*)/protocol { + include common_response_with_zauth.conf; + proxy_pass http://galley; + } + location /broadcast { include common_response_with_zauth.conf; proxy_pass http://galley; diff --git a/services/spar/test-scim-suite/README.md b/services/spar/test-scim-suite/README.md index e7dfaaf85d..d2da32d467 100644 --- a/services/spar/test-scim-suite/README.md +++ b/services/spar/test-scim-suite/README.md @@ -4,7 +4,7 @@ The scripts in this directory allow to run the [SCIM Test Suite](https://github. How to run: ```sh -./services/start-services-only.sh +./services/run-services ./services/spar/test-scim-suite/runsuite.sh ``` diff --git a/tools/stern/README.md b/tools/stern/README.md index a5ad9a345b..884f486134 100644 --- a/tools/stern/README.md +++ b/tools/stern/README.md @@ -19,7 +19,7 @@ TODO: This section is under construction ## How to run stern locally -Start local services via `services/start-services-only.sh` +Start local services via `services/run-services` Open in a browser.