Skip to content

Commit df321a5

Browse files
authored
Merge pull request #9159 from nextcloud/i2h3/fix/9686104-sharing
Fix: Resolving file provider services based on security-scoped URL access
2 parents 1f7ab3c + 1e62d2d commit df321a5

File tree

8 files changed

+201
-84
lines changed

8 files changed

+201
-84
lines changed

shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/Authentication/AuthenticationViewController.swift

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ class AuthenticationViewController: NSViewController {
2323
}
2424
}
2525

26+
var serviceResolver: ServiceResolver?
27+
2628
@IBOutlet var activityDescription: NSTextField!
2729
@IBOutlet var cancellationButton: NSButton!
2830
@IBOutlet var progressIndicator: NSProgressIndicator!
@@ -96,9 +98,15 @@ class AuthenticationViewController: NSViewController {
9698

9799
let url = try await manager.getUserVisibleURL(for: .rootContainer)
98100

99-
let connection = try await serviceConnection(url: url, interruptionHandler: {
100-
self.logger.error("Service connection interrupted")
101-
})
101+
guard let log else {
102+
fatalError("Log is not available yet!")
103+
}
104+
105+
guard let serviceResolver else {
106+
fatalError("Service resolver is not available yet!")
107+
}
108+
109+
let connection = try await serviceResolver.getService(at: url)
102110

103111
if let error = await connection.authenticate() {
104112
logger.error("An error was returned from the authentication call: \(error.localizedDescription)")

shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/DocumentActionViewController.swift

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,26 @@ class DocumentActionViewController: FPUIActionExtensionViewController {
2828
///
2929
var logger: FileProviderLogger!
3030

31+
var serviceResolver: ServiceResolver!
32+
3133
// MARK: - Lifecycle
3234

33-
func setUpLogger() {
35+
func setUp() {
3436
if log == nil {
3537
log = FileProviderLog(fileProviderDomainIdentifier: domain.identifier)
3638
}
3739

3840
if logger == nil, let log {
3941
logger = FileProviderLogger(category: "DocumentActionViewController", log: log)
4042
}
43+
44+
if serviceResolver == nil, let log {
45+
serviceResolver = ServiceResolver(log: log)
46+
}
4147
}
4248

4349
func prepare(childViewController: NSViewController) {
44-
setUpLogger()
50+
setUp()
4551
addChild(childViewController)
4652
view.addSubview(childViewController.view)
4753

@@ -54,16 +60,16 @@ class DocumentActionViewController: FPUIActionExtensionViewController {
5460
}
5561

5662
override func prepare(forAction actionIdentifier: String, itemIdentifiers: [NSFileProviderItemIdentifier]) {
57-
setUpLogger()
63+
setUp()
5864
logger?.info("Preparing action: \(actionIdentifier)")
5965

6066
switch (actionIdentifier) {
6167
case "com.nextcloud.desktopclient.FileProviderUIExt.ShareAction":
62-
prepare(childViewController: ShareViewController(itemIdentifiers, log: log))
68+
prepare(childViewController: ShareViewController(itemIdentifiers, serviceResolver: serviceResolver, log: log))
6369
case "com.nextcloud.desktopclient.FileProviderUIExt.LockFileAction":
64-
prepare(childViewController: LockViewController(itemIdentifiers, locking: true, log: log))
70+
prepare(childViewController: LockViewController(itemIdentifiers, locking: true, serviceResolver: serviceResolver, log: log))
6571
case "com.nextcloud.desktopclient.FileProviderUIExt.UnlockFileAction":
66-
prepare(childViewController: LockViewController(itemIdentifiers, locking: false, log: log))
72+
prepare(childViewController: LockViewController(itemIdentifiers, locking: false, serviceResolver: serviceResolver, log: log))
6773
case "com.nextcloud.desktopclient.FileProviderUIExt.EvictAction":
6874
evict(itemsWithIdentifiers: itemIdentifiers, inDomain: domain);
6975
extensionContext.completeRequest();
@@ -73,12 +79,13 @@ class DocumentActionViewController: FPUIActionExtensionViewController {
7379
}
7480

7581
override func prepare(forError error: Error) {
76-
setUpLogger()
82+
setUp()
7783
logger?.info("Preparing for error.", [.error: error])
7884

7985
let storyboard = NSStoryboard(name: "Authentication", bundle: Bundle(for: type(of: self)))
8086
let viewController = storyboard.instantiateInitialController() as! AuthenticationViewController
8187
viewController.log = log
88+
viewController.serviceResolver = serviceResolver
8289

8390
prepare(childViewController: viewController)
8491
}

shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/FileProviderCommunication.swift

Lines changed: 0 additions & 32 deletions
This file was deleted.

shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/Locking/LockViewController.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class LockViewController: NSViewController {
1919
let locking: Bool
2020
let log: any FileProviderLogging
2121
let logger: FileProviderLogger
22+
let serviceResolver: ServiceResolver
2223

2324
@IBOutlet weak var fileNameIcon: NSImageView!
2425
@IBOutlet weak var fileNameLabel: NSTextField!
@@ -35,11 +36,13 @@ class LockViewController: NSViewController {
3536
return parent as? DocumentActionViewController
3637
}
3738

38-
init(_ itemIdentifiers: [NSFileProviderItemIdentifier], locking: Bool, log: any FileProviderLogging) {
39+
init(_ itemIdentifiers: [NSFileProviderItemIdentifier], locking: Bool, serviceResolver: ServiceResolver, log: any FileProviderLogging) {
3940
self.itemIdentifiers = itemIdentifiers
4041
self.locking = locking
4142
self.log = log
4243
self.logger = FileProviderLogger(category: "LockViewController", log: log)
44+
self.serviceResolver = serviceResolver
45+
4346
super.init(nibName: nil, bundle: nil)
4447
}
4548

@@ -161,9 +164,7 @@ class LockViewController: NSViewController {
161164
}
162165

163166
do {
164-
let connection = try await serviceConnection(url: localItemUrl, interruptionHandler: {
165-
self.presentError("File provider service connection interrupted!")
166-
})
167+
let connection = try await serviceResolver.getService(at: localItemUrl)
167168

168169
guard let serverPath = await connection.itemServerPath(identifier: itemIdentifier),
169170
let credentials = await connection.credentials() as? Dictionary<String, String>,
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
2+
// SPDX-License-Identifier: GPL-2.0-or-later
3+
4+
import Foundation
5+
import NextcloudFileProviderKit
6+
7+
///
8+
/// Facility to establish and handle an XPC connection to the file provider extension.
9+
///
10+
final class ServiceResolver {
11+
enum ServiceResolverError: Error {
12+
case failedConnection
13+
case remoteProxyObjectInvalid
14+
case serviceNotFound
15+
}
16+
17+
let log: any FileProviderLogging
18+
let logger: FileProviderLogger
19+
20+
init(log: any FileProviderLogging) {
21+
self.log = log
22+
self.logger = FileProviderLogger(category: "ServiceResolver", log: log)
23+
}
24+
25+
///
26+
/// Logs the interruption of a connection.
27+
///
28+
private func interruptionHandler() {
29+
logger.error("Interruption handler called. Possibly, the remote file provider extension process exited or crashed.")
30+
}
31+
32+
///
33+
/// Logs the invalidation of a connection.
34+
///
35+
private func invalidationHandler() {
36+
logger.error("Invalidation handler called. Possibly, the remote file provider extension process exited or crashed.")
37+
}
38+
39+
func getService(at url: URL) async throws -> FPUIExtensionService {
40+
logger.info("Getting service for item at location.", [.url: url])
41+
42+
var services: [NSFileProviderServiceName : NSFileProviderService] = [:]
43+
44+
do {
45+
if url.startAccessingSecurityScopedResource() {
46+
logger.debug("Started accessing security-scoped resource.", [.url: url])
47+
services = try await FileManager().fileProviderServicesForItem(at: url)
48+
url.stopAccessingSecurityScopedResource()
49+
logger.debug("Stopped accessing security-scoped resource.", [.url: url])
50+
} else {
51+
logger.error("Failed to access security-scoped resource!", [.url: url])
52+
}
53+
} catch {
54+
logger.error("Failed to get file provider services for item!", [.url: url])
55+
throw error
56+
}
57+
58+
guard let service = services[fpUiExtensionServiceName] else {
59+
logger.error("Failed to find service by name in array of returned services!", [.name: fpUiExtensionServiceName])
60+
throw ServiceResolverError.serviceNotFound
61+
}
62+
63+
let connection: NSXPCConnection?
64+
65+
do {
66+
connection = try await service.fileProviderConnection()
67+
} catch {
68+
logger.error("Failed to establish XPC connection!")
69+
throw ServiceResolverError.failedConnection
70+
}
71+
72+
guard let connection else {
73+
throw ServiceResolverError.failedConnection
74+
}
75+
76+
connection.remoteObjectInterface = NSXPCInterface(with: FPUIExtensionService.self)
77+
connection.interruptionHandler = interruptionHandler
78+
connection.invalidationHandler = invalidationHandler
79+
connection.resume()
80+
81+
guard let proxy = connection.remoteObjectProxy as? FPUIExtensionService else {
82+
logger.error("The remote object proxy does not conform to the expected protocol!")
83+
throw ServiceResolverError.remoteProxyObjectInvalid
84+
}
85+
86+
logger.info("Providing service.")
87+
88+
return proxy
89+
}
90+
}

0 commit comments

Comments
 (0)