Skip to content

Commit

Permalink
fix: ensure event data is sandboxed per app for all platforms (#113)
Browse files Browse the repository at this point in the history
* fix: ensure event data is sandboxed per app for all platforms

* chore: add SandboxHelper tests

* chore: add Persistent storage test for macOS

* chore: revert back to previous storage locations until we have a migration

* chore: code clean up, efficiency improvements
  • Loading branch information
justin-fiedler authored Feb 5, 2024
1 parent 2aab265 commit 72da9f8
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 6 deletions.
12 changes: 10 additions & 2 deletions Amplitude-Swift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@
BA994B9D2A4F4FCB00D0913F /* legacy_v4.sqlite in Resources */ = {isa = PBXBuildFile; fileRef = BA994B9B2A4F4B7500D0913F /* legacy_v4.sqlite */; };
BA9BEA4B299FB43B00BC0F7C /* IdentifyInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA9BEA4A299FB43B00BC0F7C /* IdentifyInterceptor.swift */; };
BA9BEA4D299FB4BB00BC0F7C /* IdentifyInterceptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA9BEA4C299FB4BB00BC0F7C /* IdentifyInterceptorTests.swift */; };
D010435F2B6C59EE00F8173C /* SandboxHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D010435E2B6C59EE00F8173C /* SandboxHelper.swift */; };
D01043612B6C5A8500F8173C /* SandboxHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01043602B6C5A8500F8173C /* SandboxHelperTests.swift */; };
OBJ_100 /* Mediator.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_21 /* Mediator.swift */; };
OBJ_101 /* AmplitudeDestinationPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_23 /* AmplitudeDestinationPlugin.swift */; };
OBJ_102 /* ContextPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_24 /* ContextPlugin.swift */; };
Expand Down Expand Up @@ -123,14 +125,14 @@
isa = PBXContainerItemProxy;
containerPortal = OBJ_1 /* Project object */;
proxyType = 1;
remoteGlobalIDString = amplitude-swift::Amplitude-Swift;
remoteGlobalIDString = "amplitude-swift::Amplitude-Swift";
remoteInfo = "Amplitude-Swift";
};
580FD1F1294A56F60036777B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = OBJ_1 /* Project object */;
proxyType = 1;
remoteGlobalIDString = amplitude-swift::Amplitude-SwiftTests;
remoteGlobalIDString = "amplitude-swift::Amplitude-SwiftTests";
remoteInfo = "Amplitude-SwiftTests";
};
/* End PBXContainerItemProxy section */
Expand Down Expand Up @@ -176,6 +178,8 @@
BA994B9B2A4F4B7500D0913F /* legacy_v4.sqlite */ = {isa = PBXFileReference; lastKnownFileType = file; path = legacy_v4.sqlite; sourceTree = "<group>"; };
BA9BEA4A299FB43B00BC0F7C /* IdentifyInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentifyInterceptor.swift; sourceTree = "<group>"; };
BA9BEA4C299FB4BB00BC0F7C /* IdentifyInterceptorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentifyInterceptorTests.swift; sourceTree = "<group>"; };
D010435E2B6C59EE00F8173C /* SandboxHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SandboxHelper.swift; sourceTree = "<group>"; };
D01043602B6C5A8500F8173C /* SandboxHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SandboxHelperTests.swift; sourceTree = "<group>"; };
OBJ_10 /* ConsoleLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleLogger.swift; sourceTree = "<group>"; };
OBJ_11 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
OBJ_12 /* EventBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBridge.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -394,6 +398,7 @@
OBJ_52 /* UrlExtension.swift */,
BA9BEA4A299FB43B00BC0F7C /* IdentifyInterceptor.swift */,
8EDEC54AB4DF9E1074C3D6A4 /* Weak.swift */,
D010435E2B6C59EE00F8173C /* SandboxHelper.swift */,
);
path = Utilities;
sourceTree = "<group>";
Expand Down Expand Up @@ -499,6 +504,7 @@
OBJ_74 /* UrlExtensionTests.swift */,
BA9BEA4C299FB4BB00BC0F7C /* IdentifyInterceptorTests.swift */,
8EDEC4F83BFAA664749FAEF0 /* QueueTimeTests.swift */,
D01043602B6C5A8500F8173C /* SandboxHelperTests.swift */,
);
path = Utilities;
sourceTree = "<group>";
Expand Down Expand Up @@ -640,6 +646,7 @@
OBJ_154 /* TypesTests.swift in Sources */,
OBJ_155 /* EventPipelineTests.swift in Sources */,
OBJ_156 /* HttpClientTests.swift in Sources */,
D01043612B6C5A8500F8173C /* SandboxHelperTests.swift in Sources */,
OBJ_157 /* PersistentStorageResponseHandlerTests.swift in Sources */,
OBJ_158 /* UrlExtensionTests.swift in Sources */,
8EDEC4EE0DE1C89889F451B5 /* QueueTimeTests.swift in Sources */,
Expand Down Expand Up @@ -698,6 +705,7 @@
8EDEC8F8DD2CDCD6568512F8 /* RemnantDataMigration.swift in Sources */,
8EDEC977C03AA2676724F436 /* BasePlugins.swift in Sources */,
8EDEC1073A308B12B5CCD975 /* AnalyticsConnectorPlugin.swift in Sources */,
D010435F2B6C59EE00F8173C /* SandboxHelper.swift in Sources */,
8EDEC3283B812D5D34DADF7B /* AnalyticsConnectorIdentityPlugin.swift in Sources */,
8EDEC4D0C0CE07BF211804CC /* DefaultTrackingOptions.swift in Sources */,
8EDEC30C0075E9D92B1B5210 /* UIKitScreenViews.swift in Sources */,
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 10 additions & 3 deletions Sources/Amplitude/Storages/PersistentStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@ class PersistentStorage: Storage {
let fileManager: FileManager
private var outputStream: OutputFileStream?
internal weak var amplitude: Amplitude?

// Store event.callback in memory as it cannot be ser/deser in files.
private var eventCallbackMap: [String: EventCallback]

private var appPath: String!
let syncQueue = DispatchQueue(label: "syncPersistentStorage.amplitude.com")

init(storagePrefix: String) {
Expand All @@ -28,6 +27,8 @@ class PersistentStorage: Storage {
self.userDefaults = UserDefaults(suiteName: "\(PersistentStorage.AMP_STORAGE_PREFIX).\(self.storagePrefix)")
self.fileManager = FileManager.default
self.eventCallbackMap = [String: EventCallback]()
// Make sure Amplitude data is sandboxed per app
self.appPath = isStorageSandboxed() ? "" : "\(Bundle.main.bundleIdentifier!)/"
}

func write(key: StorageKey, value: Any?) throws {
Expand Down Expand Up @@ -168,6 +169,10 @@ class PersistentStorage: Storage {
}
return result
}

internal func isStorageSandboxed() -> Bool {
return SandboxHelper().isSandboxEnabled()
}
}

extension PersistentStorage {
Expand Down Expand Up @@ -239,6 +244,8 @@ extension PersistentStorage {
}

internal func getEventsStorageDirectory(createDirectory: Bool = true) -> URL {
// TODO: Update to use applicationSupportDirectory for all platforms (this will require a migration)
// let searchPathDirectory = FileManager.SearchPathDirectory.applicationSupportDirectory
// tvOS doesn't have access to document
// macOS /Documents dir might be synced with iCloud
#if os(tvOS) || os(macOS)
Expand All @@ -249,7 +256,7 @@ extension PersistentStorage {

let urls = fileManager.urls(for: searchPathDirectory, in: .userDomainMask)
let docUrl = urls[0]
let storageUrl = docUrl.appendingPathComponent("amplitude/\(eventsFileKey)/")
let storageUrl = docUrl.appendingPathComponent("amplitude/\(appPath ?? "")\(eventsFileKey)/")
if createDirectory {
// try to create it, will fail if already exists.
// tvOS, watchOS regularly clear out data.
Expand Down
25 changes: 25 additions & 0 deletions Sources/Amplitude/Utilities/SandboxHelper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// SandboxHelper.swift
// Amplitude-Swift
//
// Created by Justin Fiedler on 2/1/24.
//

import Foundation

public class SandboxHelper {
internal func getEnvironment() -> [String: String] {
return ProcessInfo.processInfo.environment
}

public func isSandboxEnabled() -> Bool {
#if os(macOS)
// Check if macOS app has "App Sandbox" enabled
let environment = getEnvironment()
return environment["APP_SANDBOX_CONTAINER_ID"] != nil
#else
// Other platforms (iOS, tvOS, watchOs) are sandboxed by default
return true
#endif
}
}
24 changes: 24 additions & 0 deletions Tests/AmplitudeTests/Storages/PersistentStorageTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,28 @@ final class PersistentStorageTests: XCTestCase {
XCTAssertNotEqual(eventFiles?[0].pathExtension, PersistentStorage.TEMP_FILE_EXTENSION)
persistentStorage.reset()
}

#if os(macOS)
func testMacOsStorageDirectorySandboxedWhenAppSandboxDisabled() {
let persistentStorage = PersistentStorage(storagePrefix: "mac-instance")

let bundleId = Bundle.main.bundleIdentifier!
let storageUrl = persistentStorage.getEventsStorageDirectory(createDirectory: false)

XCTAssertEqual(persistentStorage.isStorageSandboxed(), false)
XCTAssertEqual(storageUrl.absoluteString.contains(bundleId), true)
persistentStorage.reset()
}

func testMacOsStorageDirectorySandboxedWhenAppSandboxEnabled() {
let persistentStorage = FakePersistentStorageAppSandboxEnabled(storagePrefix: "mac-app-sandbox-instance")

let bundleId = Bundle.main.bundleIdentifier!
let storageUrl = persistentStorage.getEventsStorageDirectory(createDirectory: false)

XCTAssertEqual(persistentStorage.isStorageSandboxed(), true)
XCTAssertEqual(storageUrl.absoluteString.contains(bundleId), false)
persistentStorage.reset()
}
#endif
}
12 changes: 12 additions & 0 deletions Tests/AmplitudeTests/Supports/TestUtilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -248,3 +248,15 @@ class TestIdentifyInterceptor: IdentifyInterceptor {
overridenIdentifyBatchIntervalMillis = identifyBatchIntervalMillis
}
}

class FakeSandboxHelperWithAppSandboxContainer: SandboxHelper {
override func getEnvironment() -> [String: String] {
return ["APP_SANDBOX_CONTAINER_ID": "test-container-id"]
}
}

class FakePersistentStorageAppSandboxEnabled: PersistentStorage {
override internal func isStorageSandboxed() -> Bool {
return true
}
}
34 changes: 34 additions & 0 deletions Tests/AmplitudeTests/Utilities/SandboxHelperTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// SandboxHelperTests.swift
// Amplitude-SwiftTests
//
// Created by Justin Fiedler on 2/1/24.
//

import XCTest

@testable import AmplitudeSwift

final class SandboxHelperTests: XCTestCase {

func testIsSandboxEnabled() {
let sandboxHelper = SandboxHelper()
let isSandboxed = sandboxHelper.isSandboxEnabled()

#if os(macOS)
XCTAssertEqual(isSandboxed, false)
#else
XCTAssertEqual(isSandboxed, true)
#endif
}

#if os(macOS)
func testIsSandboxEnabledWithMacOSAppSandbox() {
let sandboxHelper = FakeSandboxHelperWithAppSandboxContainer()

let isSandboxed = sandboxHelper.isSandboxEnabled()

XCTAssertEqual(isSandboxed, true)
}
#endif
}

0 comments on commit 72da9f8

Please sign in to comment.