mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-24 06:13:39 +00:00
core: don't reveal another profile when delivering a store purchase; bound store checks and restrict Play ids
This commit is contained in:
1 parent
a329f51620
commit
aa8e8271ae
8 files changed
+70
-17
No files matched your search
+16
-1
@@ -10,6 +10,7 @@
|
||||
module BadgeTests (badgeTests) where
|
||||
|
||||
import BadgeService.Service (badgeErrorRetryAfter, shownServiceRequest)
|
||||
import BadgeService.StoreReceipts (StoreReceipt (..), StoreRefusal (..), StoreVerifier (..), storeReceipt)
|
||||
import Control.Concurrent.STM (atomically)
|
||||
import Data.ByteString.Char8 (ByteString)
|
||||
import qualified Data.ByteString.Base64.URL as B64U
|
||||
@@ -34,7 +35,7 @@ import Simplex.Chat (defaultChatConfig)
|
||||
import Simplex.Chat.Controller (ChatError (..), ChatErrorType (..), badgeRetryInterval, chatErrorAgent)
|
||||
import Simplex.Chat.Library.Commands (badgeErrorRetry, badgeFailureTransient, badgeIssueFailure, badgeRetryAfter, badgeServiceErrorText, badgeStalledInterval, storeTransactionRef)
|
||||
import Simplex.Chat.PaymentService (ServicePayment (..))
|
||||
import Simplex.Chat.PaymentService.Types (InvoiceId (..))
|
||||
import Simplex.Chat.PaymentService.Types (InvoiceId (..), PaymentProvider (..))
|
||||
import Simplex.Chat.Store.Badges (StoreTransactionRef (..))
|
||||
import Simplex.Messaging.Agent.Protocol (AgentErrorType (..), AgentServiceError (..), SMPAgentError (..))
|
||||
import Simplex.Messaging.Agent.RetryInterval (RetryInterval (..), nextRetryDelay)
|
||||
@@ -104,6 +105,7 @@ badgeTests = do
|
||||
describe "store purchases" $ do
|
||||
it "keys a purchase by the store's transaction id, not by the evidence signed over it" testStoreTransactionRef
|
||||
it "shows a service request in the terminal as its type alone" testShownServiceRequest
|
||||
it "refuses a Play product id or token that could name another purchase, before any verifier" testGooglePathStrings
|
||||
|
||||
proofOf :: BadgeProof -> BBSProof
|
||||
proofOf (BadgeProof _ _ p _) = p
|
||||
@@ -856,6 +858,19 @@ testShownServiceRequest = do
|
||||
shown BSCPurchaseBadge {masterKey = mk, payment = SPApple {jws = "a.b.c"}, upgrade = Nothing} `shouldBe` typeOnly "purchaseBadge"
|
||||
shown BSCRedeemBadgeCode {masterKey = mk, code = "SB-00000-00000-00000-00001"} `shouldBe` typeOnly "redeemBadgeCode"
|
||||
|
||||
testGooglePathStrings :: IO ()
|
||||
testGooglePathStrings = do
|
||||
let calledVerifier = StoreVerifier {verifyApple = Nothing, verifyGoogle = Just $ \_ _ -> error "verifier called", verifyTimeout = 500000}
|
||||
refused productId token = case storeReceipt calledVerifier SPGoogle {productId, token} of
|
||||
Just (Left SRInvalid {}) -> True
|
||||
_ -> False
|
||||
validToken = "fake-play-token.AO-J1Oz9x2kqE7wYt3"
|
||||
mapM_ (\p -> refused p validToken `shouldBe` True) ["badge_legend_01/tokens/other?", "badge_legend_01?x", "badge_legend_01#x", "..", "../badge_legend_01", "Badge_legend_01", ""]
|
||||
mapM_ (\t -> refused "badge_supporter_01" t `shouldBe` True) ["a/b", "../x", "t?x", "t#x", "t x", ""]
|
||||
case storeReceipt calledVerifier SPGoogle {productId = "badge_supporter_01", token = validToken} of
|
||||
Just (Right StoreReceipt {provider}) -> provider `shouldBe` PPGoogle
|
||||
_ -> expectationFailure "a valid product id and token were refused"
|
||||
|
||||
testCredentialResponseJSON :: IO ()
|
||||
testCredentialResponseJSON = do
|
||||
Right (_, sk) <- bbsKeyGen
|
||||
|
||||
@@ -133,6 +133,7 @@ badgeServiceTests = do
|
||||
it "should refuse a store purchase while a badge is held, before anything is sent" testPurchaseWhileBadgeHeld
|
||||
it "should answer a receipt presented under a second profile as the profile that bought it" testPurchaseSameReceiptOtherProfile
|
||||
it "should deliver a purchase first presented under another profile to that profile" testPurchaseStrandedUnderOtherProfile
|
||||
it "should deliver a purchase to a hidden profile without naming it" testPurchaseDeliveredToHiddenProfile
|
||||
|
||||
badgeProfile :: Profile
|
||||
badgeProfile = Profile {displayName = "SimpleX Badges", fullName = "", shortDescr = Nothing, description = Nothing, image = Nothing, contactLink = Nothing, peerType = Just CPTBot, preferences = Nothing, badge = Nothing, contactDomain = Nothing}
|
||||
@@ -1660,7 +1661,7 @@ testPurchaseSameReceiptOtherProfile ps =
|
||||
showActiveUser alice "alisa"
|
||||
-- the store transaction is the device's, so it stays with the profile it was bought under
|
||||
alice ##> ("/_badge purchase 2 " <> paymentArg supporterPlay)
|
||||
alice <## "[user: alice] badge already redeemed"
|
||||
alice <## "badge purchase delivered to another profile"
|
||||
rowCount cc "sx_badge_service_badge_purchases" `shouldReturn` 1
|
||||
alice ##> "/p"
|
||||
showActiveUser alice "alisa"
|
||||
@@ -1677,10 +1678,28 @@ testPurchaseStrandedUnderOtherProfile ps =
|
||||
settlePending store
|
||||
-- presented again under whichever profile is active, the purchase reaches the keys alice stashed
|
||||
alice ##> unsettled 2
|
||||
alice <## "[user: alice] badge redeemed"
|
||||
alice <## "supporter badge - active"
|
||||
alice <##. "expires "
|
||||
alice <## "badge purchase delivered to another profile"
|
||||
rowCount (chatController alice) "badge_store_receipts" `shouldReturn` 1
|
||||
rowCount cc "sx_badge_service_badge_purchases" `shouldReturn` 1
|
||||
alice ##> "/user alice"
|
||||
showActiveUser alice "alice (Alice, * supporter)"
|
||||
|
||||
testPurchaseDeliveredToHiddenProfile :: HasCallStack => TestParams -> IO ()
|
||||
testPurchaseDeliveredToHiddenProfile ps =
|
||||
withBadgeServiceEnv ps $ \BadgeServiceEnv {bsClientCfg, bsStore = store} ->
|
||||
withNewTestChatCfg ps bsClientCfg "alice" aliceProfile $ \alice -> do
|
||||
let unsettled userId = "/_badge purchase " <> show (userId :: Int) <> " " <> paymentArg (googlePayment "badge_supporter_01" googlePendingToken)
|
||||
alice ##> unsettled 1
|
||||
alice <## "cannot redeem badge code: badge service error: payment_pending"
|
||||
alice ##> "/create user alisa"
|
||||
showActiveUser alice "alisa"
|
||||
alice ##> "/_hide user 1 \"password\""
|
||||
alice <## "user alice:"
|
||||
alice <## "messages are hidden (use /tail to view)"
|
||||
alice <## "profile is hidden"
|
||||
settlePending store
|
||||
-- the answer names only the presenting profile, and nothing printed names the hidden one
|
||||
alice ##> unsettled 2
|
||||
alice <## "badge purchase delivered to another profile"
|
||||
alice ##> "/user alice password"
|
||||
showActiveUser alice "alice (Alice, * supporter)"
|
||||
@@ -73,7 +73,7 @@ newFakeStore = do
|
||||
appleThrowingJWS,
|
||||
pendingSettled,
|
||||
googleDown,
|
||||
fakeVerifier = StoreVerifier {verifyApple = Just verifyApple, verifyGoogle = Just verifyGoogle}
|
||||
fakeVerifier = StoreVerifier {verifyApple = Just verifyApple, verifyGoogle = Just verifyGoogle, verifyTimeout = 500000}
|
||||
}
|
||||
where
|
||||
fixtureJWS name = unsignedJWS <$> B.readFile (fixtureDir </> name)
|
||||
|
||||
Reference in new issue
Block a user