From 31a8ba4d1a18baae34c9f4a5f08e41cc257e1e1d Mon Sep 17 00:00:00 2001 From: Andy Boedo Date: Wed, 8 May 2024 18:46:14 -0300 Subject: [PATCH 1/4] updated the checks for the receipt signature so that they only run if we can't verify the environment from the receipt itself --- Sources/Misc/SandboxEnvironmentDetector.swift | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Sources/Misc/SandboxEnvironmentDetector.swift b/Sources/Misc/SandboxEnvironmentDetector.swift index d96345e335..b228e51cf2 100644 --- a/Sources/Misc/SandboxEnvironmentDetector.swift +++ b/Sources/Misc/SandboxEnvironmentDetector.swift @@ -50,7 +50,14 @@ final class BundleSandboxEnvironmentDetector: SandboxEnvironmentDetector { } #if os(macOS) || targetEnvironment(macCatalyst) - return !self.isProductionReceipt || !self.isMacAppStore + // this relies on an undocumented field in the receipt that provides the Environment. + // if it's not present, we go to a secondary check. + if let isProductionReceipt = self.isProductionReceipt { + return !isProductionReceipt + } else { + return !self.isMacAppStore + } + #else return path.contains("sandboxReceipt") #endif @@ -73,12 +80,12 @@ extension BundleSandboxEnvironmentDetector: Sendable {} private extension BundleSandboxEnvironmentDetector { - var isProductionReceipt: Bool { + var isProductionReceipt: Bool? { do { return try self.receiptFetcher.fetchAndParseLocalReceipt().environment == .production } catch { Logger.error(Strings.receipt.parse_receipt_locally_error(error: error)) - return false + return nil } } From 9983c685f4062ba51f927d4a766c1ff5cc2f4c3c Mon Sep 17 00:00:00 2001 From: Andy Boedo Date: Thu, 22 Aug 2024 19:13:39 -0300 Subject: [PATCH 2/4] added extra consideration for unknown environment --- Sources/Misc/SandboxEnvironmentDetector.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/Misc/SandboxEnvironmentDetector.swift b/Sources/Misc/SandboxEnvironmentDetector.swift index b228e51cf2..bdf101d409 100644 --- a/Sources/Misc/SandboxEnvironmentDetector.swift +++ b/Sources/Misc/SandboxEnvironmentDetector.swift @@ -82,7 +82,9 @@ private extension BundleSandboxEnvironmentDetector { var isProductionReceipt: Bool? { do { - return try self.receiptFetcher.fetchAndParseLocalReceipt().environment == .production + let receiptEnvironment = try self.receiptFetcher.fetchAndParseLocalReceipt().environment + guard receiptEnvironment != .unknown else { return nil } // don't make assumptions if we're not sure + return receiptEnvironment == .production } catch { Logger.error(Strings.receipt.parse_receipt_locally_error(error: error)) return nil From 078a16d1a4bf14454f03739cca1e172d08307602 Mon Sep 17 00:00:00 2001 From: Andy Boedo Date: Thu, 22 Aug 2024 19:39:36 -0300 Subject: [PATCH 3/4] added tests --- .../SandboxEnvironmentDetectorTests.swift | 76 +++++++++++++++++-- 1 file changed, 71 insertions(+), 5 deletions(-) diff --git a/Tests/UnitTests/Misc/SandboxEnvironmentDetectorTests.swift b/Tests/UnitTests/Misc/SandboxEnvironmentDetectorTests.swift index b78ca0f95a..c74c560555 100644 --- a/Tests/UnitTests/Misc/SandboxEnvironmentDetectorTests.swift +++ b/Tests/UnitTests/Misc/SandboxEnvironmentDetectorTests.swift @@ -91,6 +91,66 @@ class SandboxEnvironmentDetectorTests: TestCase { ) == true } + func testIsSandboxWhenReceiptEnvironmentIsUnknownDefaultToMacAppStoreDetector() throws { + let macAppStore = false + var isSandbox = false + let macAppStoreDetector = MockMacAppStoreDetector(isMacAppStore: macAppStore) + let detector = SystemInfo.with( + macAppStore: macAppStore, + receiptEnvironment: .unknown, + macAppStoreDetector: macAppStoreDetector + ) + + expect(detector.isSandbox) == isSandbox + expect(macAppStoreDetector.isMacAppStoreCalled) == true + + isSandbox = !isSandbox + + expect(detector.isSandbox) == isSandbox + } + + func testIsSandboxWhenReceiptIsProductionReturnsProductionAndDoesntHitMacAppStoreDetector() throws { + let macAppStoreDetector = MockMacAppStoreDetector(isMacAppStore: false) + let detector = SystemInfo.with( + macAppStore: false, + receiptEnvironment: .production, + macAppStoreDetector: macAppStoreDetector + ) + + expect(detector.isSandbox) == false + expect(macAppStoreDetector.isMacAppStoreCalled) == false + } + + func testIsSandboxWhenReceiptIsSandboxReturnsSandboxAndDoesntHitMacAppStoreDetector() throws { + let macAppStoreDetector = MockMacAppStoreDetector(isMacAppStore: false) + let detector = SystemInfo.with( + macAppStore: false, + receiptEnvironment: .sandbox, + macAppStoreDetector: macAppStoreDetector + ) + + expect(detector.isSandbox) == true + expect(macAppStoreDetector.isMacAppStoreCalled) == false + } + + func testIsSandboxIfReceiptParsingFailsAndNotInMacAppStore() throws { + let macAppStore = false + var isSandbox = false + let macAppStoreDetector = MockMacAppStoreDetector(isMacAppStore: macAppStore) + let detector = SystemInfo.with( + macAppStore: macAppStore, + failReceiptParsing: true, + macAppStoreDetector: macAppStoreDetector + ) + + expect(detector.isSandbox) == isSandbox + expect(macAppStoreDetector.isMacAppStoreCalled) == true + + isSandbox = !isSandbox + + expect(detector.isSandbox) == isSandbox + } + } #endif @@ -104,7 +164,8 @@ private extension SandboxEnvironmentDetector { inSimulator: Bool = false, macAppStore: Bool = false, receiptEnvironment: AppleReceipt.Environment = .production, - failReceiptParsing: Bool = false + failReceiptParsing: Bool = false, + macAppStoreDetector: MockMacAppStoreDetector? = nil ) -> SandboxEnvironmentDetector { let bundle = MockBundle() bundle.receiptURLResult = result @@ -126,7 +187,7 @@ private extension SandboxEnvironmentDetector { isRunningInSimulator: inSimulator, receiptFetcher: MockLocalReceiptFetcher(mockReceipt: mockReceipt, failReceiptParsing: failReceiptParsing), - macAppStoreDetector: MockMacAppStoreDetector(isMacAppStore: macAppStore) + macAppStoreDetector: macAppStoreDetector ?? MockMacAppStoreDetector(isMacAppStore: macAppStore) ) } @@ -151,12 +212,17 @@ private final class MockLocalReceiptFetcher: LocalReceiptFetcherType { } -private struct MockMacAppStoreDetector: MacAppStoreDetector { +private final class MockMacAppStoreDetector: MacAppStoreDetector, @unchecked Sendable { - let isMacAppStore: Bool + let isMacAppStoreValue: Bool + private(set) var isMacAppStoreCalled = false init(isMacAppStore: Bool) { - self.isMacAppStore = isMacAppStore + self.isMacAppStoreValue = isMacAppStore } + var isMacAppStore: Bool { + isMacAppStoreCalled = true + return isMacAppStoreValue + } } From 5908a452102c80b1ade6167cdc27f403256c2f44 Mon Sep 17 00:00:00 2001 From: Andy Boedo Date: Thu, 22 Aug 2024 19:46:49 -0300 Subject: [PATCH 4/4] updated tests --- .../SandboxEnvironmentDetectorTests.swift | 86 ++++++++----------- 1 file changed, 35 insertions(+), 51 deletions(-) diff --git a/Tests/UnitTests/Misc/SandboxEnvironmentDetectorTests.swift b/Tests/UnitTests/Misc/SandboxEnvironmentDetectorTests.swift index c74c560555..3934e82307 100644 --- a/Tests/UnitTests/Misc/SandboxEnvironmentDetectorTests.swift +++ b/Tests/UnitTests/Misc/SandboxEnvironmentDetectorTests.swift @@ -45,7 +45,7 @@ class SandboxEnvironmentDetectorTests: TestCase { // `macOS` sandbox detection does not rely on receipt path class SandboxEnvironmentDetectorTests: TestCase { - func testIsNotSandboxIfReceiptIsProductionAndMAS() throws { + func testIsNotSandboxIfReceiptIsProduction() throws { expect( SystemInfo.with( macAppStore: true, @@ -54,16 +54,7 @@ class SandboxEnvironmentDetectorTests: TestCase { ) == false } - func testIsSandboxIfReceiptIsProductionAndNotMAS() throws { - expect( - SystemInfo.with( - macAppStore: false, - receiptEnvironment: .production - ).isSandbox - ) == true - } - - func testIsSandboxIfReceiptIsNotProductionAndNotMAS() throws { + func testIsSandboxIfReceiptIsNotProduction() throws { expect( SystemInfo.with( macAppStore: false, @@ -72,32 +63,36 @@ class SandboxEnvironmentDetectorTests: TestCase { ) == true } - func testIsSandboxIfReceiptIsNotProductionAndMAS() throws { - expect( - SystemInfo.with( - macAppStore: true, - receiptEnvironment: .sandbox - ).isSandbox - ) == true - } + func testIsSandboxWhenReceiptEnvironmentIsUnknownDefaultToMacAppStoreDetector() throws { + var isSandbox = false + var macAppStoreDetector = MockMacAppStoreDetector(isMacAppStore: !isSandbox) + var detector = SystemInfo.with( + macAppStore: !isSandbox, + receiptEnvironment: .unknown, + macAppStoreDetector: macAppStoreDetector + ) - func testIsSandboxIfReceiptParsingFailsAndBundleSignatureIsNotMAS() throws { - expect( - SystemInfo.with( - macAppStore: false, - receiptEnvironment: .production, - failReceiptParsing: true - ).isSandbox - ) == true + expect(detector.isSandbox) == isSandbox + expect(macAppStoreDetector.isMacAppStoreCalled) == true + + isSandbox = !isSandbox + + macAppStoreDetector = MockMacAppStoreDetector(isMacAppStore: !isSandbox) + detector = SystemInfo.with( + macAppStore: !isSandbox, + receiptEnvironment: .unknown, + macAppStoreDetector: macAppStoreDetector + ) + + expect(detector.isSandbox) == isSandbox } - func testIsSandboxWhenReceiptEnvironmentIsUnknownDefaultToMacAppStoreDetector() throws { - let macAppStore = false + func testIsSandboxWhenReceiptParsingFailsDefaultsToMacAppStoreDetector() throws { var isSandbox = false - let macAppStoreDetector = MockMacAppStoreDetector(isMacAppStore: macAppStore) - let detector = SystemInfo.with( - macAppStore: macAppStore, - receiptEnvironment: .unknown, + var macAppStoreDetector = MockMacAppStoreDetector(isMacAppStore: !isSandbox) + var detector = SystemInfo.with( + macAppStore: !isSandbox, + failReceiptParsing: true, macAppStoreDetector: macAppStoreDetector ) @@ -106,6 +101,13 @@ class SandboxEnvironmentDetectorTests: TestCase { isSandbox = !isSandbox + macAppStoreDetector = MockMacAppStoreDetector(isMacAppStore: !isSandbox) + detector = SystemInfo.with( + macAppStore: !isSandbox, + failReceiptParsing: true, + macAppStoreDetector: macAppStoreDetector + ) + expect(detector.isSandbox) == isSandbox } @@ -133,24 +135,6 @@ class SandboxEnvironmentDetectorTests: TestCase { expect(macAppStoreDetector.isMacAppStoreCalled) == false } - func testIsSandboxIfReceiptParsingFailsAndNotInMacAppStore() throws { - let macAppStore = false - var isSandbox = false - let macAppStoreDetector = MockMacAppStoreDetector(isMacAppStore: macAppStore) - let detector = SystemInfo.with( - macAppStore: macAppStore, - failReceiptParsing: true, - macAppStoreDetector: macAppStoreDetector - ) - - expect(detector.isSandbox) == isSandbox - expect(macAppStoreDetector.isMacAppStoreCalled) == true - - isSandbox = !isSandbox - - expect(detector.isSandbox) == isSandbox - } - } #endif