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
20 changes: 20 additions & 0 deletions cassandra-schema.cql
Original file line number Diff line number Diff line change
Expand Up @@ -1415,6 +1415,26 @@ CREATE TABLE galley_test.group_id_conv_id (
AND read_repair_chance = 0.0
AND speculative_retry = '99PERCENTILE';

CREATE TABLE galley_test.team_admin (
team uuid,
user uuid,
PRIMARY KEY (team, user)
) WITH CLUSTERING ORDER BY (user ASC)
AND bloom_filter_fp_chance = 0.1
AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'}
AND comment = ''
AND compaction = {'class': 'org.apache.cassandra.db.compaction.LeveledCompactionStrategy'}
AND compression = {'chunk_length_in_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'}
AND crc_check_chance = 1.0
AND dclocal_read_repair_chance = 0.1
AND default_time_to_live = 0
AND gc_grace_seconds = 864000
AND max_index_interval = 2048
AND memtable_flush_period_in_ms = 0
AND min_index_interval = 128
AND read_repair_chance = 0.0
AND speculative_retry = '99PERCENTILE';

-- NOTE: this table is unused. It was replaced by mls_group_member_client
CREATE TABLE galley_test.member_client (
conv uuid,
Expand Down
10 changes: 10 additions & 0 deletions libs/galley-types/src/Galley/Types/Teams.hs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ module Galley.Types.Teams
rolePermissions,
roleHiddenPermissions,
permissionsRole,
isAdminOrOwner,
HiddenPerm (..),
IsPerm (..),
)
Expand Down Expand Up @@ -101,6 +102,15 @@ permissionsRole (Permissions p p') =
rolePerms role `Set.isSubsetOf` perms
]

isAdminOrOwner :: Permissions -> Bool
isAdminOrOwner perms =
case permissionsRole perms of
Just RoleOwner -> True
Just RoleAdmin -> True
Just RoleMember -> False
Just RoleExternalPartner -> False
Nothing -> False

-- | Internal function for 'rolePermissions'. (It works iff the two sets in 'Permissions' are
-- identical for every 'Role', otherwise it'll need to be specialized for the resp. sides.)
rolePerms :: Role -> Set Perm
Expand Down
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 @@ -59,6 +59,7 @@ data GalleyError
| UserBindingExists
| NoAddToBinding
| TooManyTeamMembers
| TooManyTeamAdmins
| -- FUTUREWORK: possibly make MissingPermission take a list of Perm
MissingPermission (Maybe Perm)
| ActionDenied Action
Expand Down Expand Up @@ -168,6 +169,8 @@ type instance MapError 'NoAddToBinding = 'StaticError 403 "binding-team" "Cannot

type instance MapError 'TooManyTeamMembers = 'StaticError 403 "too-many-team-members" "Maximum number of members per team reached"

type instance MapError 'TooManyTeamAdmins = 'StaticError 403 "too-many-team-admins" "Maximum number of admins per team reached"

type instance MapError ('MissingPermission mperm) = 'StaticError 403 "operation-denied" (MissingPermissionMessage mperm)

type instance MapError ('ActionDenied action) = 'StaticError 403 "action-denied" ("Insufficient authorization (missing " `AppendSymbol` ActionName action `AppendSymbol` ")")
Expand Down
2 changes: 2 additions & 0 deletions libs/wire-api/src/Wire/API/Routes/Internal/Galley.hs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ type ITeamsAPIBase =
"unchecked-add-team-member"
( CanThrow 'TooManyTeamMembers
:> CanThrow 'TooManyTeamMembersOnTeamWithLegalhold
:> CanThrow 'TooManyTeamAdmins
:> ReqBody '[Servant.JSON] NewTeamMember
:> MultiVerb1 'POST '[Servant.JSON] (RespondEmpty 200 "OK")
)
Expand All @@ -320,6 +321,7 @@ type ITeamsAPIBase =
:> CanThrow 'InvalidPermissions
:> CanThrow 'TeamNotFound
:> CanThrow 'TeamMemberNotFound
:> CanThrow 'TooManyTeamAdmins
:> CanThrow 'NotATeamMember
:> CanThrow OperationDenied
:> ReqBody '[Servant.JSON] NewTeamMember
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ type TeamMemberAPI =
:> CanThrow OperationDenied
:> CanThrow 'TeamNotFound
:> CanThrow 'TooManyTeamMembers
:> CanThrow 'TooManyTeamAdmins
:> CanThrow 'UserBindingExists
:> CanThrow 'TooManyTeamMembersOnTeamWithLegalhold
:> ZLocalUser
Expand Down Expand Up @@ -169,6 +170,7 @@ type TeamMemberAPI =
:> CanThrow 'InvalidPermissions
:> CanThrow 'TeamNotFound
:> CanThrow 'TeamMemberNotFound
:> CanThrow 'TooManyTeamAdmins
:> CanThrow 'NotATeamMember
:> CanThrow OperationDenied
:> ZLocalUser
Expand Down
2 changes: 1 addition & 1 deletion services/brig/test/integration/API/Federation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ testRemoteUserGetsDeleted opts brig cannon fedBrigClient = do
runFedClient @"on-user-deleted-connections" fedBrigClient (qDomain remoteUser) $
UserDeletedConnectionsNotification (qUnqualified remoteUser) (unsafeRange localUsers)

WS.assertMatchN_ (5 # Second) [cc] $ matchDeleteUserNotification remoteUser
WS.assertMatchN_ (60 # Second) [cc] $ matchDeleteUserNotification remoteUser
WS.assertNoEvent (1 # Second) [pc, bc, uc]

for_ localUsers $ \u ->
Expand Down
3 changes: 3 additions & 0 deletions services/galley/galley.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,7 @@ executable galley-migrate-data
Paths_galley
V1_BackfillBillingTeamMembers
V2_MigrateMLSMembers
V3_BackfillTeamAdmins

hs-source-dirs: migrate-data/src
default-extensions:
Expand Down Expand Up @@ -583,6 +584,7 @@ executable galley-migrate-data
, exceptions
, extended
, galley
, galley-types
, imports
, lens
, optparse-applicative
Expand Down Expand Up @@ -667,6 +669,7 @@ executable galley-schema
V80_AddConversationCodePassword
V81_TeamFeatureMlsE2EIdUpdate
V82_RemoteDomainIndexes
V83_CreateTableTeamAdmin

hs-source-dirs: schema/src
default-extensions:
Expand Down
4 changes: 3 additions & 1 deletion services/galley/migrate-data/src/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import Options.Applicative
import qualified System.Logger.Extended as Log
import qualified V1_BackfillBillingTeamMembers
import qualified V2_MigrateMLSMembers
import qualified V3_BackfillTeamAdmins

main :: IO ()
main = do
Expand All @@ -32,7 +33,8 @@ main = do
l
o
[ V1_BackfillBillingTeamMembers.migration,
V2_MigrateMLSMembers.migration
V2_MigrateMLSMembers.migration,
V3_BackfillTeamAdmins.migration
]
where
desc = header "Galley Cassandra Data Migrations" <> fullDesc
74 changes: 74 additions & 0 deletions services/galley/migrate-data/src/V3_BackfillTeamAdmins.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
-- This file is part of the Wire Server implementation.
--
-- Copyright (C) 2023 Wire Swiss GmbH <opensource@wire.com>
--
-- This program is free software: you can redistribute it and/or modify it under
-- the terms of the GNU Affero General Public License as published by the Free
-- Software Foundation, either version 3 of the License, or (at your option) any
-- later version.
--
-- This program is distributed in the hope that it will be useful, but WITHOUT
-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
-- details.
--
-- You should have received a copy of the GNU Affero General Public License along
-- with this program. If not, see <https://www.gnu.org/licenses/>.

module V3_BackfillTeamAdmins where

import Cassandra
import Conduit
import Data.Conduit.Internal (zipSources)
import qualified Data.Conduit.List as C
import Data.Id
import Galley.DataMigration.Types
import Galley.Types.Teams
import Imports
import qualified System.Logger.Class as Log
import Wire.API.Team.Permission
import Wire.API.Team.Role

migration :: Migration
migration =
Migration
{ version = MigrationVersion 3,
text = "Backfill team_admin",
action =
runConduit $
zipSources
(C.sourceList [(1 :: Int32) ..])
getTeamMembers
.| C.mapM
( \(i, p) ->
Log.info (Log.field "team members" (show (i * pageSize)))
>> pure p
)
.| C.concatMap (filter isAdmin)
.| C.map (\(t, u, _) -> (t, u))
.| C.mapM_ createTeamAdmins
}

pageSize :: Int32
pageSize = 1000

----------------------------------------------------------------------------
-- Queries

-- | Get team members from Galley
getTeamMembers :: MonadClient m => ConduitM () [(TeamId, UserId, Maybe Permissions)] m ()
getTeamMembers = paginateC cql (paramsP LocalQuorum () pageSize) x5
where
cql :: PrepQuery R () (TeamId, UserId, Maybe Permissions)
cql = "SELECT team, user, perms FROM team_member"

createTeamAdmins :: MonadClient m => (TeamId, UserId) -> m ()
createTeamAdmins pair =
retry x5 $ write cql (params LocalQuorum pair)
where
cql :: PrepQuery W (TeamId, UserId) ()
cql = "INSERT INTO team_admin (team, user) values (?, ?)"

isAdmin :: (TeamId, UserId, Maybe Permissions) -> Bool
isAdmin (_, _, Just p) = permissionsRole p == Just RoleAdmin || permissionsRole p == Just RoleOwner
isAdmin _ = False
4 changes: 3 additions & 1 deletion services/galley/schema/src/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ import qualified V79_TeamFeatureMlsE2EId
import qualified V80_AddConversationCodePassword
import qualified V81_TeamFeatureMlsE2EIdUpdate
import qualified V82_RemoteDomainIndexes
import qualified V83_CreateTableTeamAdmin

main :: IO ()
main = do
Expand Down Expand Up @@ -155,7 +156,8 @@ main = do
V79_TeamFeatureMlsE2EId.migration,
V80_AddConversationCodePassword.migration,
V81_TeamFeatureMlsE2EIdUpdate.migration,
V82_RemoteDomainIndexes.migration
V82_RemoteDomainIndexes.migration,
V83_CreateTableTeamAdmin.migration
-- When adding migrations here, don't forget to update
-- 'schemaVersion' in Galley.Cassandra
-- (see also docs/developer/cassandra-interaction.md)
Expand Down
36 changes: 36 additions & 0 deletions services/galley/schema/src/V83_CreateTableTeamAdmin.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
-- This file is part of the Wire Server implementation.
--
-- Copyright (C) 2023 Wire Swiss GmbH <opensource@wire.com>
--
-- This program is free software: you can redistribute it and/or modify it under
-- the terms of the GNU Affero General Public License as published by the Free
-- Software Foundation, either version 3 of the License, or (at your option) any
-- later version.
--
-- This program is distributed in the hope that it will be useful, but WITHOUT
-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
-- details.
--
-- You should have received a copy of the GNU Affero General Public License along
-- with this program. If not, see <https://www.gnu.org/licenses/>.

module V83_CreateTableTeamAdmin
( migration,
)
where

import Cassandra.Schema
import Imports
import Text.RawString.QQ

migration :: Migration
migration = Migration 83 "Create table `team_admin`" $ do
schema'
[r|
CREATE TABLE team_admin (
team uuid,
user uuid,
PRIMARY KEY (team, user)
) WITH compaction = {'class': 'LeveledCompactionStrategy'};
|]
5 changes: 3 additions & 2 deletions services/galley/src/Galley/API/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ import Galley.Effects.MemberStore
import qualified Galley.Effects.MemberStore as E
import Galley.Effects.ProposalStore
import Galley.Effects.TeamStore
import qualified Galley.Effects.TeamStore as E
import qualified Galley.Intra.Push as Intra
import Galley.Monad
import Galley.Options
Expand Down Expand Up @@ -375,8 +376,8 @@ rmUser lusr conn = do
goConvPages range newCids

leaveTeams page = for_ (pageItems page) $ \tid -> do
mems <- getTeamMembersForFanout tid
uncheckedDeleteTeamMember lusr conn tid (tUnqualified lusr) mems
admins <- E.getTeamAdmins tid
uncheckedDeleteTeamMember lusr conn tid (tUnqualified lusr) admins
page' <- listTeams @p2 (tUnqualified lusr) (Just (pageState page)) maxBound
leaveTeams page'

Expand Down
Loading