|
| 1 | +{-# LANGUAGE RecordWildCards #-} |
| 2 | + |
| 3 | +module UsersInUnknownTeams where |
| 4 | + |
| 5 | +import Cassandra |
| 6 | +import Cassandra.Util |
| 7 | +import Conduit |
| 8 | +import Data.Aeson (ToJSON) |
| 9 | +import Data.Aeson qualified as Aeson |
| 10 | +import Data.ByteString qualified as BS |
| 11 | +import Data.Conduit.Internal (zipSources) |
| 12 | +import Data.Conduit.List qualified as C |
| 13 | +import Data.Id |
| 14 | +import Imports |
| 15 | +import System.Logger (Logger) |
| 16 | +import System.Logger qualified as Log |
| 17 | +import UnliftIO (pooledMapConcurrentlyN) |
| 18 | +import Wire.API.Team.Permission |
| 19 | +import Wire.API.User (AccountStatus) |
| 20 | + |
| 21 | +runCommand :: Logger -> FilePath -> ClientState -> ClientState -> IO () |
| 22 | +runCommand l inconsistenciesFile casBrig casGalley = do |
| 23 | + runResourceT $ |
| 24 | + runConduit $ |
| 25 | + zipSources |
| 26 | + (C.sourceList [(1 :: Int32) ..]) |
| 27 | + (transPipe (runClient casBrig) getUsers) |
| 28 | + .| C.mapM |
| 29 | + ( \(i, userDetailsRow) -> do |
| 30 | + let userDetails = map mkUserDetails userDetailsRow |
| 31 | + Log.info l (Log.field "userIds" (show ((i - 1) * pageSize + fromIntegral (length userDetails)))) |
| 32 | + pure $ |
| 33 | + mapMaybe |
| 34 | + ( \user -> case user.teamId of |
| 35 | + Nothing -> Nothing |
| 36 | + Just tid -> Just (user, tid.value) |
| 37 | + ) |
| 38 | + userDetails |
| 39 | + ) |
| 40 | + .| C.mapM (pooledMapConcurrentlyN 48 (liftIO . checkUser l casBrig casGalley)) |
| 41 | + .| C.map ((<> "\n") . BS.intercalate "\n" . map (BS.toStrict . Aeson.encode) . catMaybes) |
| 42 | + .| sinkFile inconsistenciesFile |
| 43 | + |
| 44 | +data InconsistentData = InconsistentData |
| 45 | + { user :: UserDetails, |
| 46 | + perms :: Maybe (WithWritetime Permissions), |
| 47 | + clients :: [ClientId], |
| 48 | + connections :: [UserId], |
| 49 | + admins :: [UserId] |
| 50 | + } |
| 51 | + deriving (Generic) |
| 52 | + |
| 53 | +instance ToJSON InconsistentData |
| 54 | + |
| 55 | +checkUser :: Logger -> ClientState -> ClientState -> (UserDetails, TeamId) -> IO (Maybe InconsistentData) |
| 56 | +checkUser l casBrig casGalley (user, tid) = do |
| 57 | + mTeam <- runClient casGalley $ getTeam tid |
| 58 | + case mTeam of |
| 59 | + Just _ -> pure Nothing |
| 60 | + Nothing -> do |
| 61 | + Log.warn l $ |
| 62 | + Log.msg (Log.val "team not found") |
| 63 | + . Log.field "team" (idToText tid) |
| 64 | + . Log.field "user" (idToText user.id_) |
| 65 | + mMember <- runClient casGalley $ getTeamMember tid user.id_ |
| 66 | + let perms = case mMember of |
| 67 | + Nothing -> Nothing |
| 68 | + Just (p, writeTime) -> WithWritetime <$> p <*> writeTime |
| 69 | + admins <- runClient casGalley $ getTeamAdmins tid |
| 70 | + clients <- runClient casBrig $ getClients user.id_ |
| 71 | + connections <- runClient casBrig $ getConnections user.id_ |
| 72 | + pure . Just $ InconsistentData {..} |
| 73 | + |
| 74 | +-- CQL |
| 75 | + |
| 76 | +pageSize :: Int32 |
| 77 | +pageSize = 10000 |
| 78 | + |
| 79 | +getUsers :: ConduitM () [UserDetailsRow] Client () |
| 80 | +getUsers = paginateC cql (paramsP LocalQuorum () pageSize) x5 |
| 81 | + where |
| 82 | + cql :: PrepQuery R () UserDetailsRow |
| 83 | + cql = "SELECT id, activated, status, writetime(status), team, writetime(team) from user" |
| 84 | + |
| 85 | +getClients :: UserId -> Client [ClientId] |
| 86 | +getClients uid = runIdentity <$$> query cql (params One (Identity uid)) |
| 87 | + where |
| 88 | + cql :: PrepQuery R (Identity UserId) (Identity ClientId) |
| 89 | + cql = "SELECT client from clients where user = ?" |
| 90 | + |
| 91 | +getConnections :: UserId -> Client [UserId] |
| 92 | +getConnections uid = runIdentity <$$> query cql (params One (Identity uid)) |
| 93 | + where |
| 94 | + cql :: PrepQuery R (Identity UserId) (Identity UserId) |
| 95 | + cql = "SELECT right from connection where left = ?" |
| 96 | + |
| 97 | +getTeamMember :: TeamId -> UserId -> Client (Maybe TeamMemberRow) |
| 98 | +getTeamMember tid uid = query1 cql (params One (tid, uid)) |
| 99 | + where |
| 100 | + cql :: PrepQuery R (TeamId, UserId) TeamMemberRow |
| 101 | + cql = "SELECT perms, writetime(perms) from team_member where team = ? AND user = ?" |
| 102 | + |
| 103 | +getTeamAdmins :: TeamId -> Client [UserId] |
| 104 | +getTeamAdmins tid = runIdentity <$$> query cql (params One (Identity tid)) |
| 105 | + where |
| 106 | + cql :: PrepQuery R (Identity TeamId) (Identity UserId) |
| 107 | + cql = "SELECT user from team_admin where team = ?" |
| 108 | + |
| 109 | +getTeam :: TeamId -> Client (Maybe TeamRow) |
| 110 | +getTeam tid = query1 cql (params One (Identity tid)) |
| 111 | + where |
| 112 | + cql :: PrepQuery R (Identity TeamId) TeamRow |
| 113 | + cql = "SELECT binding, creator, deleted, name, search_visibility, status from team where team = ?" |
| 114 | + |
| 115 | +type UserDetailsRow = (UserId, Maybe Bool, Maybe AccountStatus, Maybe (Writetime AccountStatus), Maybe TeamId, Maybe (Writetime TeamId)) |
| 116 | + |
| 117 | +data WithWritetime a = WithWritetime |
| 118 | + { value :: a, |
| 119 | + writetime :: Writetime a |
| 120 | + } |
| 121 | + deriving (Generic) |
| 122 | + |
| 123 | +instance (ToJSON a) => ToJSON (WithWritetime a) |
| 124 | + |
| 125 | +data UserDetails = UserDetails |
| 126 | + { id_ :: UserId, |
| 127 | + activated :: Maybe Bool, |
| 128 | + accountStatus :: Maybe (WithWritetime AccountStatus), |
| 129 | + teamId :: Maybe (WithWritetime TeamId) |
| 130 | + } |
| 131 | + deriving (Generic) |
| 132 | + |
| 133 | +instance ToJSON UserDetails |
| 134 | + |
| 135 | +mkUserDetails :: UserDetailsRow -> UserDetails |
| 136 | +mkUserDetails (uid, activated, accountStatus, accountStateWrite, teamId, teamIdWrite) = |
| 137 | + UserDetails |
| 138 | + { id_ = uid, |
| 139 | + activated = activated, |
| 140 | + accountStatus = WithWritetime <$> accountStatus <*> accountStateWrite, |
| 141 | + teamId = WithWritetime <$> teamId <*> teamIdWrite |
| 142 | + } |
| 143 | + |
| 144 | +type TeamMemberRow = (Maybe Permissions, Maybe (Writetime Permissions)) |
| 145 | + |
| 146 | +type TeamRow = (Maybe Bool, Maybe UserId, Maybe Bool, Maybe Text, Maybe Int32, Maybe Int32) |
| 147 | + |
| 148 | +data TeamDetails = TeamDetails |
| 149 | + { binding :: Maybe Bool, |
| 150 | + creator :: Maybe UserId, |
| 151 | + deleted :: Maybe Bool, |
| 152 | + name :: Maybe Text, |
| 153 | + searchVisibility :: Maybe Int32, |
| 154 | + status :: Maybe Int32 |
| 155 | + } |
| 156 | + |
| 157 | +mkTeamDetails :: TeamRow -> TeamDetails |
| 158 | +mkTeamDetails (binding, creator, deleted, name, searchVisibility, status) = |
| 159 | + TeamDetails {..} |
0 commit comments