diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e9611cb462..593eca13dbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ +# [2022-01-28] + +## Release notes + +* Bump the webapp version. (#2082) + +## Internal changes + +* Additional integration testing for conversation access control. (#2057) + + # [2022-01-27] ## Release notes diff --git a/charts/webapp/values.yaml b/charts/webapp/values.yaml index dfc3b9b224c..b956b323de3 100644 --- a/charts/webapp/values.yaml +++ b/charts/webapp/values.yaml @@ -9,7 +9,7 @@ resources: cpu: "1" image: repository: quay.io/wire/webapp - tag: "2021-12-02-federation-M1-spillover" + tag: "2022-01-27-production.0-v0.28.29-0-42c9a1e" service: https: externalPort: 443 diff --git a/libs/wire-api/package.yaml b/libs/wire-api/package.yaml index 187e761ff4b..c616fb4d1c2 100644 --- a/libs/wire-api/package.yaml +++ b/libs/wire-api/package.yaml @@ -66,6 +66,7 @@ library: - proto-lens - QuickCheck >=2.14 - quickcheck-instances >=0.3.16 + - random >=1.2.0 - resourcet - servant-client - servant-client-core diff --git a/libs/wire-api/src/Wire/API/Conversation.hs b/libs/wire-api/src/Wire/API/Conversation.hs index 976fcaeb8a6..29cf0dab1dc 100644 --- a/libs/wire-api/src/Wire/API/Conversation.hs +++ b/libs/wire-api/src/Wire/API/Conversation.hs @@ -46,6 +46,7 @@ module Wire.API.Conversation -- * Conversation properties Access (..), AccessRoleV2 (..), + genAccessRolesV2, AccessRoleLegacy (..), ConvType (..), ReceiptMode (..), @@ -95,6 +96,7 @@ import Control.Lens (at, (?~)) import Data.Aeson (FromJSON (..), ToJSON (..)) import qualified Data.Aeson as A import Data.Id +import Data.List.Extra (disjointOrd) import Data.List.NonEmpty (NonEmpty) import Data.List1 import Data.Misc @@ -107,6 +109,7 @@ import Data.String.Conversions (cs) import qualified Data.Swagger as S import qualified Data.Swagger.Build.Api as Doc import Imports +import System.Random (randomRIO) import qualified Test.QuickCheck as QC import Wire.API.Arbitrary (Arbitrary (arbitrary), GenericUniform (..)) import Wire.API.Conversation.Member @@ -476,10 +479,23 @@ data AccessRoleV2 | NonTeamMemberAccessRole | GuestAccessRole | ServiceAccessRole - deriving stock (Eq, Ord, Show, Generic) + deriving stock (Eq, Ord, Show, Generic, Bounded, Enum) deriving (Arbitrary) via (GenericUniform AccessRoleV2) deriving (ToJSON, FromJSON, S.ToSchema) via Schema AccessRoleV2 +genAccessRolesV2 :: [AccessRoleV2] -> [AccessRoleV2] -> IO (Either String (Set AccessRoleV2)) +genAccessRolesV2 = genEnumSet + +genEnumSet :: forall a. (Bounded a, Enum a, Ord a, Eq a, Show a) => [a] -> [a] -> IO (Either String (Set a)) +genEnumSet with without = + if disjointOrd with without + then do + let xs = Set.toList . Set.powerSet . Set.fromList $ [minBound ..] + x <- (xs !!) <$> randomRIO (0, length xs - 1) + pure . Right . Set.fromList $ (Set.toList x <> with) \\ without + else do + pure $ Left ("overlapping arguments: " <> show (with, without)) + toAccessRoleLegacy :: Set AccessRoleV2 -> AccessRoleLegacy toAccessRoleLegacy accessRoles = do fromMaybe NonActivatedAccessRole $ find (allMember accessRoles . fromAccessRoleLegacy) [minBound ..] diff --git a/libs/wire-api/wire-api.cabal b/libs/wire-api/wire-api.cabal index 89857ca6b45..c26cfc4b431 100644 --- a/libs/wire-api/wire-api.cabal +++ b/libs/wire-api/wire-api.cabal @@ -189,6 +189,7 @@ library , proto-lens , protobuf >=0.2 , quickcheck-instances >=0.3.16 + , random >=1.2.0 , resourcet , saml2-web-sso , schema-profunctor diff --git a/services/brig/test/integration/API/Provider.hs b/services/brig/test/integration/API/Provider.hs index b97fc380499..5bbc50e43b7 100644 --- a/services/brig/test/integration/API/Provider.hs +++ b/services/brig/test/integration/API/Provider.hs @@ -133,7 +133,8 @@ tests dom conf p db b c g = do "bot" [ test p "add-remove" $ testAddRemoveBot conf db b g c, test p "message" $ testMessageBot conf db b g c, - test p "bad fingerprint" $ testBadFingerprint conf db b g c + test p "bad fingerprint" $ testBadFingerprint conf db b g c, + test p "add bot forbidden" $ testAddBotForbidden conf db b g ], testGroup "bot-teams" @@ -520,24 +521,41 @@ testDeleteService config db brig galley cannon = withTestService config db brig testAddRemoveBot :: Config -> DB.ClientState -> Brig -> Galley -> Cannon -> Http () testAddRemoveBot config db brig galley cannon = withTestService config db brig defServiceApp $ \sref buf -> do + (pid, sid, u1, u2, h) <- prepareUsers sref brig + let uid1 = userId u1 + quid1 = userQualifiedId u1 + localDomain = qDomain quid1 + uid2 = userId u2 + -- Create conversation + _rs <- createConv galley uid1 [uid2] DB.ClientState -> Brig -> Galley -> Http () +testAddBotForbidden config db brig galley = withTestService config db brig defServiceApp $ \sref _ -> do + (pid, sid, userId -> uid1, userId -> uid2, _) <- prepareUsers sref brig + -- Create conversation without the service access role + let accessRoles = Set.fromList [TeamMemberAccessRole, NonTeamMemberAccessRole, GuestAccessRole] + _rs <- createConvWithAccessRoles (Just accessRoles) galley uid1 [uid2] Brig -> Http (ProviderId, ServiceId, User, User, Text) +prepareUsers sref brig = do let pid = sref ^. serviceRefProvider let sid = sref ^. serviceRefId -- Prepare users u1 <- createUser "Ernie" brig u2 <- createUser "Bert" brig let uid1 = userId u1 - quid1 = userQualifiedId u1 - localDomain = qDomain quid1 uid2 = userId u2 h <- randomHandle putHandle brig uid1 h !!! const 200 === statusCode postConnection brig uid1 uid2 !!! const 201 === statusCode putConnection brig uid2 uid1 Accepted !!! const 200 === statusCode - -- Create conversation - _rs <- createConv galley uid1 [uid2] DB.ClientState -> Brig -> Galley -> Cannon -> Http () testMessageBot config db brig galley cannon = withTestService config db brig defServiceApp $ \sref buf -> do @@ -1257,7 +1275,15 @@ createConv :: UserId -> [UserId] -> Http ResponseLBS -createConv g u us = +createConv = createConvWithAccessRoles Nothing + +createConvWithAccessRoles :: + Maybe (Set AccessRoleV2) -> + Galley -> + UserId -> + [UserId] -> + Http ResponseLBS +createConvWithAccessRoles ars g u us = post $ g . path "/conversations" @@ -1267,7 +1293,7 @@ createConv g u us = . contentJson . body (RequestBodyLBS (encode (NewConvUnmanaged conv))) where - conv = NewConv us [] Nothing Set.empty Nothing Nothing Nothing Nothing roleNameWireAdmin + conv = NewConv us [] Nothing Set.empty ars Nothing Nothing Nothing roleNameWireAdmin postMessage :: Galley -> diff --git a/services/galley/test/integration/API.hs b/services/galley/test/integration/API.hs index 6598df2a2e4..ecde034202a 100644 --- a/services/galley/test/integration/API.hs +++ b/services/galley/test/integration/API.hs @@ -113,7 +113,7 @@ tests s = where mainTests = testGroup - "Main API" + "Main Conversations API" [ test s "status" status, test s "metrics" metrics, test s "create conversation" postConvOk, @@ -1190,9 +1190,9 @@ postJoinConvOk = do testJoinCodeConv :: TestM () testJoinCodeConv = do let convName = "gossip" - + Right noGuestsAccess <- liftIO $ genAccessRolesV2 [NonTeamMemberAccessRole] [GuestAccessRole] alice <- randomUser - convId <- decodeConvId <$> postConv alice [] (Just convName) [CodeAccess] (Just (Set.fromList [TeamMemberAccessRole, NonTeamMemberAccessRole])) Nothing + convId <- decodeConvId <$> postConv alice [] (Just convName) [CodeAccess] (Just noGuestsAccess) Nothing cCode <- decodeConvCodeEvent <$> postConvCode alice convId qbob <- randomQualifiedUser @@ -1209,8 +1209,9 @@ testGetCodeRejectedIfGuestLinksDisabled :: TestM () testGetCodeRejectedIfGuestLinksDisabled = do galley <- view tsGalley (owner, teamId, []) <- Util.createBindingTeamWithNMembers 0 + Right accessRoles <- liftIO $ genAccessRolesV2 [TeamMemberAccessRole] [GuestAccessRole] let createConvWithGuestLink = do - convId <- decodeConvId <$> postTeamConv teamId owner [] (Just "testConversation") [CodeAccess] (Just (Set.fromList [TeamMemberAccessRole, NonTeamMemberAccessRole])) Nothing + convId <- decodeConvId <$> postTeamConv teamId owner [] (Just "testConversation") [CodeAccess] (Just accessRoles) Nothing void $ decodeConvCodeEvent <$> postConvCode owner convId pure convId convId <- createConvWithGuestLink @@ -1229,7 +1230,8 @@ testPostCodeRejectedIfGuestLinksDisabled :: TestM () testPostCodeRejectedIfGuestLinksDisabled = do galley <- view tsGalley (owner, teamId, []) <- Util.createBindingTeamWithNMembers 0 - convId <- decodeConvId <$> postTeamConv teamId owner [] (Just "testConversation") [CodeAccess] (Just (Set.fromList [TeamMemberAccessRole, NonTeamMemberAccessRole])) Nothing + Right noGuestsAccess <- liftIO $ genAccessRolesV2 [NonTeamMemberAccessRole] [GuestAccessRole] + convId <- decodeConvId <$> postTeamConv teamId owner [] (Just "testConversation") [CodeAccess] (Just noGuestsAccess) Nothing let checkPostCode expectedStatus = postConvCode owner convId !!! statusCode === const expectedStatus let setStatus tfStatus = TeamFeatures.putTeamFeatureFlagWithGalley @'Public.TeamFeatureGuestLinks galley owner teamId (Public.TeamFeatureStatusNoConfig tfStatus) !!! do @@ -1247,7 +1249,8 @@ testJoinTeamConvGuestLinksDisabled = do let convName = "testConversation" (owner, teamId, []) <- Util.createBindingTeamWithNMembers 0 userNotInTeam <- randomUser - convId <- decodeConvId <$> postTeamConv teamId owner [] (Just convName) [CodeAccess] (Just (Set.fromList [TeamMemberAccessRole, NonTeamMemberAccessRole])) Nothing + Right accessRoles <- liftIO $ genAccessRolesV2 [TeamMemberAccessRole, NonTeamMemberAccessRole] [GuestAccessRole] + convId <- decodeConvId <$> postTeamConv teamId owner [] (Just convName) [CodeAccess] (Just accessRoles) Nothing cCode <- decodeConvCodeEvent <$> postConvCode owner convId -- works by default @@ -1278,7 +1281,8 @@ testJoinNonTeamConvGuestLinksDisabled = do let convName = "testConversation" (owner, teamId, []) <- Util.createBindingTeamWithNMembers 0 userNotInTeam <- randomUser - convId <- decodeConvId <$> postConv owner [] (Just convName) [CodeAccess] (Just (Set.fromList [TeamMemberAccessRole, NonTeamMemberAccessRole])) Nothing + Right accessRoles <- liftIO $ genAccessRolesV2 [NonTeamMemberAccessRole] [GuestAccessRole] + convId <- decodeConvId <$> postConv owner [] (Just convName) [CodeAccess] (Just accessRoles) Nothing cCode <- decodeConvCodeEvent <$> postConvCode owner convId -- works by default @@ -1303,7 +1307,8 @@ postJoinCodeConvOk = do let bob = qUnqualified qbob eve <- ephemeralUser dave <- ephemeralUser - conv <- decodeConvId <$> postConv alice [] (Just "gossip") [CodeAccess] (Just (Set.fromList [TeamMemberAccessRole, NonTeamMemberAccessRole])) Nothing + Right accessRoles <- liftIO $ genAccessRolesV2 [TeamMemberAccessRole, NonTeamMemberAccessRole] [GuestAccessRole] + conv <- decodeConvId <$> postConv alice [] (Just "gossip") [CodeAccess] (Just accessRoles) Nothing let qconv = Qualified conv (qDomain qbob) cCode <- decodeConvCodeEvent <$> postConvCode alice conv -- currently ConversationCode is used both as return type for POST ../code and as body for ../join @@ -1326,11 +1331,12 @@ postJoinCodeConvOk = do WS.assertMatchN (5 # Second) [wsA, wsB] $ wsAssertMemberJoinWithRole qconv qbob [qbob] roleNameWireMember -- changing access to non-activated should give eve access - let nonActivatedAccess = ConversationAccessData (Set.singleton CodeAccess) (Set.fromList [TeamMemberAccessRole, NonTeamMemberAccessRole, GuestAccessRole, ServiceAccessRole]) + Right accessRolesWithGuests <- liftIO $ genAccessRolesV2 [TeamMemberAccessRole, NonTeamMemberAccessRole, GuestAccessRole] [] + let nonActivatedAccess = ConversationAccessData (Set.singleton CodeAccess) accessRolesWithGuests putAccessUpdate alice conv nonActivatedAccess !!! const 200 === statusCode postJoinCodeConv eve payload !!! const 200 === statusCode -- after removing CodeAccess, no further people can join - let noCodeAccess = ConversationAccessData (Set.singleton InviteAccess) (Set.fromList [TeamMemberAccessRole, NonTeamMemberAccessRole, GuestAccessRole, ServiceAccessRole]) + let noCodeAccess = ConversationAccessData (Set.singleton InviteAccess) accessRoles putAccessUpdate alice conv noCodeAccess !!! const 200 === statusCode postJoinCodeConv dave payload !!! const 404 === statusCode @@ -1373,7 +1379,10 @@ postConvertCodeConv = do getConvCode alice conv !!! const 404 === statusCode -- create a new code; then revoking CodeAccess should make existing codes invalid void $ postConvCode alice conv - let noCodeAccess = ConversationAccessData (Set.singleton InviteAccess) (Set.fromList [TeamMemberAccessRole, NonTeamMemberAccessRole, GuestAccessRole, ServiceAccessRole]) + let noCodeAccess = + ConversationAccessData + (Set.singleton InviteAccess) + (Set.fromList [TeamMemberAccessRole, NonTeamMemberAccessRole, GuestAccessRole, ServiceAccessRole]) putAccessUpdate alice conv noCodeAccess !!! const 200 === statusCode getConvCode alice conv !!! const 403 === statusCode @@ -1397,8 +1406,9 @@ postConvertTeamConv = do -- creating a team-only conversation containing eve should fail createTeamConvAccessRaw alice tid [bob, eve] (Just "blaa") acc (Just (Set.fromList [TeamMemberAccessRole])) Nothing Nothing !!! const 403 === statusCode + Right accessRoles <- liftIO $ genAccessRolesV2 [TeamMemberAccessRole, NonTeamMemberAccessRole, GuestAccessRole] [] -- create conversation allowing any type of guest - conv <- createTeamConvAccess alice tid [bob, eve] (Just "blaa") acc (Just (Set.fromList [TeamMemberAccessRole, NonTeamMemberAccessRole, GuestAccessRole, ServiceAccessRole])) Nothing Nothing + conv <- createTeamConvAccess alice tid [bob, eve] (Just "blaa") acc (Just accessRoles) Nothing Nothing -- mallory joins by herself mallory <- ephemeralUser let qmallory = Qualified mallory localDomain