Skip to content

Commit

Permalink
Handle authentication challenges with DefaultHTTPClient (#409)
Browse files Browse the repository at this point in the history
  • Loading branch information
mickael-menu authored Apr 4, 2024
1 parent c7e28f2 commit 58065b8
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ All notable changes to this project will be documented in this file. Take a look

### Added

#### Shared

* You can now use `DefaultHTTPClientDelegate.httpClient(_:request:didReceive:completion:)` to handle authentication challenges (e.g. Basic) with `DefaultHTTPClient`.

#### Navigator

* The `AudioNavigator` API has been promoted to stable and ships with a new Preferences API.
Expand Down
71 changes: 70 additions & 1 deletion Sources/Shared/Toolkit/HTTP/DefaultHTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@
import Foundation
import UIKit

public enum URLAuthenticationChallengeResponse {
/// Use the specified credential.
case useCredential(URLCredential)
/// Use the default handling for the challenge as though this delegate method were not implemented.
case performDefaultHandling
/// Cancel the entire request.
case cancelAuthenticationChallenge
/// Reject this challenge, and call the authentication delegate method again with the next
/// authentication protection space.
case rejectProtectionSpace
}

/// Delegate protocol for `DefaultHTTPClient`.
public protocol DefaultHTTPClientDelegate: AnyObject {
/// Tells the delegate that the HTTP client will start a new `request`.
Expand Down Expand Up @@ -42,6 +54,14 @@ public protocol DefaultHTTPClientDelegate: AnyObject {
/// This will be called only if `httpClient(_:recoverRequest:fromError:completion:)` is not implemented, or returns
/// an error.
func httpClient(_ httpClient: DefaultHTTPClient, request: HTTPRequest, didFailWithError error: HTTPError)

/// Requests credentials from the delegate in response to an authentication request from the remote server.
func httpClient(
_ httpClient: DefaultHTTPClient,
request: HTTPRequest,
didReceive challenge: URLAuthenticationChallenge,
completion: @escaping (URLAuthenticationChallengeResponse) -> Void
)
}

public extension DefaultHTTPClientDelegate {
Expand All @@ -55,6 +75,15 @@ public extension DefaultHTTPClientDelegate {

func httpClient(_ httpClient: DefaultHTTPClient, request: HTTPRequest, didReceiveResponse response: HTTPResponse) {}
func httpClient(_ httpClient: DefaultHTTPClient, request: HTTPRequest, didFailWithError error: HTTPError) {}

func httpClient(
_ httpClient: DefaultHTTPClient,
request: HTTPRequest,
didReceive challenge: URLAuthenticationChallenge,
completion: @escaping (URLAuthenticationChallengeResponse) -> Void
) {
completion(.performDefaultHandling)
}
}

/// An implementation of `HTTPClient` using native APIs.
Expand Down Expand Up @@ -200,6 +229,13 @@ public final class DefaultHTTPClient: HTTPClient, Loggable {
}
receiveResponse?(response)
},
receiveChallenge: { [weak self] challenge, completion in
if let self = self, let delegate = self.delegate {
delegate.httpClient(self, request: request, didReceive: challenge, completion: completion)
} else {
completion(.performDefaultHandling)
}
},
consume: consume,
completion: { [weak self] result in
if let self = self, case let .failure(error) = result {
Expand Down Expand Up @@ -283,6 +319,15 @@ public final class DefaultHTTPClient: HTTPClient, Loggable {
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
findTask(for: task)?.urlSession(session, didCompleteWithError: error)
}

func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
guard let task = findTask(for: task) else {
completionHandler(.performDefaultHandling, nil)
return
}

task.urlSession(session, didReceive: challenge, completion: completionHandler)
}
}

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

Expand All @@ -312,11 +358,19 @@ public final class DefaultHTTPClient: HTTPClient, Loggable {
case finished
}

init(request: HTTPRequest, task: URLSessionDataTask, receiveResponse: @escaping ((HTTPResponse) -> Void), consume: @escaping (Data, Double?) -> Void, completion: @escaping (HTTPResult<HTTPResponse>) -> Void) {
init(
request: HTTPRequest,
task: URLSessionDataTask,
receiveResponse: @escaping (HTTPResponse) -> Void,
receiveChallenge: @escaping (URLAuthenticationChallenge, @escaping (URLAuthenticationChallengeResponse) -> Void) -> Void,
consume: @escaping (Data, Double?) -> Void,
completion: @escaping (HTTPResult<HTTPResponse>) -> Void
) {
self.request = request
self.task = task
self.completion = completion
self.receiveResponse = receiveResponse
self.receiveChallenge = receiveChallenge
self.consume = consume
}

Expand Down Expand Up @@ -427,6 +481,21 @@ public final class DefaultHTTPClient: HTTPClient, Loggable {
}
finish()
}

func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completion: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
receiveChallenge(challenge) { response in
switch response {
case let .useCredential(credential):
completion(.useCredential, credential)
case .performDefaultHandling:
completion(.performDefaultHandling, nil)
case .cancelAuthenticationChallenge:
completion(.cancelAuthenticationChallenge, nil)
case .rejectProtectionSpace:
completion(.rejectProtectionSpace, nil)
}
}
}
}
}

Expand Down

0 comments on commit 58065b8

Please sign in to comment.