Skip to content

feat: add access group and keychain synchronization #378

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
Aug 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ coverage:
status:
patch:
default:
target: 75
target: auto
changes: false
project:
default:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
__New features__
- Add methods for migrating users and installations from the Parse Objective-C SDK to the Swift SDK ([#391](https://github.com/parse-community/Parse-Swift/pull/391)), thanks to [Corey Baker](https://github.com/cbaker6).
- Enable query caching by using GET instead of POST. GET is now used by default. To switch back to POST, set usingPostForQuery = true when initializing the SDK which will automatically disable all query caching ([#386](https://github.com/parse-community/Parse-Swift/pull/386)), thanks to [Corey Baker](https://github.com/cbaker6).
- Add setAccessGroup method which allows the Parse Keychain to be shared with app extensions and iCloud accounts ([#378](https://github.com/parse-community/Parse-Swift/pull/378)), thanks to [Corey Baker](https://github.com/cbaker6).

__Improvements__
- Add more details to error messages related when decoding errors occur ([#388](https://github.com/parse-community/Parse-Swift/pull/388)), thanks to [Daniel Blyth](https://github.com/dblythy).
Expand Down
18 changes: 18 additions & 0 deletions ParseSwift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,9 @@
7085DDB326D1EC7F0033B977 /* ParseAuthenticationCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7085DDB226D1EC7F0033B977 /* ParseAuthenticationCombineTests.swift */; };
7085DDB426D1EC7F0033B977 /* ParseAuthenticationCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7085DDB226D1EC7F0033B977 /* ParseAuthenticationCombineTests.swift */; };
7085DDB526D1EC7F0033B977 /* ParseAuthenticationCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7085DDB226D1EC7F0033B977 /* ParseAuthenticationCombineTests.swift */; };
708CADCF2872263D0066C279 /* ParseKeychainAccessGroupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 708CADCE2872263D0066C279 /* ParseKeychainAccessGroupTests.swift */; };
708CADD02872263D0066C279 /* ParseKeychainAccessGroupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 708CADCE2872263D0066C279 /* ParseKeychainAccessGroupTests.swift */; };
708CADD12872263D0066C279 /* ParseKeychainAccessGroupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 708CADCE2872263D0066C279 /* ParseKeychainAccessGroupTests.swift */; };
708D035225215F9B00646C70 /* Deletable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 708D035125215F9B00646C70 /* Deletable.swift */; };
708D035325215F9B00646C70 /* Deletable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 708D035125215F9B00646C70 /* Deletable.swift */; };
708D035425215F9B00646C70 /* Deletable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 708D035125215F9B00646C70 /* Deletable.swift */; };
Expand Down Expand Up @@ -638,6 +641,10 @@
70D41D6B28B294C100613510 /* MigrateObjCSDKCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70D41D6A28B294C100613510 /* MigrateObjCSDKCombineTests.swift */; };
70D41D6C28B294C100613510 /* MigrateObjCSDKCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70D41D6A28B294C100613510 /* MigrateObjCSDKCombineTests.swift */; };
70D41D6D28B294C100613510 /* MigrateObjCSDKCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70D41D6A28B294C100613510 /* MigrateObjCSDKCombineTests.swift */; };
70D41D8028B520E200613510 /* ParseKeychainAccessGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70D41D7F28B520E200613510 /* ParseKeychainAccessGroup.swift */; };
70D41D8128B520E200613510 /* ParseKeychainAccessGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70D41D7F28B520E200613510 /* ParseKeychainAccessGroup.swift */; };
70D41D8228B520E200613510 /* ParseKeychainAccessGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70D41D7F28B520E200613510 /* ParseKeychainAccessGroup.swift */; };
70D41D8328B520E200613510 /* ParseKeychainAccessGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70D41D7F28B520E200613510 /* ParseKeychainAccessGroup.swift */; };
70DFEA8A2618E77800F8EB4B /* InitializeSDKTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70DFEA892618E77800F8EB4B /* InitializeSDKTests.swift */; };
70DFEA8B2618E77800F8EB4B /* InitializeSDKTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70DFEA892618E77800F8EB4B /* InitializeSDKTests.swift */; };
70DFEA8C2618E77800F8EB4B /* InitializeSDKTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70DFEA892618E77800F8EB4B /* InitializeSDKTests.swift */; };
Expand Down Expand Up @@ -1257,6 +1264,7 @@
7085DD9326CBF3A70033B977 /* Documentation.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = Documentation.docc; sourceTree = "<group>"; };
7085DDA226CC8A470033B977 /* ParseHealth+combine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ParseHealth+combine.swift"; sourceTree = "<group>"; };
7085DDB226D1EC7F0033B977 /* ParseAuthenticationCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseAuthenticationCombineTests.swift; sourceTree = "<group>"; };
708CADCE2872263D0066C279 /* ParseKeychainAccessGroupTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseKeychainAccessGroupTests.swift; sourceTree = "<group>"; };
708D035125215F9B00646C70 /* Deletable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Deletable.swift; sourceTree = "<group>"; };
709A147C283949D100BF85E5 /* ParseSchema.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseSchema.swift; sourceTree = "<group>"; };
709A148128395ED100BF85E5 /* ParseSchema+async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseSchema+async.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1311,6 +1319,7 @@
70D1BE7225BB43EB00A42E7C /* BaseConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseConfig.swift; sourceTree = "<group>"; };
70D41D6628B0235100613510 /* MigrateObjCSDKTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateObjCSDKTests.swift; sourceTree = "<group>"; };
70D41D6A28B294C100613510 /* MigrateObjCSDKCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateObjCSDKCombineTests.swift; sourceTree = "<group>"; };
70D41D7F28B520E200613510 /* ParseKeychainAccessGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseKeychainAccessGroup.swift; sourceTree = "<group>"; };
70DFEA892618E77800F8EB4B /* InitializeSDKTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitializeSDKTests.swift; sourceTree = "<group>"; };
70E09E1B262F0634002DD451 /* ParsePointerCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParsePointerCombineTests.swift; sourceTree = "<group>"; };
70E6B015286120E00043EC4A /* ParseHookFunctionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseHookFunctionTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1619,6 +1628,7 @@
703B092626BE0719005A112F /* ParseInstallationAsyncTests.swift */,
7044C1BA25C52E410011F6E7 /* ParseInstallationCombineTests.swift */,
70110D5B2506ED0E0091CC1D /* ParseInstallationTests.swift */,
708CADCE2872263D0066C279 /* ParseKeychainAccessGroupTests.swift */,
917BA4552703F75E00F8D747 /* ParseLDAPAsyncTests.swift */,
70386A5B25D9A4010048EC1B /* ParseLDAPCombineTests.swift */,
70386A4525D99C8B0048EC1B /* ParseLDAPTests.swift */,
Expand Down Expand Up @@ -2115,6 +2125,7 @@
70385E7F2858EAA90084D306 /* ParseHookFunctionRequest.swift */,
70CE0A9328590A0A00DAEA86 /* ParseHookResponse.swift */,
70CE0AB1285963A300DAEA86 /* ParseHookTriggerRequest.swift */,
70D41D7F28B520E200613510 /* ParseKeychainAccessGroup.swift */,
F97B464024D9C78B00F4A88B /* ParseOperation.swift */,
703B091026BD992E005A112F /* ParseOperation+async.swift */,
7044C19E25C4FA870011F6E7 /* ParseOperation+combine.swift */,
Expand Down Expand Up @@ -2783,6 +2794,7 @@
7045769D26BD934000F86F71 /* ParseFile+async.swift in Sources */,
F97B463324D9C74400F4A88B /* URLSession.swift in Sources */,
F97B464E24D9C78B00F4A88B /* Add.swift in Sources */,
70D41D8028B520E200613510 /* ParseKeychainAccessGroup.swift in Sources */,
70385E762858E1000084D306 /* ParseHookFunctionable.swift in Sources */,
703B095326BF47FD005A112F /* ParseTwitter+async.swift in Sources */,
70CE0ABC285F8FF900DAEA86 /* ParseTypeable.swift in Sources */,
Expand Down Expand Up @@ -2943,6 +2955,7 @@
911DB13324C494390027F3C7 /* MockURLProtocol.swift in Sources */,
917BA44A2703F10400F8D747 /* ParseAppleAsyncTests.swift in Sources */,
705025A5284407C4008D6624 /* ParseSchemaTests.swift in Sources */,
708CADCF2872263D0066C279 /* ParseKeychainAccessGroupTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -3089,6 +3102,7 @@
7045769E26BD934000F86F71 /* ParseFile+async.swift in Sources */,
F97B463424D9C74400F4A88B /* URLSession.swift in Sources */,
F97B464F24D9C78B00F4A88B /* Add.swift in Sources */,
70D41D8128B520E200613510 /* ParseKeychainAccessGroup.swift in Sources */,
70385E772858E1000084D306 /* ParseHookFunctionable.swift in Sources */,
703B095426BF47FD005A112F /* ParseTwitter+async.swift in Sources */,
70CE0ABD285F8FF900DAEA86 /* ParseTypeable.swift in Sources */,
Expand Down Expand Up @@ -3258,6 +3272,7 @@
709B98542556ECAA00507778 /* ParseInstallationTests.swift in Sources */,
917BA44C2703F10400F8D747 /* ParseAppleAsyncTests.swift in Sources */,
705025A7284407C4008D6624 /* ParseSchemaTests.swift in Sources */,
708CADD12872263D0066C279 /* ParseKeychainAccessGroupTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -3380,6 +3395,7 @@
70F2E2B9254F283000B2EA5C /* KeychainStoreTests.swift in Sources */,
917BA44B2703F10400F8D747 /* ParseAppleAsyncTests.swift in Sources */,
705025A6284407C4008D6624 /* ParseSchemaTests.swift in Sources */,
708CADD02872263D0066C279 /* ParseKeychainAccessGroupTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -3526,6 +3542,7 @@
704576A026BD934000F86F71 /* ParseFile+async.swift in Sources */,
F97B464924D9C78B00F4A88B /* ParseOperation.swift in Sources */,
F97B45D124D9C6F200F4A88B /* ParseCoding.swift in Sources */,
70D41D8328B520E200613510 /* ParseKeychainAccessGroup.swift in Sources */,
70385E792858E1000084D306 /* ParseHookFunctionable.swift in Sources */,
703B095626BF47FD005A112F /* ParseTwitter+async.swift in Sources */,
70CE0ABF285F8FF900DAEA86 /* ParseTypeable.swift in Sources */,
Expand Down Expand Up @@ -3710,6 +3727,7 @@
7045769F26BD934000F86F71 /* ParseFile+async.swift in Sources */,
F97B464824D9C78B00F4A88B /* ParseOperation.swift in Sources */,
F97B45D024D9C6F200F4A88B /* ParseCoding.swift in Sources */,
70D41D8228B520E200613510 /* ParseKeychainAccessGroup.swift in Sources */,
70385E782858E1000084D306 /* ParseHookFunctionable.swift in Sources */,
703B095526BF47FD005A112F /* ParseTwitter+async.swift in Sources */,
70CE0ABE285F8FF900DAEA86 /* ParseTypeable.swift in Sources */,
Expand Down
4 changes: 2 additions & 2 deletions Sources/ParseSwift/Objects/ParseInstallation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public extension ParseInstallation {
}

