Skip to content

Commit

Permalink
Change of team permission semantics (#569)
Browse files Browse the repository at this point in the history
* Update `Perm` data type.

* Check for new permission where applicable.
  • Loading branch information
fisx authored Jan 15, 2019
1 parent 39825bd commit eeaeb4c
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 21 deletions.
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

0 comments on commit eeaeb4c

Please sign in to comment.