From ce79b29197866ee8d21e3bd51778389a0f6df269 Mon Sep 17 00:00:00 2001 From: Dan Cunningham Date: Thu, 11 Jul 2024 15:27:06 -0700 Subject: [PATCH] Enable keychain sharing between notification service and app (#789) and clean up auth handling a bit Signed-off-by: Dan Cunningham --- .../NotificationService.entitlements | 4 + NotificationService/NotificationService.swift | 4 +- .../Sources/OpenHABCore/Util/HTTPClient.swift | 80 ++++++++++++++----- openHAB/OpenHABWebViewController.swift | 9 +-- openHAB/openHAB.entitlements | 4 + 5 files changed, 72 insertions(+), 29 deletions(-) diff --git a/NotificationService/NotificationService.entitlements b/NotificationService/NotificationService.entitlements index b6063e1b..029463f3 100644 --- a/NotificationService/NotificationService.entitlements +++ b/NotificationService/NotificationService.entitlements @@ -6,5 +6,9 @@ group.org.openhab.app + keychain-access-groups + + $(AppIdentifierPrefix)org.openhab.app + diff --git a/NotificationService/NotificationService.swift b/NotificationService/NotificationService.swift index c99065b7..cf785e5a 100644 --- a/NotificationService/NotificationService.swift +++ b/NotificationService/NotificationService.swift @@ -135,7 +135,7 @@ class NotificationService: UNNotificationServiceExtension { } private func downloadAndAttachMedia(url: String, completion: @escaping (UNNotificationAttachment?) -> Void) { - let client = HTTPClient(username: Preferences.username, password: Preferences.username) // lets not always send auth with this + let client = HTTPClient(username: Preferences.username, password: Preferences.username, alwaysSendBasicAuth: Preferences.alwaysSendCreds) let downloadCompletionHandler: @Sendable (URL?, URLResponse?, Error?) -> Void = { (localURL, response, error) in guard let localURL else { @@ -161,7 +161,7 @@ class NotificationService: UNNotificationServiceExtension { let itemName = String(itemURI.absoluteString.dropFirst(scheme.count + 1)) - let client = HTTPClient(username: Preferences.username, password: Preferences.username, alwaysSendBasicAuth: Preferences.alwaysSendCreds) + let client = HTTPClient(username: Preferences.username, password: Preferences.password, alwaysSendBasicAuth: Preferences.alwaysSendCreds) client.getItem(baseURLs: [Preferences.localUrl, Preferences.remoteUrl], itemName: itemName) { item, error in guard let item else { os_log("Could not find item %{PUBLIC}@", log: .default, type: .info, itemName) diff --git a/OpenHABCore/Sources/OpenHABCore/Util/HTTPClient.swift b/OpenHABCore/Sources/OpenHABCore/Util/HTTPClient.swift index c17863cf..425f62b4 100644 --- a/OpenHABCore/Sources/OpenHABCore/Util/HTTPClient.swift +++ b/OpenHABCore/Sources/OpenHABCore/Util/HTTPClient.swift @@ -12,19 +12,17 @@ import Foundation import os.log -public class HTTPClient: NSObject, URLSessionDelegate { +public class HTTPClient: NSObject, URLSessionDelegate, URLSessionTaskDelegate { // MARK: - Properties private var session: URLSession! private let username: String private let password: String - private let certManager: ClientCertificateManager private let alwaysSendBasicAuth: Bool public init(username: String, password: String, alwaysSendBasicAuth: Bool = false) { self.username = username self.password = password - certManager = ClientCertificateManager() self.alwaysSendBasicAuth = alwaysSendBasicAuth super.init() @@ -283,28 +281,70 @@ public class HTTPClient: NSObject, URLSessionDelegate { task.resume() } - // MARK: - URLSessionDelegate for Client Certificates - + // MARK: - URLSessionDelegate for Client Certificates and Basic Auth + public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { - if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate { - let serverDistinguishedNames = challenge.protectionSpace.distinguishedNames - let identity = certManager.evaluateTrust(distinguishedNames: serverDistinguishedNames ?? []) + urlSessionInternal(session, task: nil, didReceive: challenge, completionHandler: completionHandler) + } - if let identity { - let credential = URLCredential(identity: identity, certificates: nil, persistence: .forSession) - completionHandler(.useCredential, credential) + public func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + urlSessionInternal(session, task: task, didReceive: challenge, completionHandler: completionHandler) + } + + private func urlSessionInternal(_ session: URLSession, task: URLSessionTask?, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + os_log("URLAuthenticationChallenge: %{public}@", log: .networking, type: .info, challenge.protectionSpace.authenticationMethod) + let authenticationMethod = challenge.protectionSpace.authenticationMethod + switch authenticationMethod { + case NSURLAuthenticationMethodServerTrust: + handleServerTrust(challenge: challenge, completionHandler: completionHandler) + case NSURLAuthenticationMethodDefault, NSURLAuthenticationMethodHTTPBasic: + if let task { + task.authAttemptCount += 1 + if task.authAttemptCount > 1 { + completionHandler(.cancelAuthenticationChallenge, nil) + } else { + handleBasicAuth(challenge: challenge, completionHandler: completionHandler) + } } else { - completionHandler(.cancelAuthenticationChallenge, nil) + handleBasicAuth(challenge: challenge, completionHandler: completionHandler) } - } else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust { - let serverTrust = challenge.protectionSpace.serverTrust! - let credential = URLCredential(trust: serverTrust) - completionHandler(.useCredential, credential) - } else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic { - let credential = URLCredential(user: username, password: password, persistence: .forSession) - completionHandler(.useCredential, credential) - } else { + case NSURLAuthenticationMethodClientCertificate: + handleClientCertificateAuth(challenge: challenge, completionHandler: completionHandler) + default: + completionHandler(.performDefaultHandling, nil) + } + } + + private func handleServerTrust(challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + guard let serverTrust = challenge.protectionSpace.serverTrust else { completionHandler(.performDefaultHandling, nil) + return + } + let credential = URLCredential(trust: serverTrust) + completionHandler(.useCredential, credential) + } + + private func handleBasicAuth(challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + let credential = URLCredential(user: username, password: password, persistence: .forSession) + completionHandler(.useCredential, credential) + } + + private func handleClientCertificateAuth(challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + let certificateManager = ClientCertificateManager() + let (disposition, credential) = certificateManager.evaluateTrust(with: challenge) + completionHandler(disposition, credential) + } +} + +extension URLSessionTask { + private static var authAttemptCountKey: UInt8 = 0 + + var authAttemptCount: Int { + get { + objc_getAssociatedObject(self, &URLSessionTask.authAttemptCountKey) as? Int ?? 0 + } + set { + objc_setAssociatedObject(self, &URLSessionTask.authAttemptCountKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } } diff --git a/openHAB/OpenHABWebViewController.swift b/openHAB/OpenHABWebViewController.swift index cfa9812a..e0cbb224 100644 --- a/openHAB/OpenHABWebViewController.swift +++ b/openHAB/OpenHABWebViewController.swift @@ -404,13 +404,8 @@ extension OpenHABWebViewController: WKUIDelegate { extension OpenHABWebViewController: OpenHABTrackerDelegate { func openHABTracked(_ openHABUrl: URL?, version: Int) { os_log("OpenHABWebViewController openHAB URL = %{PUBLIC}@", log: .remoteAccess, type: .error, "\(openHABUrl!)") - if version >= 2 { - openHABTrackedRootUrl = openHABUrl?.absoluteString ?? "" - loadWebView(force: false) - } else { - showPopupMessage(seconds: 2, title: NSLocalizedString("select_sitemap", comment: ""), message: "", theme: .info) - showSideMenu() - } + openHABTrackedRootUrl = openHABUrl?.absoluteString ?? "" + loadWebView(force: false) } func openHABTrackingProgress(_ message: String?) { diff --git a/openHAB/openHAB.entitlements b/openHAB/openHAB.entitlements index 234af8ed..add7953c 100644 --- a/openHAB/openHAB.entitlements +++ b/openHAB/openHAB.entitlements @@ -14,5 +14,9 @@ com.apple.security.network.client + keychain-access-groups + + $(AppIdentifierPrefix)org.openhab.app +