Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change of team permission semantics #569

Merged
merged 9 commits into from
Jan 15, 2019
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
34 changes: 17 additions & 17 deletions libs/galley-types/src/Galley/Types/Teams.hs
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,8 @@ data Perm =
| RemoveTeamMember
| AddConversationMember
| RemoveConversationMember
| GetBilling
| SetBilling
| ModifyConversationMetadata
| CRUDBilling
| SetTeamData
| GetMemberPermissions
| SetMemberPermissions
Expand Down Expand Up @@ -394,19 +394,19 @@ isTeamOwner :: TeamMember -> Bool
isTeamOwner tm = fullPermissions == (tm^.permissions)

permToInt :: Perm -> Word64
permToInt CreateConversation = 0x0001
permToInt DeleteConversation = 0x0002
permToInt AddTeamMember = 0x0004
permToInt RemoveTeamMember = 0x0008
permToInt AddConversationMember = 0x0010
permToInt RemoveConversationMember = 0x0020
permToInt GetBilling = 0x0040
permToInt SetBilling = 0x0080
permToInt SetTeamData = 0x0100
permToInt GetMemberPermissions = 0x0200
permToInt GetTeamConversations = 0x0400
permToInt DeleteTeam = 0x0800
permToInt SetMemberPermissions = 0x1000
permToInt CreateConversation = 0x0001
permToInt DeleteConversation = 0x0002
permToInt AddTeamMember = 0x0004
permToInt RemoveTeamMember = 0x0008
permToInt AddConversationMember = 0x0010
permToInt RemoveConversationMember = 0x0020
permToInt ModifyConversationMetadata = 0x0040
permToInt CRUDBilling = 0x0080
permToInt SetTeamData = 0x0100
permToInt GetMemberPermissions = 0x0200
permToInt GetTeamConversations = 0x0400
permToInt DeleteTeam = 0x0800
permToInt SetMemberPermissions = 0x1000

intToPerm :: Word64 -> Maybe Perm
intToPerm 0x0001 = Just CreateConversation
Expand All @@ -415,8 +415,8 @@ intToPerm 0x0004 = Just AddTeamMember
intToPerm 0x0008 = Just RemoveTeamMember
intToPerm 0x0010 = Just AddConversationMember
intToPerm 0x0020 = Just RemoveConversationMember
intToPerm 0x0040 = Just GetBilling
intToPerm 0x0080 = Just SetBilling
intToPerm 0x0040 = Just ModifyConversationMetadata
intToPerm 0x0080 = Just CRUDBilling
intToPerm 0x0100 = Just SetTeamData
intToPerm 0x0200 = Just GetMemberPermissions
intToPerm 0x0400 = Just GetTeamConversations
Expand Down
8 changes: 7 additions & 1 deletion services/galley/src/Galley/API/Update.hs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE ViewPatterns #-}

