diff --git a/changelog.d/3-bug-fixes/mls-public-keys b/changelog.d/3-bug-fixes/mls-public-keys new file mode 100644 index 00000000000..fd07f3bbede --- /dev/null +++ b/changelog.d/3-bug-fixes/mls-public-keys @@ -0,0 +1 @@ +Fix all clients having the same MLS public key diff --git a/libs/wire-api/src/Wire/API/User/Client.hs b/libs/wire-api/src/Wire/API/User/Client.hs index 58016933d9f..aa2b8349bb3 100644 --- a/libs/wire-api/src/Wire/API/User/Client.hs +++ b/libs/wire-api/src/Wire/API/User/Client.hs @@ -44,6 +44,7 @@ module Wire.API.User.Client PubClient (..), ClientType (..), ClientClass (..), + MLSPublicKeys, -- * New/Update/Remove Client NewClient (..), diff --git a/services/brig/src/Brig/Data/Client.hs b/services/brig/src/Brig/Data/Client.hs index 51b42529a32..a14a929273e 100644 --- a/services/brig/src/Brig/Data/Client.hs +++ b/services/brig/src/Brig/Data/Client.hs @@ -211,8 +211,17 @@ lookupPubClientsBulk uids = liftClient $ do lookupClients :: MonadClient m => UserId -> m [Client] lookupClients u = do - keys <- retry x1 (query selectMLSPublicKeysByUser (params LocalQuorum (Identity u))) - toClient keys <$$> retry x1 (query selectClients (params LocalQuorum (Identity u))) + keys <- + (\(cid, ss, Blob b) -> (cid, [(ss, LBS.toStrict b)])) + <$$> retry x1 (query selectMLSPublicKeysByUser (params LocalQuorum (Identity u))) + let keyMap = Map.fromListWith (<>) keys + updateKeys c = + c + { clientMLSPublicKeys = + Map.fromList $ Map.findWithDefault [] (clientId c) keyMap + } + updateKeys . toClient [] + <$$> retry x1 (query selectClients (params LocalQuorum (Identity u))) lookupClientIds :: MonadClient m => UserId -> m [ClientId] lookupClientIds u = @@ -405,8 +414,8 @@ selectMLSPublicKey = "SELECT key from mls_public_keys where user = ? and client selectMLSPublicKeys :: PrepQuery R (UserId, ClientId) (SignatureSchemeTag, Blob) selectMLSPublicKeys = "SELECT sig_scheme, key from mls_public_keys where user = ? and client = ?" -selectMLSPublicKeysByUser :: PrepQuery R (Identity UserId) (SignatureSchemeTag, Blob) -selectMLSPublicKeysByUser = "SELECT sig_scheme, key from mls_public_keys where user = ?" +selectMLSPublicKeysByUser :: PrepQuery R (Identity UserId) (ClientId, SignatureSchemeTag, Blob) +selectMLSPublicKeysByUser = "SELECT client, sig_scheme, key from mls_public_keys where user = ?" insertMLSPublicKeys :: PrepQuery W (UserId, ClientId, SignatureSchemeTag, Blob) Row insertMLSPublicKeys = diff --git a/services/brig/test/integration/API/User/Client.hs b/services/brig/test/integration/API/User/Client.hs index e8e7a427b06..a3fa27228db 100644 --- a/services/brig/test/integration/API/User/Client.hs +++ b/services/brig/test/integration/API/User/Client.hs @@ -103,6 +103,7 @@ tests _cl _at opts p db b c g = test p "put /clients/:client - 200" $ testUpdateClient opts b, test p "put /clients/:client - 200 (mls keys)" $ testMLSPublicKeyUpdate b, test p "get /clients/:client - 404" $ testMissingClient b, + test p "get /clients/:client - 200" $ testMLSClient b, test p "post /clients - 200 multiple temporary" $ testAddMultipleTemporary b g, test p "client/prekeys/race" $ testPreKeyRace b ] @@ -308,10 +309,15 @@ testListClients brig = do let (pk1, lk1) = (somePrekeys !! 0, (someLastPrekeys !! 0)) let (pk2, lk2) = (somePrekeys !! 1, (someLastPrekeys !! 1)) let (pk3, lk3) = (somePrekeys !! 2, (someLastPrekeys !! 2)) - c1 <- responseJsonMaybe <$> addClient brig uid (defNewClient PermanentClientType [pk1] lk1) - c2 <- responseJsonMaybe <$> addClient brig uid (defNewClient PermanentClientType [pk2] lk2) - c3 <- responseJsonMaybe <$> addClient brig uid (defNewClient TemporaryClientType [pk3] lk3) - let cs = sortBy (compare `on` clientId) $ catMaybes [c1, c2, c3] + c1 <- responseJsonError =<< addClient brig uid (defNewClient PermanentClientType [pk1] lk1) + c2 <- responseJsonError =<< addClient brig uid (defNewClient PermanentClientType [pk2] lk2) + c3 <- responseJsonError =<< addClient brig uid (defNewClient TemporaryClientType [pk3] lk3) + + let pks = Map.fromList [(Ed25519, "random")] + void $ putClient brig uid (clientId c1) pks + let c1' = c1 {clientMLSPublicKeys = pks} + let cs = sortBy (compare `on` clientId) [c1', c2, c3] + get ( brig . path "clients" @@ -321,6 +327,25 @@ testListClients brig = do const 200 === statusCode const (Just cs) === responseJsonMaybe +testMLSClient :: Brig -> Http () +testMLSClient brig = do + uid <- userId <$> randomUser brig + let (pk1, lk1) = (somePrekeys !! 0, (someLastPrekeys !! 0)) + let (pk2, lk2) = (somePrekeys !! 1, (someLastPrekeys !! 1)) + -- An MLS client + c1 <- responseJsonError =<< addClient brig uid (defNewClient PermanentClientType [pk1] lk1) + -- Non-MLS client + c2 <- responseJsonError =<< addClient brig uid (defNewClient PermanentClientType [pk2] lk2) + + let pks = Map.fromList [(Ed25519, "random")] + void $ putClient brig uid (clientId c1) pks + + -- Assert that adding MLS public keys to one client does not affect the other + -- client + getClient brig uid (clientId c2) !!! do + const 200 === statusCode + const (Just c2) === responseJsonMaybe + testListClientsBulk :: Opt.Opts -> Brig -> Http () testListClientsBulk opts brig = do uid1 <- userId <$> randomUser brig @@ -840,25 +865,11 @@ testMLSPublicKeyUpdate brig = do } c <- responseJsonError =<< addClient brig uid clt let keys = Map.fromList [(Ed25519, "aGVsbG8gd29ybGQ=")] - put - ( brig - . paths ["clients", toByteString' (clientId c)] - . zUser uid - . contentJson - . json (UpdateClient [] Nothing Nothing Nothing keys) - ) - !!! const 200 === statusCode + putClient brig uid (clientId c) keys !!! const 200 === statusCode c' <- responseJsonError =<< getClient brig uid (clientId c) Http () testMissingClient brig = do diff --git a/services/brig/test/integration/API/User/Util.hs b/services/brig/test/integration/API/User/Util.hs index e5379f5f36e..04f4c3ae792 100644 --- a/services/brig/test/integration/API/User/Util.hs +++ b/services/brig/test/integration/API/User/Util.hs @@ -33,7 +33,7 @@ import qualified Cassandra as DB import qualified Codec.MIME.Type as MIME import Control.Lens (preview, (^?)) import Control.Monad.Catch (MonadCatch) -import Data.Aeson +import Data.Aeson hiding (json) import Data.Aeson.Lens import Data.ByteString.Builder (toLazyByteString) import Data.ByteString.Char8 (pack) @@ -66,6 +66,7 @@ import Wire.API.Routes.MultiTablePaging (LocalOrRemoteTable, MultiTablePagingSta import Wire.API.Team.Feature (featureNameBS) import qualified Wire.API.Team.Feature as Public import qualified Wire.API.User as Public +import Wire.API.User.Client hiding (UpdateClient) newtype ConnectionLimit = ConnectionLimit Int64 @@ -231,6 +232,20 @@ getClient brig u c = . paths ["clients", toByteString' c] . zUser u +putClient :: + (MonadIO m, MonadHttp m, HasCallStack) => + Brig -> + UserId -> + ClientId -> + MLSPublicKeys -> + m ResponseLBS +putClient brig uid c keys = + put $ + brig + . paths ["clients", toByteString' c] + . zUser uid + . json (UpdateClient [] Nothing Nothing Nothing keys) + getClientCapabilities :: Brig -> UserId -> ClientId -> (MonadIO m, MonadHttp m) => m ResponseLBS getClientCapabilities brig u c = get $