Skip to content
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

Enable keychain sharing between notification service and app #789

Merged
merged 1 commit into from
Jul 11, 2024
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
4 changes: 4 additions & 0 deletions NotificationService/NotificationService.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,9 @@
<array>
<string>group.org.openhab.app</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)org.openhab.app</string>
</array>
</dict>
</plist>
4 changes: 2 additions & 2 deletions NotificationService/NotificationService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
Expand Down
80 changes: 60 additions & 20 deletions OpenHABCore/Sources/OpenHABCore/Util/HTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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)
}
}
}
9 changes: 2 additions & 7 deletions openHAB/OpenHABWebViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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?) {
Expand Down
4 changes: 4 additions & 0 deletions openHAB/openHAB.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,9 @@
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)org.openhab.app</string>
</array>
</dict>
</plist>