|
12 | 12 | import Foundation |
13 | 13 | import os |
14 | 14 |
|
15 | | -@MainActor |
16 | | -public func onReceiveSessionTaskChallenge(with challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) { |
| 15 | +private func logChallengeDecision(label: String, _ challenge: URLAuthenticationChallenge, _ disposition: URLSession.AuthChallengeDisposition, reason: String) { |
17 | 16 | let host = challenge.protectionSpace.host |
18 | | - let authenticationMethod = challenge.protectionSpace.authenticationMethod |
19 | | - Logger.sessionChallenge.info("onReceiveSessionTaskChallenge host=\(host, privacy: .public), method=\(authenticationMethod, privacy: .public)") |
| 17 | + let method = challenge.protectionSpace.authenticationMethod |
| 18 | + Logger.sessionChallenge.info("\(label, privacy: .public) decision host=\(host, privacy: .public), method=\(method, privacy: .public), disposition=\(String(describing: disposition), privacy: .public), reason=\(reason, privacy: .public)") |
| 19 | +} |
20 | 20 |
|
21 | | - func logDecision(_ disposition: URLSession.AuthChallengeDisposition, reason: String) { |
22 | | - Logger.sessionChallenge.info("Session task challenge decision host=\(host, privacy: .public), method=\(authenticationMethod, privacy: .public), disposition=\(String(describing: disposition), privacy: .public), reason=\(reason, privacy: .public)") |
23 | | - } |
| 21 | +private func logSessionTaskChallengeDecision(_ challenge: URLAuthenticationChallenge, _ disposition: URLSession.AuthChallengeDisposition, reason: String) { |
| 22 | + logChallengeDecision(label: "Session task challenge", challenge, disposition, reason: reason) |
| 23 | +} |
24 | 24 |
|
25 | | - if challenge.previousFailureCount > 0 { |
26 | | - let decision: URLSession.AuthChallengeDisposition = .cancelAuthenticationChallenge |
27 | | - logDecision(decision, reason: "previous-failure") |
28 | | - return (decision, nil) |
29 | | - } else if authenticationMethod.isAny(of: NSURLAuthenticationMethodHTTPBasic, NSURLAuthenticationMethodDefault) { |
30 | | - let networkTracker = NetworkTracker.shared |
31 | | - let activeConnection = await networkTracker.activeConnection |
| 25 | +private func logSessionChallengeDecision(_ challenge: URLAuthenticationChallenge, _ disposition: URLSession.AuthChallengeDisposition, reason: String) { |
| 26 | + logChallengeDecision(label: "Session challenge", challenge, disposition, reason: reason) |
| 27 | +} |
32 | 28 |
|
33 | | - var candidateConfigurations: [ConnectionConfiguration] = [] |
34 | | - if let activeConfiguration = activeConnection?.configuration { |
35 | | - candidateConfigurations.append(activeConfiguration) |
36 | | - } |
37 | | - for configuration in await networkTracker.configuredConnections() where !candidateConfigurations.contains(configuration) { |
38 | | - candidateConfigurations.append(configuration) |
39 | | - } |
| 29 | +private func credentialForMatchedHost( |
| 30 | + _ challenge: URLAuthenticationChallenge, |
| 31 | + networkTracker: NetworkTracker, |
| 32 | + log: (URLAuthenticationChallenge, URLSession.AuthChallengeDisposition, String) -> Void |
| 33 | +) async -> (URLSession.AuthChallengeDisposition, URLCredential?) { |
| 34 | + let host = challenge.protectionSpace.host |
| 35 | + guard let matchedConfiguration = await networkTracker.connectionConfiguration(forHost: host) else { |
| 36 | + Logger.sessionChallenge.error("No host match for challenge host=\(host, privacy: .public)") |
| 37 | + let decision: URLSession.AuthChallengeDisposition = .performDefaultHandling |
| 38 | + log(challenge, decision, "no-host-match") |
| 39 | + return (decision, nil) |
| 40 | + } |
40 | 41 |
|
41 | | - let proxyHost = activeConnection?.proxyURL?.host |
42 | | - let matchedConfiguration = candidateConfigurations.first { configuration in |
43 | | - URL(string: configuration.url)?.host == host |
44 | | - } |
| 42 | + let credential = URLCredential(user: matchedConfiguration.username, password: matchedConfiguration.password, persistence: .forSession) |
| 43 | + let decision: URLSession.AuthChallengeDisposition = .useCredential |
| 44 | + log(challenge, decision, "matched-host") |
| 45 | + return (decision, credential) |
| 46 | +} |
45 | 47 |
|
46 | | - if let matchedConfiguration { |
47 | | - let credential = URLCredential(user: matchedConfiguration.username, password: matchedConfiguration.password, persistence: .forSession) |
48 | | - let decision: URLSession.AuthChallengeDisposition = .useCredential |
49 | | - logDecision(decision, reason: "matched-connection-host") |
50 | | - return (decision, credential) |
51 | | - } |
| 48 | +@MainActor |
| 49 | +public func onReceiveSessionTaskChallenge(with challenge: URLAuthenticationChallenge, networkTracker: NetworkTracker = .shared) async -> (URLSession.AuthChallengeDisposition, URLCredential?) { |
| 50 | + let host = challenge.protectionSpace.host |
| 51 | + Logger.sessionChallenge.info("onReceiveSessionTaskChallenge host=\(host, privacy: .public), method=\(challenge.protectionSpace.authenticationMethod, privacy: .public)") |
52 | 52 |
|
53 | | - if let proxyHost, host == proxyHost, let activeConfiguration = activeConnection?.configuration { |
54 | | - let credential = URLCredential(user: activeConfiguration.username, password: activeConfiguration.password, persistence: .forSession) |
55 | | - let decision: URLSession.AuthChallengeDisposition = .useCredential |
56 | | - logDecision(decision, reason: "matched-active-proxy-host") |
57 | | - return (decision, credential) |
58 | | - } |
| 53 | + guard challenge.previousFailureCount == 0 else { |
| 54 | + let decision: URLSession.AuthChallengeDisposition = .cancelAuthenticationChallenge |
| 55 | + logSessionTaskChallengeDecision(challenge, decision, reason: "previous-failure") |
| 56 | + return (decision, nil) |
| 57 | + } |
59 | 58 |
|
60 | | - let candidateHosts = candidateConfigurations.compactMap { URL(string: $0.url)?.host }.joined(separator: ",") |
61 | | - Logger.sessionChallenge.error("No host match for challenge host=\(host, privacy: .public). Candidate hosts: \(candidateHosts, privacy: .public)") |
| 59 | + guard challenge.protectionSpace.authenticationMethod.isAny(of: NSURLAuthenticationMethodHTTPBasic, NSURLAuthenticationMethodDefault) else { |
62 | 60 | let decision: URLSession.AuthChallengeDisposition = .performDefaultHandling |
63 | | - logDecision(decision, reason: "no-host-match-default-handling") |
| 61 | + logSessionTaskChallengeDecision(challenge, decision, reason: "unsupported-auth-method") |
64 | 62 | return (decision, nil) |
65 | 63 | } |
66 | | - let decision: URLSession.AuthChallengeDisposition = .performDefaultHandling |
67 | | - logDecision(decision, reason: "unsupported-auth-method-default-handling") |
68 | | - return (decision, nil) |
| 64 | + |
| 65 | + return await credentialForMatchedHost(challenge, networkTracker: networkTracker, log: logSessionTaskChallengeDecision) |
69 | 66 | } |
70 | 67 |
|
71 | 68 | @MainActor |
72 | | -public func onReceiveSessionChallenge(with challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) { |
| 69 | +public func onReceiveSessionChallenge(with challenge: URLAuthenticationChallenge, networkTracker: NetworkTracker = .shared) async -> (URLSession.AuthChallengeDisposition, URLCredential?) { |
73 | 70 | let host = challenge.protectionSpace.host |
74 | 71 | let authenticationMethod = challenge.protectionSpace.authenticationMethod |
75 | 72 | Logger.sessionChallenge.warning("onReceiveSessionChallenge is not implemented fully (see TODOs)") |
76 | 73 | Logger.sessionChallenge.info("onReceiveSessionChallenge host=\(host, privacy: .public), method=\(authenticationMethod, privacy: .public)") |
77 | | - var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling |
78 | | - |
79 | | - func logDecision(_ disposition: URLSession.AuthChallengeDisposition, reason: String) { |
80 | | - Logger.sessionChallenge.info("Session challenge decision host=\(host, privacy: .public), method=\(authenticationMethod, privacy: .public), disposition=\(String(describing: disposition), privacy: .public), reason=\(reason, privacy: .public)") |
81 | | - } |
82 | 74 |
|
83 | 75 | switch challenge.protectionSpace.authenticationMethod { |
84 | 76 | case NSURLAuthenticationMethodServerTrust: |
85 | 77 | // Check if the active connection has ignoreSSL enabled |
86 | | - if let activeConnection = await NetworkTracker.shared.activeConnection, |
| 78 | + if let activeConnection = await networkTracker.activeConnection, |
87 | 79 | activeConnection.configuration.ignoreSSL, |
88 | 80 | let serverTrust = challenge.protectionSpace.serverTrust { |
89 | 81 | Logger.sessionChallenge.info("Ignoring SSL certificate validation (ignoreSSL enabled)") |
90 | 82 | let decision: URLSession.AuthChallengeDisposition = .useCredential |
91 | | - logDecision(decision, reason: "ignore-ssl-enabled") |
| 83 | + logSessionChallengeDecision(challenge, decision, reason: "ignore-ssl-enabled") |
92 | 84 | return (decision, URLCredential(trust: serverTrust)) |
93 | 85 | } |
94 | 86 | let result = await CertificateManagers.serverCertificateManager.evaluateTrust(with: challenge) |
95 | | - logDecision(result.0, reason: "server-trust-manager") |
| 87 | + logSessionChallengeDecision(challenge, result.0, reason: "server-trust-manager") |
96 | 88 | return result |
97 | 89 | case NSURLAuthenticationMethodClientCertificate: |
98 | 90 | let result = CertificateManagers.clientCertificateManager.evaluateTrust(with: challenge) |
99 | | - logDecision(result.0, reason: "client-certificate-manager") |
| 91 | + logSessionChallengeDecision(challenge, result.0, reason: "client-certificate-manager") |
100 | 92 | return result |
101 | 93 | // attemptCredentialAuthentication |
102 | 94 | default: |
103 | | - if challenge.previousFailureCount > 0 { |
104 | | - disposition = .cancelAuthenticationChallenge |
105 | | - logDecision(disposition, reason: "previous-failure") |
106 | | - } else { |
107 | | - // TODO: in the last version, the httpClient had never been set and always remained nil. Figure out if and how this worked and if it is still needed |
108 | | - // credential = await NetworkTracker.shared.httpClient?.session.configuration.urlCredentialStorage?.defaultCredential(for: challenge.protectionSpace) |
109 | | - // if credential != nil { |
110 | | - // disposition = .useCredential |
111 | | - // } |
112 | | - logDecision(disposition, reason: "default-handling-no-credential") |
| 95 | + guard challenge.previousFailureCount == 0 else { |
| 96 | + let decision: URLSession.AuthChallengeDisposition = .cancelAuthenticationChallenge |
| 97 | + logSessionChallengeDecision(challenge, decision, reason: "previous-failure") |
| 98 | + return (decision, nil) |
113 | 99 | } |
114 | | - return (disposition, nil) |
| 100 | + |
| 101 | + // TODO: Figure out if credential lookup for the default case is needed. |
| 102 | + // The old httpClient-based lookup was never wired up. A possible replacement: |
| 103 | + // return await credentialForMatchedHost(challenge, networkTracker: networkTracker, log: logSessionChallengeDecision) |
| 104 | + |
| 105 | + let decision: URLSession.AuthChallengeDisposition = .performDefaultHandling |
| 106 | + logSessionChallengeDecision(challenge, decision, reason: "default-handling-no-credential") |
| 107 | + return (decision, nil) |
115 | 108 | } |
116 | 109 | } |
0 commit comments