Skip to content

Commit 717b66b

Browse files
committed
Handle authentication challenges with DefaultHTTPClient
1 parent c7e28f2 commit 717b66b

File tree

2 files changed

+74
-1
lines changed

2 files changed

+74
-1
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ All notable changes to this project will be documented in this file. Take a look
88

99
### Added
1010

11+
#### Shared
12+
13+
* You can now use `DefaultHTTPClientDelegate.httpClient(_:request:didReceive:completion:)` to handle authentication challenges (e.g. Basic) with `DefaultHTTPClient`.
14+
1115
#### Navigator
1216

1317
* The `AudioNavigator` API has been promoted to stable and ships with a new Preferences API.

Sources/Shared/Toolkit/HTTP/DefaultHTTPClient.swift

+70-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@
77
import Foundation
88
import UIKit
99

10+
public enum URLAuthenticationChallengeResponse {
11+
/// Use the specified credential.
12+
case useCredential(URLCredential)
13+
/// Use the default handling for the challenge as though this delegate method were not implemented.
14+
case performDefaultHandling
15+
/// Cancel the entire request.
16+
case cancelAuthenticationChallenge
17+
/// Reject this challenge, and call the authentication delegate method again with the next
18+
/// authentication protection space.
19+
case rejectProtectionSpace
20+
}
21+
1022
/// Delegate protocol for `DefaultHTTPClient`.
1123
public protocol DefaultHTTPClientDelegate: AnyObject {
1224
/// Tells the delegate that the HTTP client will start a new `request`.
@@ -42,6 +54,14 @@ public protocol DefaultHTTPClientDelegate: AnyObject {
4254
/// This will be called only if `httpClient(_:recoverRequest:fromError:completion:)` is not implemented, or returns
4355
/// an error.
4456
func httpClient(_ httpClient: DefaultHTTPClient, request: HTTPRequest, didFailWithError error: HTTPError)
57+
58+
/// Requests credentials from the delegate in response to an authentication request from the remote server.
59+
func httpClient(
60+
_ httpClient: DefaultHTTPClient,
61+
request: HTTPRequest,
62+
didReceive challenge: URLAuthenticationChallenge,
63+
completion: @escaping (URLAuthenticationChallengeResponse) -> Void
64+
)
4565
}
4666

4767
public extension DefaultHTTPClientDelegate {
@@ -55,6 +75,15 @@ public extension DefaultHTTPClientDelegate {
5575

5676
func httpClient(_ httpClient: DefaultHTTPClient, request: HTTPRequest, didReceiveResponse response: HTTPResponse) {}
5777
func httpClient(_ httpClient: DefaultHTTPClient, request: HTTPRequest, didFailWithError error: HTTPError) {}
78+
79+
func httpClient(
80+
_ httpClient: DefaultHTTPClient,
81+
request: HTTPRequest,
82+
didReceive challenge: URLAuthenticationChallenge,
83+
completion: @escaping (URLAuthenticationChallengeResponse) -> Void
84+
) {
85+
completion(.performDefaultHandling)
86+
}
5887
}
5988

6089
/// An implementation of `HTTPClient` using native APIs.
@@ -200,6 +229,13 @@ public final class DefaultHTTPClient: HTTPClient, Loggable {
200229
}
201230
receiveResponse?(response)
202231
},
232+
receiveChallenge: { [weak self] challenge, completion in
233+
if let self = self, let delegate = self.delegate {
234+
delegate.httpClient(self, request: request, didReceive: challenge, completion: completion)
235+
} else {
236+
completion(.performDefaultHandling)
237+
}
238+
},
203239
consume: consume,
204240
completion: { [weak self] result in
205241
if let self = self, case let .failure(error) = result {
@@ -283,6 +319,15 @@ public final class DefaultHTTPClient: HTTPClient, Loggable {
283319
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
284320
findTask(for: task)?.urlSession(session, didCompleteWithError: error)
285321
}
322+
323+
func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
324+
guard let task = findTask(for: task) else {
325+
completionHandler(.performDefaultHandling, nil)
326+
return
327+
}
328+
329+
task.urlSession(session, didReceive: challenge, completion: completionHandler)
330+
}
286331
}
287332

288333
/// Represents an on-going HTTP task.
@@ -294,6 +339,7 @@ public final class DefaultHTTPClient: HTTPClient, Loggable {
294339
private let request: HTTPRequest
295340
fileprivate let task: URLSessionTask
296341
private let receiveResponse: (HTTPResponse) -> Void
342+
private let receiveChallenge: (URLAuthenticationChallenge, @escaping (URLAuthenticationChallengeResponse) -> Void) -> Void
297343
private let consume: (Data, Double?) -> Void
298344
private let completion: (HTTPResult<HTTPResponse>) -> Void
299345

@@ -312,11 +358,19 @@ public final class DefaultHTTPClient: HTTPClient, Loggable {
312358
case finished
313359
}
314360

315-
init(request: HTTPRequest, task: URLSessionDataTask, receiveResponse: @escaping ((HTTPResponse) -> Void), consume: @escaping (Data, Double?) -> Void, completion: @escaping (HTTPResult<HTTPResponse>) -> Void) {
361+
init(
362+
request: HTTPRequest,
363+
task: URLSessionDataTask,
364+
receiveResponse: @escaping (HTTPResponse) -> Void,
365+
receiveChallenge: @escaping (URLAuthenticationChallenge, @escaping (URLAuthenticationChallengeResponse) -> Void) -> Void,
366+
consume: @escaping (Data, Double?) -> Void,
367+
completion: @escaping (HTTPResult<HTTPResponse>) -> Void
368+
) {
316369
self.request = request
317370
self.task = task
318371
self.completion = completion
319372
self.receiveResponse = receiveResponse
373+
self.receiveChallenge = receiveChallenge
320374
self.consume = consume
321375
}
322376

@@ -427,6 +481,21 @@ public final class DefaultHTTPClient: HTTPClient, Loggable {
427481
}
428482
finish()
429483
}
484+
485+
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completion: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
486+
receiveChallenge(challenge) { response in
487+
switch response {
488+
case let .useCredential(credential):
489+
completion(.useCredential, credential)
490+
case .performDefaultHandling:
491+
completion(.performDefaultHandling, nil)
492+
case .cancelAuthenticationChallenge:
493+
completion(.cancelAuthenticationChallenge, nil)
494+
case .rejectProtectionSpace:
495+
completion(.rejectProtectionSpace, nil)
496+
}
497+
}
498+
}
430499
}
431500
}
432501

0 commit comments

Comments
 (0)