diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+ClientInterface.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+ClientInterface.swift index 61f0a06052fb5..3cae7f945acb6 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+ClientInterface.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+ClientInterface.swift @@ -19,6 +19,10 @@ import NextcloudKit import NextcloudFileProviderKit import OSLog +let AuthenticationTimeouts: [UInt64] = [ // Have progressively longer timeouts to not hammer server + 3_000_000_000, 6_000_000_000, 30_000_000_000, 60_000_000_000, 120_000_000_000, 300_000_000_000 +] + extension FileProviderExtension: NSFileProviderServicing, ChangeNotificationInterface { /* This FileProviderExtension extension contains everything needed to communicate with the client. @@ -100,14 +104,57 @@ extension FileProviderExtension: NSFileProviderServicing, ChangeNotificationInte } } - @objc func setupDomainAccount(user: String, serverUrl: String, password: String) { - let newNcAccount = Account(user: user, serverUrl: serverUrl, password: password) + @objc func setupDomainAccount( + user: String, userId: String, serverUrl: String, password: String + ) { + let semaphore = DispatchSemaphore(value: 0) + var authAttemptState = AuthenticationAttemptResultState.connectionError // default + Task { + let authTestNcKit = NextcloudKit() + authTestNcKit.setup(user: user, userId: userId, password: password, urlBase: serverUrl) + + // Retry a few times if we have a connection issue + for authTimeout in AuthenticationTimeouts { + authAttemptState = await authTestNcKit.tryAuthenticationAttempt() + guard authAttemptState == .connectionError else { break } + + Logger.fileProviderExtension.info( + "\(user, privacy: .public) authentication try timed out. Trying again soon." + ) + try? await Task.sleep(nanoseconds: authTimeout) + } + semaphore.signal() + } + semaphore.wait() + + switch (authAttemptState) { + case .authenticationError: + Logger.fileProviderExtension.info( + "\(user, privacy: .public) authentication failed due to bad creds, stopping" + ) + return + case .connectionError: + // Despite multiple connection attempts we are still getting connection issues, so quit. + Logger.fileProviderExtension.info( + "\(user, privacy: .public) authentication try failed, no connection." + ) + return + case .success: + Logger.fileProviderExtension.info( + """ + Authenticated! Nextcloud account set up in File Provider extension. + User: \(user, privacy: .public) at server: \(serverUrl, privacy: .public) + """ + ) + } + + let newNcAccount = Account(user: user, id: userId, serverUrl: serverUrl, password: password) guard newNcAccount != ncAccount else { return } ncAccount = newNcAccount ncKit.setup( account: newNcAccount.ncKitAccount, user: newNcAccount.username, - userId: newNcAccount.username, + userId: newNcAccount.id, password: newNcAccount.password, urlBase: newNcAccount.serverUrl, userAgent: "Nextcloud-macOS/FileProviderExt", @@ -118,11 +165,6 @@ extension FileProviderExtension: NSFileProviderServicing, ChangeNotificationInte remoteInterface: ncKit, changeNotificationInterface: self, domain: domain ) ncKit.setup(delegate: changeObserver) - - Logger.fileProviderExtension.info( - "Nextcloud account set up in File Provider extension for user: \(user, privacy: .public) at server: \(serverUrl, privacy: .public)" - ) - signalEnumeratorAfterAccountSetup() } diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderSocketLineProcessor.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderSocketLineProcessor.swift index 3a9f54a10abf4..c9a49d4190f16 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderSocketLineProcessor.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderSocketLineProcessor.swift @@ -46,13 +46,14 @@ class FileProviderSocketLineProcessor: NSObject, LineProcessor { delegate.removeAccountConfig() } else if command == "ACCOUNT_DETAILS" { guard let accountDetailsSubsequence = splitLine.last else { return } - let splitAccountDetails = accountDetailsSubsequence.split(separator: "~", maxSplits: 2) + let splitAccountDetails = accountDetailsSubsequence.split(separator: "~", maxSplits: 3) let user = String(splitAccountDetails[0]) - let serverUrl = String(splitAccountDetails[1]) - let password = String(splitAccountDetails[2]) + let userId = String(splitAccountDetails[1]) + let serverUrl = String(splitAccountDetails[2]) + let password = String(splitAccountDetails[3]) - delegate.setupDomainAccount(user: user, serverUrl: serverUrl, password: password) + delegate.setupDomainAccount(user: user, userId: userId, serverUrl: serverUrl, password: password) } } } diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationProtocol.h b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationProtocol.h index 1ab5e8853d284..6f7f0e24da666 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationProtocol.h +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationProtocol.h @@ -21,6 +21,7 @@ - (void)getExtensionAccountIdWithCompletionHandler:(void(^)(NSString *extensionAccountId, NSError *error))completionHandler; - (void)configureAccountWithUser:(NSString *)user + userId:(NSString *)userId serverUrl:(NSString *)serverUrl password:(NSString *)password; - (void)removeAccountConfig; diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationService.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationService.swift index 76a0f00ce267a..41670fe557a8d 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationService.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationService.swift @@ -48,11 +48,13 @@ class ClientCommunicationService: NSObject, NSFileProviderServiceSource, NSXPCLi completionHandler(accountUserId, nil) } - func configureAccount(withUser user: String, + func configureAccount(withUser user: String, + userId: String, serverUrl: String, password: String) { Logger.desktopClientConnection.info("Received configure account information over client communication service") self.fpExtension.setupDomainAccount(user: user, + userId: userId, serverUrl: serverUrl, password: password) } diff --git a/src/gui/macOS/fileprovidersocketcontroller.cpp b/src/gui/macOS/fileprovidersocketcontroller.cpp index fcd5517b77de6..dad2032b5bd78 100644 --- a/src/gui/macOS/fileprovidersocketcontroller.cpp +++ b/src/gui/macOS/fileprovidersocketcontroller.cpp @@ -213,13 +213,15 @@ void FileProviderSocketController::sendAccountDetails() const const auto credentials = account->credentials(); Q_ASSERT(credentials); - const auto accountUser = account->davUser(); - const auto accountUrl = account->url().toString(); - const auto accountPassword = credentials->password(); + const auto accountUser = credentials->user(); // User-provided username/email + const auto accountUserId = account->davUser(); // Backing user id on server + const auto accountUrl = account->url().toString(); // Server base URL + const auto accountPassword = credentials->password(); // Account password // We cannot use colons as separators here due to "https://" in the url const auto message = QString(QStringLiteral("ACCOUNT_DETAILS:") + accountUser + "~" + + accountUserId + "~" + accountUrl + "~" + accountPassword); sendMessage(message); diff --git a/src/gui/macOS/fileproviderxpc_mac.mm b/src/gui/macOS/fileproviderxpc_mac.mm index e94dc2cbcca48..b7128cf6941c8 100644 --- a/src/gui/macOS/fileproviderxpc_mac.mm +++ b/src/gui/macOS/fileproviderxpc_mac.mm @@ -64,11 +64,13 @@ const auto account = accountState->account(); const auto credentials = account->credentials(); NSString *const user = credentials->user().toNSString(); + NSString *const userId = account->davUser().toNSString(); NSString *const serverUrl = account->url().toString().toNSString(); NSString *const password = credentials->password().toNSString(); const auto clientCommService = (NSObject *)_clientCommServices.value(extensionAccountId); [clientCommService configureAccountWithUser:user + userId:userId serverUrl:serverUrl password:password]; }