module Galley.API.Update
( -- * Managing Conversations
Expand Down Expand Up @@ -122,7 +124,8 @@ updateConversationAccess (usr ::: zcon ::: cnv ::: req ::: _ ) = do
ensureGroupConv conv
-- Team conversations incur another round of checks
case Data.convTeam conv of
Just tid -> checkTeamConv tid
Just tid -> checkTeamConv tid >>
permissionCheckTeamConv usr cnv ModifyConversationMetadata
Nothing -> when (targetRole == TeamAccessRole) $ throwM invalidTargetAccess
-- When there is no update to be done, we return 204; otherwise we go
-- with 'uncheckedUpdateConversationAccess', which will potentially kick
Expand Down Expand Up @@ -208,6 +211,7 @@ uncheckedUpdateConversationAccess body usr zcon conv (currentAccess, targetAcces
updateConversationReceiptMode :: UserId ::: ConnId ::: ConvId ::: Request ::: JSON ::: JSON -> Galley Response
updateConversationReceiptMode (usr ::: zcon ::: cnv ::: req ::: _ ::: _) = do
ConversationReceiptModeUpdate target <- fromBody req invalidPayload
permissionCheckTeamConv usr cnv ModifyConversationMetadata
(bots, users) <- botsAndUsers <$> Data.members cnv
current <- Data.lookupReceiptMode cnv
if current == Just target
Expand All @@ -232,6 +236,7 @@ updateConversationMessageTimer (usr ::: zcon ::: cnv ::: req ::: _ ) = do
conv <- Data.conversation cnv >>= ifNothing convNotFound
ensureGroupConv conv
traverse_ ensureTeamMember $ Data.convTeam conv -- only team members can change the timer
permissionCheckTeamConv usr cnv ModifyConversationMetadata
let currentTimer = Data.convMessageTimer conv
if currentTimer == messageTimer then
return $ empty & setStatus status204
Expand Down Expand Up @@ -499,6 +504,7 @@ newMessage usr con cnv msg now (m, c, t) ~(toBots, toUsers) =
updateConversation :: UserId ::: ConnId ::: ConvId ::: Request ::: JSON -> Galley Response
updateConversation (zusr ::: zcon ::: cnv ::: req ::: _) = do
body <- fromBody req invalidPayload
permissionCheckTeamConv zusr cnv ModifyConversationMetadata
alive <- Data.isConvAlive cnv
unless alive $ do
Data.deleteConversation cnv
Expand Down
15 changes: 14 additions & 1 deletion services/galley/src/Galley/API/Util.hs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ViewPatterns #-}

module Galley.API.Util where

Expand Down Expand Up @@ -72,6 +73,9 @@ bindingTeamMembers tid = do
Binding -> Data.teamMembers tid
NonBinding -> throwM nonBindingTeam

-- | Pick a team member with a given user id from some team members. If the filter comes up empty,
-- throw 'noTeamMember'; if the user is found and does not have the given permission, throw
-- 'operationDenied'. Otherwise, return the found user.
permissionCheck :: Foldable m => UserId -> Perm -> m TeamMember -> Galley TeamMember
permissionCheck u p t =
case find ((u ==) . view userId) t of
Expand All @@ -81,6 +85,15 @@ permissionCheck u p t =
pure m
Nothing -> throwM noTeamMember

-- | If the conversation is in a team, throw iff zusr is a team member and does not have named
-- permission. If the conversation is not in a team, do nothing (no error).
permissionCheckTeamConv :: UserId -> ConvId -> Perm -> Galley ()
permissionCheckTeamConv zusr cnv perm = Data.conversation cnv >>= \case
Just cnv' -> case Data.convTeam cnv' of
Just tid -> void $ permissionCheck zusr perm =<< Data.teamMembers tid
Nothing -> pure ()
Nothing -> throwM convNotFound

-- | Try to accept a 1-1 conversation, promoting connect conversations as appropriate.
acceptOne2One :: UserId -> Data.Conversation -> Maybe ConnId -> Galley Data.Conversation
acceptOne2One usr conv conn = case Data.convType conv of
Expand Down
2 changes: 1 addition & 1 deletion services/galley/src/Galley/Intra/Journal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,5 @@ journalEvent typ tid dat tim = view aEnv >>= \mEnv -> for_ mEnv $ \e -> do
evData :: [TeamMember] -> Maybe Currency.Alpha -> TeamEvent'EventData
evData mems cur = TeamEvent'EventData count (toBytes <$> uids) (pack . show <$> cur) []
where
uids = view userId <$> filter (`hasPermission` SetBilling) mems
uids = view userId <$> filter (`hasPermission` CRUDBilling) mems
count = fromIntegral $ length mems
26 changes: 25 additions & 1 deletion services/galley/test/integration/API/Teams.hs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ tests s = testGroup "Teams API"
, test s "add managed conversation through public endpoint (fail)" testAddManagedConv
, test s "add managed team conversation ignores given users" testAddTeamConvWithUsers
, test s "add team member to conversation without connection" testAddTeamMemberToConv
, test s "update conversation as member" (testUpdateTeamConv True)
, test s "update conversation as collaborator" (testUpdateTeamConv False)
, test s "delete non-binding team" testDeleteTeam
, test s "delete binding team (owner has passwd)" (testDeleteBindingTeam True)
, test s "delete binding team (owner has no passwd)" (testDeleteBindingTeam False)
Expand Down Expand Up @@ -241,7 +243,7 @@ testAddTeamMemberInternal :: Galley -> Brig -> Cannon -> Maybe Aws.Env -> Http (
testAddTeamMemberInternal g b c a = do
owner <- Util.randomUser b
tid <- Util.createTeam g "foo" owner []
let p1 = Util.symmPermissions [GetBilling] -- permissions are irrelevant on internal endpoint
let p1 = Util.symmPermissions [AddTeamMember] -- permissions are irrelevant on internal endpoint
mem1 <- newTeamMember' p1 <$> Util.randomUser b

WS.bracketRN c [owner, mem1^.userId] $ \[wsOwner, wsMem1] -> do
Expand Down Expand Up @@ -482,6 +484,28 @@ testAddTeamMemberToConv g b _ _ = do
const 403 === statusCode
const "operation-denied" === (Error.label . Util.decodeBody' "error label")

testUpdateTeamConv :: Bool -> Galley -> Brig -> Cannon -> Maybe Aws.Env -> Http ()
testUpdateTeamConv roleIsMember g b _ _ = do
owner <- Util.randomUser b
member <- Util.randomUser b
let perms = Util.symmPermissions $ if roleIsMember then permsMember else permsCollaborator
permsMember = permsCollaborator <>
[ DeleteConversation
, AddConversationMember
, RemoveConversationMember
, ModifyConversationMetadata
, GetMemberPermissions
]
permsCollaborator =
[ CreateConversation
, GetTeamConversations
]
Util.connectUsers b owner (list1 member [])
tid <- Util.createTeam g "foo" owner [newTeamMember member perms Nothing]
cid <- Util.createTeamConv g owner tid [member] (Just "gossip") Nothing Nothing
resp <- updateTeamConv g member cid (ConversationRename "not gossip")
liftIO $ assertEqual "status" (if roleIsMember then 200 else 403) (statusCode resp)

testDeleteTeam :: Galley -> Brig -> Cannon -> Maybe Aws.Env -> Http ()
testDeleteTeam g b c a = do
owner <- Util.randomUser b
Expand Down
11 changes: 11 additions & 0 deletions services/galley/test/integration/API/Util.hs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,17 @@ createTeamConvAccessRaw g u tid us name acc role mtimer = do
. json conv
)

updateTeamConv :: Galley -> UserId -> ConvId -> ConversationRename -> Http ResponseLBS
updateTeamConv g zusr convid upd = do
put ( g
. paths ["/conversations", toByteString' convid]
. zUser zusr
. zConn "conn"
. zType "access"
. json upd
)

-- | See Note [managed conversations]
createManagedConv :: HasCallStack => Galley -> UserId -> TeamId -> [UserId] -> Maybe Text -> Maybe (Set Access) -> Maybe Milliseconds -> Http ConvId
createManagedConv g u tid us name acc mtimer = do
let tinfo = ConvTeamInfo tid True
Expand Down