func mergeParse(with object: Self) throws -> Self {
guard hasSameObjectId(as: object) == true else {
guard hasSameObjectId(as: object) else {
throw ParseError(code: .unknownError,
message: "objectId's of objects do not match")
}
Expand Down Expand Up @@ -193,7 +193,7 @@ extension ParseInstallation {
}

// MARK: CurrentInstallationContainer
struct CurrentInstallationContainer<T: ParseInstallation>: Codable {
struct CurrentInstallationContainer<T: ParseInstallation>: Codable, Hashable {
var currentInstallation: T?
var installationId: String?
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/ParseSwift/Objects/ParseObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ public extension ParseObject {
}

func mergeParse(with object: Self) throws -> Self {
guard hasSameObjectId(as: object) == true else {
guard hasSameObjectId(as: object) else {
throw ParseError(code: .unknownError,
message: "objectId's of objects do not match")
}
Expand Down
14 changes: 8 additions & 6 deletions Sources/ParseSwift/Objects/ParseUser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public extension ParseUser {
}

func mergeParse(with object: Self) throws -> Self {
guard hasSameObjectId(as: object) == true else {
guard hasSameObjectId(as: object) else {
throw ParseError(code: .unknownError,
message: "objectId's of objects do not match")
}
Expand Down Expand Up @@ -101,7 +101,7 @@ extension ParseUser {
}

// MARK: CurrentUserContainer
struct CurrentUserContainer<T: ParseUser>: Codable {
struct CurrentUserContainer<T: ParseUser>: Codable, Hashable {
var currentUser: T?
var sessionToken: String?
}
Expand Down Expand Up @@ -399,7 +399,9 @@ extension ParseUser {
}
return
}
completion(.success(currentUser))
callbackQueue.async {
completion(.success(currentUser))
}
}
#endif

Expand Down Expand Up @@ -439,9 +441,9 @@ extension ParseUser {
var options = options
options.insert(.cachePolicy(.reloadIgnoringLocalCacheData))
let error = try? logoutCommand().execute(options: options)
//Always let user logout locally, no matter the error.
// Always let user logout locally, no matter the error.
deleteCurrentKeychain()
//Wait to throw error
// Wait to throw error
if let parseError = error {
throw parseError
}
Expand All @@ -465,7 +467,7 @@ extension ParseUser {
options.insert(.cachePolicy(.reloadIgnoringLocalCacheData))
logoutCommand().executeAsync(options: options,
callbackQueue: callbackQueue) { result in
//Always let user logout locally, no matter the error.
// Always let user logout locally, no matter the error.
deleteCurrentKeychain()

switch result {
Expand Down
63 changes: 61 additions & 2 deletions Sources/ParseSwift/Parse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,10 @@ public struct ParseConfiguration {
(URLSession.AuthChallengeDisposition,
URLCredential?) -> Void) -> Void)?
internal var mountPath: String
internal var isTestingSDK = false //Enable this only for certain tests like ParseFile
internal var isTestingSDK = false // Enable this only for certain tests like ParseFile
#if !os(Linux) && !os(Android) && !os(Windows)
internal var keychainAccessGroup = ParseKeychainAccessGroup()
#endif

/**
Create a Parse Swift configuration.
Expand Down Expand Up @@ -266,6 +269,14 @@ public struct ParseSwift {
authentication: configuration.authentication)
deleteKeychainIfNeeded()

#if !os(Linux) && !os(Android) && !os(Windows)
if let keychainAccessGroup = ParseKeychainAccessGroup.current {
ParseSwift.configuration.keychainAccessGroup = keychainAccessGroup
} else {
ParseKeychainAccessGroup.current = ParseKeychainAccessGroup()
}
#endif

do {
let previousSDKVersion = try ParseVersion(ParseVersion.current)
let currentSDKVersion = try ParseVersion(ParseConstants.version)
Expand All @@ -276,7 +287,9 @@ public struct ParseSwift {
if previousSDKVersion < oneNineEightSDKVersion {
// Old macOS Keychain cannot be used because it is global to all apps.
_ = KeychainStore.old
KeychainStore.shared.copy(keychain: KeychainStore.old)
try? KeychainStore.shared.copy(KeychainStore.old,
oldAccessGroup: configuration.keychainAccessGroup,
newAccessGroup: configuration.keychainAccessGroup)
// Need to delete the old Keychain because a new one is created with bundleId.
try? KeychainStore.old.deleteAll()
}
Expand Down Expand Up @@ -537,6 +550,51 @@ public struct ParseSwift {
initialize(configuration: configuration)
}

#if !os(Linux) && !os(Android) && !os(Windows)

/**
Sets all of the items in the Parse Keychain to a specific access group.
Apps in the same access group can share Keychain items. See Apple's
[documentation](https://developer.apple.com/documentation/security/ksecattraccessgroup)
for more information.
- parameter accessGroup: The name of the access group.
- parameter synchronizeAcrossDevices: **true** to synchronize all necessary Parse Keychain items to
other devices using iCloud. See Apple's [documentation](https://developer.apple.com/documentation/security/ksecattrsynchronizable)
for more information. **false** to disable synchronization.
- throws: An error of type `ParseError`.
- returns: **true** if the Keychain was moved to the new `accessGroup`, **false** otherwise.
- warning: Setting `synchronizeAcrossDevices == true` requires `accessGroup` to be
set to a valid [keychain group](https://developer.apple.com/documentation/security/ksecattraccessgroup).
*/
@discardableResult static public func setAccessGroup(_ accessGroup: String?,
synchronizeAcrossDevices: Bool) throws -> Bool {
if synchronizeAcrossDevices && accessGroup == nil {
throw ParseError(code: .unknownError,
message: "\"accessGroup\" must be set to a valid string when \"synchronizeAcrossDevices == true\"")
}
guard let currentAccessGroup = ParseKeychainAccessGroup.current else {
throw ParseError(code: .unknownError,
message: "Problem unwrapping the current access group. Did you initialize the SDK before calling this method?")
}
let newKeychainAccessGroup = ParseKeychainAccessGroup(accessGroup: accessGroup,
isSyncingKeychainAcrossDevices: synchronizeAcrossDevices)
guard newKeychainAccessGroup != currentAccessGroup else {
ParseKeychainAccessGroup.current = newKeychainAccessGroup
return true
}
do {
try KeychainStore.shared.copy(KeychainStore.shared,
oldAccessGroup: currentAccessGroup,
newAccessGroup: newKeychainAccessGroup)
ParseKeychainAccessGroup.current = newKeychainAccessGroup
} catch {
ParseKeychainAccessGroup.current = currentAccessGroup
throw error
}
return true
}
#endif

static internal func deleteKeychainIfNeeded() {
#if !os(Linux) && !os(Android) && !os(Windows)
// Clear items out of the Keychain on app first run.
Expand All @@ -545,6 +603,7 @@ public struct ParseSwift {
try? KeychainStore.old.deleteAll()
try? KeychainStore.shared.deleteAll()
}
ParseSwift.configuration.keychainAccessGroup = .init()
clearCache()
// This is no longer the first run
UserDefaults.standard.setValue(String(ParseConstants.bundlePrefix),
Expand Down
2 changes: 1 addition & 1 deletion Sources/ParseSwift/ParseConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation

enum ParseConstants {
static let sdk = "swift"
static let version = "4.8.0"
static let version = "4.9.0"
static let fileManagementDirectory = "parse/"
static let fileManagementPrivateDocumentsDirectory = "Private Documents/"
static let fileManagementLibraryDirectory = "Library/"
Expand Down
2 changes: 1 addition & 1 deletion Sources/ParseSwift/Protocols/ParseHookRequestable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ extension ParseHookRequestable {
public func options() -> API.Options {
var options = API.Options()
if let masterKey = masterKey,
masterKey == true {
masterKey {
options.insert(.useMasterKey)
} else if let sessionToken = user?.sessionToken {
options.insert(.sessionToken(sessionToken))
Expand Down
Loading