diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 50343ef4a..3fc514cef 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,11 +6,10 @@ on: jobs: build: name: Build and test - runs-on: macos-14 + runs-on: [self-hosted, macOS, ARM64] env: DERIVED_DATA_PATH: 'DerivedData' DEVICE: 'iPhone 15 Pro' - if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')" strategy: matrix: config: ['freemium', 'premium'] @@ -32,8 +31,8 @@ jobs: run: | cd fastlane ./scripts/create-cloud-access-secrets.sh - - name: Select Xcode 15.2 - run: sudo xcode-select -s /Applications/Xcode_15.2.app + - name: Select Xcode 15.3 + run: sudo xcode-select -s /Applications/Xcode_15.3.app - name: Configuration for freemium if: ${{ matrix.config == 'freemium' }} run: | diff --git a/Cryptomator.xcodeproj/project.pbxproj b/Cryptomator.xcodeproj/project.pbxproj index f577662f6..91b4f6ac5 100644 --- a/Cryptomator.xcodeproj/project.pbxproj +++ b/Cryptomator.xcodeproj/project.pbxproj @@ -391,6 +391,10 @@ 740D3684266A1B180058744D /* SettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 740D3683266A1B180058744D /* SettingsCoordinator.swift */; }; 742679FC26A56CF9004C61BC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 742679F926A56B33004C61BC /* Localizable.strings */; }; 742679FD26A56CFA004C61BC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 742679F926A56B33004C61BC /* Localizable.strings */; }; + 74420BC32BD2449900E77F92 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 74420BC22BD2449900E77F92 /* PrivacyInfo.xcprivacy */; }; + 74420BC42BD2449900E77F92 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 74420BC22BD2449900E77F92 /* PrivacyInfo.xcprivacy */; }; + 74420BC52BD2449900E77F92 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 74420BC22BD2449900E77F92 /* PrivacyInfo.xcprivacy */; }; + 74420BC62BD2449900E77F92 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 74420BC22BD2449900E77F92 /* PrivacyInfo.xcprivacy */; }; 7460FFEF26FCC6FC0018BCC4 /* OnboardingNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7460FFEE26FCC6FC0018BCC4 /* OnboardingNavigationController.swift */; }; 746815462475605E00038679 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4AE97DB324572E4A00452814 /* Assets.xcassets */; }; 746815472475605E00038679 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4AE97DB524572E4A00452814 /* LaunchScreen.storyboard */; }; @@ -996,6 +1000,7 @@ 74275AE728478E160058AD25 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Intents.strings; sourceTree = ""; }; 74397A842832A05E00CB9410 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; 74397A852832A09B00CB9410 /* sw-TZ */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sw-TZ"; path = "sw-TZ.lproj/Localizable.strings"; sourceTree = ""; }; + 74420BC22BD2449900E77F92 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 7460FFED26FB6C100018BCC4 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Localizable.strings; sourceTree = ""; }; 7460FFEE26FCC6FC0018BCC4 /* OnboardingNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingNavigationController.swift; sourceTree = ""; }; 74626665283BD2D20070924B /* zh-HK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-HK"; path = "zh-HK.lproj/Localizable.strings"; sourceTree = ""; }; @@ -2024,6 +2029,7 @@ children = ( 4AE97DB324572E4A00452814 /* Assets.xcassets */, 4AF91CC625A6437000ACF01E /* Colors.xcassets */, + 74420BC22BD2449900E77F92 /* PrivacyInfo.xcprivacy */, 742679F926A56B33004C61BC /* Localizable.strings */, ); path = SharedResources; @@ -2380,6 +2386,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 74420BC42BD2449900E77F92 /* PrivacyInfo.xcprivacy in Resources */, 4A80407B2769201400D7D999 /* Localizable.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2390,6 +2397,7 @@ files = ( 4A6A5213268B66AC006F7368 /* Colors.xcassets in Resources */, 742679FD26A56CFA004C61BC /* Localizable.strings in Resources */, + 74420BC52BD2449900E77F92 /* PrivacyInfo.xcprivacy in Resources */, 4A6A5212268B6697006F7368 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2398,6 +2406,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 74420BC62BD2449900E77F92 /* PrivacyInfo.xcprivacy in Resources */, 4A1A7AC628327419008EEC84 /* Assets.xcassets in Resources */, 4A1A7AC528326554008EEC84 /* Localizable.strings in Resources */, ); @@ -2413,6 +2422,7 @@ 742679FC26A56CF9004C61BC /* Localizable.strings in Resources */, 7408E6C52677954000D7FAEA /* about.html in Resources */, 7408E6CA2677985800D7FAEA /* jquery-3.6.0.slim.min.js in Resources */, + 74420BC32BD2449900E77F92 /* PrivacyInfo.xcprivacy in Resources */, 746815462475605E00038679 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3300,7 +3310,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 2.5.1; + MARKETING_VERSION = 2.5.2; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -3362,7 +3372,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 2.5.1; + MARKETING_VERSION = 2.5.2; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-expression-type-checking=200 -Xfrontend -warn-long-function-bodies=200"; diff --git a/Cryptomator.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Cryptomator.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 967f9b0ad..0defb2bc4 100644 --- a/Cryptomator.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Cryptomator.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -59,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/cryptomator/cloud-access-swift.git", "state" : { - "revision" : "bb9cc1c300be890f3a47efa0ac0808ee7c42146d", - "version" : "1.9.2" + "revision" : "cd7a18abcaf09349f066363c7524b738f4f4ad79", + "version" : "1.10.1" } }, { diff --git a/Cryptomator/AppDelegate.swift b/Cryptomator/AppDelegate.swift index 729e12a5c..c91d18079 100644 --- a/Cryptomator/AppDelegate.swift +++ b/Cryptomator/AppDelegate.swift @@ -38,16 +38,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate { cleanup() // Set up cloud storage services - CloudProviderDBManager.shared.useBackgroundSession = false DropboxSetup.constants = DropboxSetup(appKey: CloudAccessSecrets.dropboxAppKey, sharedContainerIdentifier: nil, keychainService: CryptomatorConstants.mainAppBundleId, forceForegroundSession: true) GoogleDriveSetup.constants = GoogleDriveSetup(clientId: CloudAccessSecrets.googleDriveClientId, redirectURL: CloudAccessSecrets.googleDriveRedirectURL!, sharedContainerIdentifier: nil) - let oneDriveConfiguration = MSALPublicClientApplicationConfig(clientId: CloudAccessSecrets.oneDriveClientId, redirectUri: CloudAccessSecrets.oneDriveRedirectURI, authority: nil) - oneDriveConfiguration.cacheConfig.keychainSharingGroup = CryptomatorConstants.mainAppBundleId do { - OneDriveSetup.clientApplication = try MSALPublicClientApplication(configuration: oneDriveConfiguration) + let oneDriveConfiguration = MSALPublicClientApplicationConfig(clientId: CloudAccessSecrets.oneDriveClientId, redirectUri: CloudAccessSecrets.oneDriveRedirectURI, authority: nil) + oneDriveConfiguration.cacheConfig.keychainSharingGroup = CryptomatorConstants.mainAppBundleId + let oneDriveClientApplication = try MSALPublicClientApplication(configuration: oneDriveConfiguration) + OneDriveSetup.constants = OneDriveSetup(clientApplication: oneDriveClientApplication, sharedContainerIdentifier: nil) } catch { DDLogError("Setting up OneDrive failed with error: \(error)") } + PCloudSetup.constants = PCloudSetup(appKey: CloudAccessSecrets.pCloudAppKey, sharedContainerIdentifier: nil) // Set up payment queue SKPaymentQueue.default().add(StoreObserver.shared) diff --git a/Cryptomator/Common/CloudAuthenticator.swift b/Cryptomator/Common/CloudAuthenticator.swift index be92c3a1e..fb7306d26 100644 --- a/Cryptomator/Common/CloudAuthenticator.swift +++ b/Cryptomator/Common/CloudAuthenticator.swift @@ -52,8 +52,7 @@ class CloudAuthenticator { } func authenticatePCloud(from viewController: UIViewController) -> Promise { - let authenticator = PCloudAuthenticator(appKey: CloudAccessSecrets.pCloudAppKey) - return authenticator.authenticate(from: viewController).then { credential -> CloudProviderAccount in + return PCloudAuthenticator.authenticate(from: viewController).then { credential -> CloudProviderAccount in try credential.saveToKeychain() let account = CloudProviderAccount(accountUID: credential.userID, cloudProviderType: .pCloud) try self.accountManager.saveNewAccount(account) diff --git a/CryptomatorCommon/Package.swift b/CryptomatorCommon/Package.swift index ecb713a4b..7a467a8a8 100644 --- a/CryptomatorCommon/Package.swift +++ b/CryptomatorCommon/Package.swift @@ -26,7 +26,7 @@ let package = Package( ) ], dependencies: [ - .package(url: "https://github.com/cryptomator/cloud-access-swift.git", .upToNextMinor(from: "1.9.0")), + .package(url: "https://github.com/cryptomator/cloud-access-swift.git", .upToNextMinor(from: "1.10.0")), .package(url: "https://github.com/CocoaLumberjack/CocoaLumberjack.git", .upToNextMinor(from: "3.8.0")), .package(url: "https://github.com/PhilLibs/simple-swift-dependencies", .upToNextMajor(from: "0.1.0")), .package(url: "https://github.com/siteline/SwiftUI-Introspect.git", .upToNextMajor(from: "0.3.0")), diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/CloudProviderDBManager.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/CloudProviderDBManager.swift index 4a52aee6e..030468582 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/CloudProviderDBManager.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/CloudProviderDBManager.swift @@ -12,16 +12,23 @@ import PCloudSDKSwift public protocol CloudProviderManager { func getProvider(with accountUID: String) throws -> CloudProvider + func getBackgroundSessionProvider(with accountUID: String, sessionIdentifier: String) throws -> CloudProvider } public protocol CloudProviderUpdating { func providerShouldUpdate(with accountUID: String) } +struct CachedProvider { + let accountUID: String + let provider: CloudProvider + let backgroundSessionIdentifier: String? + var isBackgroundSession: Bool { backgroundSessionIdentifier != nil } +} + public class CloudProviderDBManager: CloudProviderManager, CloudProviderUpdating { - static var cachedProvider = [String: CloudProvider]() + static var cachedProvider = [CachedProvider]() public static let shared = CloudProviderDBManager(accountManager: CloudProviderAccountDBManager.shared) - public var useBackgroundSession = true let accountManager: CloudProviderAccountDBManager private let maxPageSizeForFileProvider = 500 @@ -31,17 +38,25 @@ public class CloudProviderDBManager: CloudProviderManager, CloudProviderUpdating } public func getProvider(with accountUID: String) throws -> CloudProvider { - if let provider = CloudProviderDBManager.cachedProvider[accountUID] { - return provider + if let entry = CloudProviderDBManager.cachedProvider.first(where: { + $0.accountUID == accountUID && !$0.isBackgroundSession + }) { + return entry.provider } return try createProvider(for: accountUID) } + public func getBackgroundSessionProvider(with accountUID: String, sessionIdentifier: String) throws -> any CloudProvider { + if let entry = CloudProviderDBManager.cachedProvider.first(where: { + $0.accountUID == accountUID && $0.backgroundSessionIdentifier == sessionIdentifier + }) { + return entry.provider + } + return try createBackgroundSessionProvider(for: accountUID, sessionIdentifier: sessionIdentifier) + } + /** Creates and returns a cloud provider for the given `accountUID`. - - If `useBackgroundURLSession` is set to `true`, the number of returned items from a `fetchItemList(forFolderAt:pageToken:)` call is limited to 500. - This is necessary because otherwise memory limit problems can occur with folders with many items in the `FileProviderExtension` where a background `URLSession` is used. */ func createProvider(for accountUID: String) throws -> CloudProvider { let cloudProviderType = try accountManager.getCloudProviderType(for: accountUID) @@ -49,66 +64,103 @@ public class CloudProviderDBManager: CloudProviderManager, CloudProviderUpdating switch cloudProviderType { case .dropbox: let credential = DropboxCredential(tokenUID: accountUID) - provider = DropboxCloudProvider(credential: credential, maxPageSize: useBackgroundSession ? maxPageSizeForFileProvider : .max) + provider = DropboxCloudProvider(credential: credential, maxPageSize: .max) case .googleDrive: let credential = GoogleDriveCredential(userID: accountUID) - provider = try GoogleDriveCloudProvider(credential: credential, - useBackgroundSession: useBackgroundSession, - maxPageSize: useBackgroundSession ? maxPageSizeForFileProvider : .max) + provider = try GoogleDriveCloudProvider(credential: credential, maxPageSize: .max) case .oneDrive: let credential = try OneDriveCredential(with: accountUID) - provider = try OneDriveCloudProvider(credential: credential, - useBackgroundSession: useBackgroundSession, - maxPageSize: useBackgroundSession ? maxPageSizeForFileProvider : .max) + provider = try OneDriveCloudProvider(credential: credential, maxPageSize: .max) case .pCloud: - provider = try createPCloudProvider(for: accountUID) + let credential = try PCloudCredential(userID: accountUID) + let client = PCloud.createClient(with: credential.user) + provider = try PCloudCloudProvider(client: client) case .webDAV: - guard let credential = WebDAVCredentialManager.shared.getCredentialFromKeychain(with: accountUID) else { + let credential = try getWebDAVCredential(for: accountUID) + let client = WebDAVClient(credential: credential) + provider = try WebDAVProvider(with: client, maxPageSize: .max) + case .localFileSystem: + guard let rootURL = try LocalFileSystemBookmarkManager.getBookmarkedRootURL(for: accountUID) else { throw CloudProviderAccountError.accountNotFoundError } - let client: WebDAVClient - if useBackgroundSession { - client = WebDAVClient.withBackgroundSession(credential: credential, sharedContainerIdentifier: CryptomatorConstants.appGroupName) - } else { - client = WebDAVClient(credential: credential) - } - provider = try WebDAVProvider(with: client, maxPageSize: useBackgroundSession ? maxPageSizeForFileProvider : .max) + provider = try LocalFileSystemProvider(rootURL: rootURL, maxPageSize: .max) + case .s3: + let credential = try getS3Credential(for: accountUID) + provider = try S3CloudProvider(credential: credential) + } + CloudProviderDBManager.cachedProvider.append( + .init( + accountUID: accountUID, + provider: provider, + backgroundSessionIdentifier: nil + ) + ) + return provider + } + + /** + Creates and returns a cloud provider for the given `accountUID` using a background URLSession with the given `sessionIdentifier`. + + The number of returned items from a `fetchItemList(forFolderAt:pageToken:)` call is limited to 500. + This is necessary because otherwise memory limit problems can occur with folders with many items in the `FileProviderExtension` where a background `URLSession` is used. + */ + func createBackgroundSessionProvider(for accountUID: String, sessionIdentifier: String) throws -> CloudProvider { + let cloudProviderType = try accountManager.getCloudProviderType(for: accountUID) + let provider: CloudProvider + + switch cloudProviderType { + case .dropbox: + let credential = DropboxCredential(tokenUID: accountUID) + provider = DropboxCloudProvider(credential: credential, maxPageSize: maxPageSizeForFileProvider) + case .googleDrive: + let credential = GoogleDriveCredential(userID: accountUID) + provider = try GoogleDriveCloudProvider.withBackgroundSession(credential: credential, maxPageSize: maxPageSizeForFileProvider, sessionIdentifier: sessionIdentifier) + case .oneDrive: + let credential = try OneDriveCredential(with: accountUID) + provider = try OneDriveCloudProvider.withBackgroundSession(credential: credential, maxPageSize: maxPageSizeForFileProvider, sessionIdentifier: sessionIdentifier) + case .pCloud: + let credential = try PCloudCredential(userID: accountUID) + let client = PCloud.createBackgroundClient(with: credential.user, sessionIdentifier: sessionIdentifier) + provider = try PCloudCloudProvider(client: client) + case .webDAV: + let credential = try getWebDAVCredential(for: accountUID) + let client = WebDAVClient.withBackgroundSession(credential: credential, sessionIdentifier: sessionIdentifier, sharedContainerIdentifier: CryptomatorConstants.appGroupName) + provider = try WebDAVProvider(with: client, maxPageSize: maxPageSizeForFileProvider) case .localFileSystem: guard let rootURL = try LocalFileSystemBookmarkManager.getBookmarkedRootURL(for: accountUID) else { throw CloudProviderAccountError.accountNotFoundError } - provider = try LocalFileSystemProvider(rootURL: rootURL, maxPageSize: useBackgroundSession ? maxPageSizeForFileProvider : .max) + provider = try LocalFileSystemProvider(rootURL: rootURL, maxPageSize: maxPageSizeForFileProvider) case .s3: - provider = try createS3Provider(for: accountUID) + let credential = try getS3Credential(for: accountUID) + provider = try S3CloudProvider.withBackgroundSession(credential: credential, sharedContainerIdentifier: CryptomatorConstants.appGroupName) } - CloudProviderDBManager.cachedProvider[accountUID] = provider + CloudProviderDBManager.cachedProvider.append( + .init( + accountUID: accountUID, + provider: provider, + backgroundSessionIdentifier: sessionIdentifier + ) + ) return provider } - private func createS3Provider(for accountUID: String) throws -> CloudProvider { + private func getS3Credential(for accountUID: String) throws -> S3Credential { guard let credential = S3CredentialManager.shared.getCredential(with: accountUID) else { throw CloudProviderAccountError.accountNotFoundError } - if useBackgroundSession { - return try S3CloudProvider.withBackgroundSession(credential: credential, sharedContainerIdentifier: CryptomatorConstants.appGroupName) - } else { - return try S3CloudProvider(credential: credential) - } + return credential } - private func createPCloudProvider(for accountUID: String) throws -> CloudProvider { - let credential = try PCloudCredential(userID: accountUID) - let client: PCloudClient - if useBackgroundSession { - client = PCloud.createBackgroundClient(with: credential.user, sharedContainerIdentifier: CryptomatorConstants.appGroupName) - } else { - client = PCloud.createClient(with: credential.user) + private func getWebDAVCredential(for accountUID: String) throws -> WebDAVCredential { + guard let credential = WebDAVCredentialManager.shared.getCredentialFromKeychain(with: accountUID) else { + throw CloudProviderAccountError.accountNotFoundError } - return try PCloudCloudProvider(client: client) + return credential } public func providerShouldUpdate(with accountUID: String) { - CloudProviderDBManager.cachedProvider[accountUID] = nil + CloudProviderDBManager.cachedProvider.removeAll(where: { $0.accountUID == accountUID }) // call XPCService for FileProvider } } diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift index 1c24c9aba..435baeb73 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift @@ -483,7 +483,8 @@ public class VaultDBManager: VaultManager { private func createVaultProvider(cachedVault: CachedVault, masterkey: Masterkey, masterkeyFile: MasterkeyFile) throws -> CloudProvider { let vaultUID = cachedVault.vaultUID let vaultAccount = try vaultAccountManager.getAccount(with: vaultUID) - let provider = try providerManager.getProvider(with: vaultAccount.delegateAccountUID) + // it's important to use the vaultUID as background URLSession identifier to avoid identifier collisions + let provider = try providerManager.getBackgroundSessionProvider(with: vaultAccount.delegateAccountUID, sessionIdentifier: vaultUID) let decorator: CloudProvider if let vaultConfigToken = cachedVault.vaultConfigToken { let unverifiedVaultConfig = try UnverifiedVaultConfig(token: vaultConfigToken) diff --git a/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Manager/CloudProviderManagerTests.swift b/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Manager/CloudProviderManagerTests.swift index be08fd73c..7a0add33b 100644 --- a/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Manager/CloudProviderManagerTests.swift +++ b/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Manager/CloudProviderManagerTests.swift @@ -25,12 +25,12 @@ class CloudProviderManagerTests: XCTestCase { DropboxSetup.constants = DropboxSetup(appKey: "", sharedContainerIdentifier: nil, keychainService: nil, forceForegroundSession: false) let account = CloudProviderAccount(accountUID: UUID().uuidString, cloudProviderType: .dropbox) try accountManager.saveNewAccount(account) - XCTAssertNil(CloudProviderDBManager.cachedProvider[account.accountUID]) + XCTAssert(CloudProviderDBManager.cachedProvider.isEmpty) let provider = try manager.getProvider(with: account.accountUID) guard provider is DropboxCloudProvider else { XCTFail("Provider has wrong type") return } - XCTAssertNotNil(CloudProviderDBManager.cachedProvider[account.accountUID]) + XCTAssertEqual(CloudProviderDBManager.cachedProvider.filter { $0.accountUID == account.accountUID }.count, 1) } } diff --git a/FileProviderExtension/FileProviderExtension.swift b/FileProviderExtension/FileProviderExtension.swift index 2458a4f48..3aa574061 100644 --- a/FileProviderExtension/FileProviderExtension.swift +++ b/FileProviderExtension/FileProviderExtension.swift @@ -30,10 +30,11 @@ class FileProviderExtension: NSFileProviderExtension { FileProviderExtension.sharedDatabaseInitialized = true DropboxSetup.constants = DropboxSetup(appKey: CloudAccessSecrets.dropboxAppKey, sharedContainerIdentifier: CryptomatorConstants.appGroupName, keychainService: CryptomatorConstants.mainAppBundleId, forceForegroundSession: false) GoogleDriveSetup.constants = GoogleDriveSetup(clientId: CloudAccessSecrets.googleDriveClientId, redirectURL: CloudAccessSecrets.googleDriveRedirectURL!, sharedContainerIdentifier: CryptomatorConstants.appGroupName) - OneDriveSetup.sharedContainerIdentifier = CryptomatorConstants.appGroupName let oneDriveConfiguration = MSALPublicClientApplicationConfig(clientId: CloudAccessSecrets.oneDriveClientId, redirectUri: CloudAccessSecrets.oneDriveRedirectURI, authority: nil) oneDriveConfiguration.cacheConfig.keychainSharingGroup = CryptomatorConstants.mainAppBundleId - OneDriveSetup.clientApplication = try MSALPublicClientApplication(configuration: oneDriveConfiguration) + let oneDriveClientApplication = try MSALPublicClientApplication(configuration: oneDriveConfiguration) + OneDriveSetup.constants = OneDriveSetup(clientApplication: oneDriveClientApplication, sharedContainerIdentifier: CryptomatorConstants.appGroupName) + PCloudSetup.constants = PCloudSetup(appKey: CloudAccessSecrets.pCloudAppKey, sharedContainerIdentifier: CryptomatorConstants.appGroupName) } catch { // MARK: Handle error diff --git a/FileProviderExtensionUI/RootViewController.swift b/FileProviderExtensionUI/RootViewController.swift index 99f427bbf..8e885d318 100644 --- a/FileProviderExtensionUI/RootViewController.swift +++ b/FileProviderExtensionUI/RootViewController.swift @@ -61,16 +61,17 @@ class RootViewController: FPUIActionExtensionViewController { LoggerSetup.oneTimeSetup() // Set up cloud storage services - CloudProviderDBManager.shared.useBackgroundSession = false DropboxSetup.constants = DropboxSetup(appKey: CloudAccessSecrets.dropboxAppKey, sharedContainerIdentifier: nil, keychainService: CryptomatorConstants.mainAppBundleId, forceForegroundSession: true) GoogleDriveSetup.constants = GoogleDriveSetup(clientId: CloudAccessSecrets.googleDriveClientId, redirectURL: CloudAccessSecrets.googleDriveRedirectURL!, sharedContainerIdentifier: nil) - let oneDriveConfiguration = MSALPublicClientApplicationConfig(clientId: CloudAccessSecrets.oneDriveClientId, redirectUri: CloudAccessSecrets.oneDriveRedirectURI, authority: nil) - oneDriveConfiguration.cacheConfig.keychainSharingGroup = CryptomatorConstants.mainAppBundleId do { - OneDriveSetup.clientApplication = try MSALPublicClientApplication(configuration: oneDriveConfiguration) + let oneDriveConfiguration = MSALPublicClientApplicationConfig(clientId: CloudAccessSecrets.oneDriveClientId, redirectUri: CloudAccessSecrets.oneDriveRedirectURI, authority: nil) + oneDriveConfiguration.cacheConfig.keychainSharingGroup = CryptomatorConstants.mainAppBundleId + let oneDriveClientApplication = try MSALPublicClientApplication(configuration: oneDriveConfiguration) + OneDriveSetup.constants = OneDriveSetup(clientApplication: oneDriveClientApplication, sharedContainerIdentifier: nil) } catch { DDLogError("Setting up OneDrive failed with error: \(error)") } + PCloudSetup.constants = PCloudSetup(appKey: CloudAccessSecrets.pCloudAppKey, sharedContainerIdentifier: nil) return {} }() diff --git a/SharedResources/PrivacyInfo.xcprivacy b/SharedResources/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..bfb731504 --- /dev/null +++ b/SharedResources/PrivacyInfo.xcprivacy @@ -0,0 +1,28 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + DDA9.1 + C617.1 + 3B52.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + 1C8F.1 + + + + + diff --git a/SharedResources/ca.lproj/Localizable.strings b/SharedResources/ca.lproj/Localizable.strings index e1653121c..dfa810429 100644 --- a/SharedResources/ca.lproj/Localizable.strings +++ b/SharedResources/ca.lproj/Localizable.strings @@ -22,6 +22,7 @@ "common.button.next" = "Següent"; "common.button.ok" = "D'acord"; "common.button.refresh" = "Refresca"; +"common.button.register" = "Registre"; "common.button.remove" = "Elimina"; "common.button.retry" = "Reintenta"; "common.button.signOut" = "Tanca la sessió"; @@ -59,6 +60,7 @@ "addVault.openExistingVault.chooseCloud.header" = "On es troba la caixa forta?"; "addVault.openExistingVault.detectedMasterkey.text" = "Cryptomator ha detectat la caixa forta \"%@\".\nVoleu afegir aquesta caixa forta?"; "addVault.openExistingVault.detectedMasterkey.add" = "Afegeix aquesta caixa forta"; +"addVault.openExistingVault.downloadVault.progress" = "Descàrrega de la caixa forta…"; "addVault.openExistingVault.password.footer" = "Introduïu la contrasenya per a \"%@\"."; "addVault.openExistingVault.progress" = "Ara s'afegeix la caixa forta…"; "addVault.success.info" = "S'ha afegit la caixa forta \"%@\".\nAccediu a la caixa forta via l'app de fitxers."; @@ -111,10 +113,18 @@ "getFolderIntent.error.missingPath" = "No s'ha proporcionat cap ruta. Proporcioneu una ruta vàlida que retorni una carpeta."; "getFolderIntent.error.noVaultSelected" = "No s'ha seleccionat cap caixa forta."; + +"hubAuthentication.title" = "Caixa forta de Hub"; "hubAuthentication.accessNotGranted" = "El vostre dispositiu no ha estat encara autoritzat a accedir a aquesta caixa forta. Demaneu autorització al propietari."; "hubAuthentication.licenseExceeded" = "Aquest Cryptomator Hub no té una llicència vàlida. Informa si us plau a l'administrador perquè actualitzi o renovi la llicència."; "hubAuthentication.deviceRegistration.deviceName.cells.name" = "Nom del dispositiu"; +"hubAuthentication.deviceRegistration.deviceName.footer.title" = "Sembla que és el primer accés al Hub des d'aquest dispositiu. Per identificar-lo i poder autoritzar l'accés heu de posar nom a aquest dispositiu."; +"hubAuthentication.deviceRegistration.accountKey.footer.title" = "Cal la vostra Account Key per a iniciar sessió des d'una aplicació o navegador nous. La trobareu al vostre perfil."; +"hubAuthentication.deviceRegistration.needsAuthorization.alert.title" = "Registre de dispositiu correcte"; "hubAuthentication.deviceRegistration.needsAuthorization.alert.message" = "Per a accedir a la caixa forta, el vostre dispositiu ha de ser autoritzat pel propietari de la caixa."; +"hubAuthentication.requireAccountInit.alert.title" = "Acció necessària"; +"hubAuthentication.requireAccountInit.alert.message" = "Per a continuar, si us plau, seguiu els passos necessaris en el vostre perfil d'usuari de Hub."; +"hubAuthentication.requireAccountInit.alert.actionButton" = "Vés al perfil"; "intents.saveFile.missingFile" = "El fitxer proporcionat no és vàlid."; "intents.saveFile.invalidFolder" = "La carpeta proporcionada no és vàlida."; diff --git a/SharedResources/cs.lproj/Localizable.strings b/SharedResources/cs.lproj/Localizable.strings index de842ad8d..d5f7408c1 100644 --- a/SharedResources/cs.lproj/Localizable.strings +++ b/SharedResources/cs.lproj/Localizable.strings @@ -60,6 +60,7 @@ "addVault.openExistingVault.chooseCloud.header" = "Kde je trezor umístěn?"; "addVault.openExistingVault.detectedMasterkey.text" = "Cryptomator detekoval trezor \"%@\".\nChcete přidat tento trezor?"; "addVault.openExistingVault.detectedMasterkey.add" = "Přidat tento trezor"; +"addVault.openExistingVault.downloadVault.progress" = "Stahování trezoru…"; "addVault.openExistingVault.password.footer" = "Zadejte heslo pro \"%@\"."; "addVault.openExistingVault.progress" = "Přidávání trezoru…"; "addVault.success.info" = "Úspěšně přidán trezor \"%@\".\nPřistupujte k tomuto trezoru prostřednictvím aplikace Soubory."; diff --git a/SharedResources/da.lproj/Localizable.strings b/SharedResources/da.lproj/Localizable.strings index 8d789792e..ebf214eb0 100644 --- a/SharedResources/da.lproj/Localizable.strings +++ b/SharedResources/da.lproj/Localizable.strings @@ -21,7 +21,8 @@ "common.button.enable" = "Aktivér"; "common.button.next" = "Næste"; "common.button.ok" = "OK"; -"common.button.refresh" = "Opdatér"; +"common.button.refresh" = "Genopfrisk"; +"common.button.register" = "Registrer"; "common.button.remove" = "Fjern"; "common.button.retry" = "Forsøg igen"; "common.button.signOut" = "Log ud"; @@ -59,6 +60,7 @@ "addVault.openExistingVault.chooseCloud.header" = "Hvor er boksen placeret?"; "addVault.openExistingVault.detectedMasterkey.text" = "Kryptomator fandt boksen \"%@\".\nVil du tilføje denne boks?"; "addVault.openExistingVault.detectedMasterkey.add" = "Tilføj denne boks"; +"addVault.openExistingVault.downloadVault.progress" = "Downloader Boks…"; "addVault.openExistingVault.password.footer" = "Indtast adgangskode for \"%@\"."; "addVault.openExistingVault.progress" = "Tilføjer boks…"; "addVault.success.info" = "Tilføjede boksen \"%@\".\nFå adgang til denne boks via Filer appen."; @@ -111,10 +113,18 @@ "getFolderIntent.error.missingPath" = "Ingen sti blev angivet. Angiv venligst en gyldig sti til en mappe."; "getFolderIntent.error.noVaultSelected" = "Ingen boks er valgt."; + +"hubAuthentication.title" = "Hub Boks"; "hubAuthentication.accessNotGranted" = "Din enhed er endnu ikke blevet godkendt til at få adgang til denne boks. Spørg boks-ejeren om godkendelse."; "hubAuthentication.licenseExceeded" = "Din Cryptomator Hub har en ugyldig licens. Få venligst en Hub administrator til at opgradere eller forny licensen."; "hubAuthentication.deviceRegistration.deviceName.cells.name" = "Enheds-navn"; +"hubAuthentication.deviceRegistration.deviceName.footer.title" = "Det ser ud til at dette er første gang denne enhed tilgår Hub. For at kunne identificere den for adgangstilladelse, skal du navngive denne enhed."; +"hubAuthentication.deviceRegistration.accountKey.footer.title" = "Din kontonøgle er påkrævet til at logge ind fra nye apps eller browsere. Den kan findes i din profil."; +"hubAuthentication.deviceRegistration.needsAuthorization.alert.title" = "Registrering af Enhed Lykkedes"; "hubAuthentication.deviceRegistration.needsAuthorization.alert.message" = "For at tilgå boksen, skal din enhed godkendes af boks-ejeren."; +"hubAuthentication.requireAccountInit.alert.title" = "Handling Påkrævet"; +"hubAuthentication.requireAccountInit.alert.message" = "For at fortsætte, skal du fuldføre de nødvendige trin i din Hub brugerprofil."; +"hubAuthentication.requireAccountInit.alert.actionButton" = "Gå til Profil"; "intents.saveFile.missingFile" = "Den angivne fil er ikke gyldig."; "intents.saveFile.invalidFolder" = "Den angivne mappe er ikke gyldig."; diff --git a/fastlane/changelog.txt b/fastlane/changelog.txt index 6afe6a9f2..8d4343f6c 100644 --- a/fastlane/changelog.txt +++ b/fastlane/changelog.txt @@ -1 +1 @@ -- Fixed "Content Unavailable" error when unlocking vault that was created on macOS in iCloud Drive (#316) \ No newline at end of file +- Fixed "Content Unavailable" error when unlocking multiple vaults in Google Drive, OneDrive, pCloud, and WebDAV (#342, #345, #351) \ No newline at end of file diff --git a/fastlane/config/freemium/metadata/de-DE/release_notes.txt b/fastlane/config/freemium/metadata/de-DE/release_notes.txt index e3ee06575..c5bae17dd 100644 --- a/fastlane/config/freemium/metadata/de-DE/release_notes.txt +++ b/fastlane/config/freemium/metadata/de-DE/release_notes.txt @@ -1 +1 @@ -- Fehler "Inhalt nicht verfügbar" beim Entsperren eines Tresors, der auf macOS in iCloud Drive erstellt wurde, behoben (#316) \ No newline at end of file +- Fehler "Inhalt nicht verfügbar" beim Entsperren mehrerer Tresore in Google Drive, OneDrive, pCloud und WebDAV behoben (#342, #345, #351) \ No newline at end of file diff --git a/fastlane/config/freemium/metadata/en-US/release_notes.txt b/fastlane/config/freemium/metadata/en-US/release_notes.txt index 6afe6a9f2..8d4343f6c 100644 --- a/fastlane/config/freemium/metadata/en-US/release_notes.txt +++ b/fastlane/config/freemium/metadata/en-US/release_notes.txt @@ -1 +1 @@ -- Fixed "Content Unavailable" error when unlocking vault that was created on macOS in iCloud Drive (#316) \ No newline at end of file +- Fixed "Content Unavailable" error when unlocking multiple vaults in Google Drive, OneDrive, pCloud, and WebDAV (#342, #345, #351) \ No newline at end of file diff --git a/fastlane/config/premium/metadata/de-DE/release_notes.txt b/fastlane/config/premium/metadata/de-DE/release_notes.txt index e3ee06575..c5bae17dd 100644 --- a/fastlane/config/premium/metadata/de-DE/release_notes.txt +++ b/fastlane/config/premium/metadata/de-DE/release_notes.txt @@ -1 +1 @@ -- Fehler "Inhalt nicht verfügbar" beim Entsperren eines Tresors, der auf macOS in iCloud Drive erstellt wurde, behoben (#316) \ No newline at end of file +- Fehler "Inhalt nicht verfügbar" beim Entsperren mehrerer Tresore in Google Drive, OneDrive, pCloud und WebDAV behoben (#342, #345, #351) \ No newline at end of file diff --git a/fastlane/config/premium/metadata/en-US/release_notes.txt b/fastlane/config/premium/metadata/en-US/release_notes.txt index 6afe6a9f2..8d4343f6c 100644 --- a/fastlane/config/premium/metadata/en-US/release_notes.txt +++ b/fastlane/config/premium/metadata/en-US/release_notes.txt @@ -1 +1 @@ -- Fixed "Content Unavailable" error when unlocking vault that was created on macOS in iCloud Drive (#316) \ No newline at end of file +- Fixed "Content Unavailable" error when unlocking multiple vaults in Google Drive, OneDrive, pCloud, and WebDAV (#342, #345, #351) \ No newline at end of file