-
Notifications
You must be signed in to change notification settings - Fork 1.4k
fix: DownloadTaskManager handle redirect authentication #8947
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
base: main
Are you sure you want to change the base?
Conversation
@swift-ci test |
|
||
// Add new authorization header for a redirect if there is one, otherwise remove | ||
var redirectRequest = request | ||
if let redirectURL = request.url, let authorization = task.authorizationProvider?(redirectURL) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: Can you add a test case to Tests/BasicsTests/URLSessionHTTPClientTests.swift
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a draft based on testAsyncDownloadDefaultAuthenticationSuccess(), the 302 request is sent out but the URLSessionHTTPClient is not being executed for me under Xcode.
I don't see how to add an assert or to view the intermediate headers in the test.
Overall this looks good, thanks for the contribution! I'm a little concerned that GitHub changed this behaviour unintentionally, but even if they revert to the old behaviour I think this should still work. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry this took me a while to get back to. Its possible to update this test to exercise the redirect handling but requires an update to the MockURLProtocol.
@@ -805,6 +805,67 @@ final class URLSessionHTTPClientTest: XCTestCase { | |||
} | |||
} | |||
|
|||
func testAsyncDownloadAuthenticateWithRedirectdSuccess() async throws { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
func testAsyncDownloadAuthenticateWithRedirectdSuccess() async throws { | |
func testAsyncDownloadAuthenticateWithRedirectedSuccess() async throws { |
let request = HTTPClient.Request.download( | ||
url: url, | ||
options: options, | ||
fileSystem: localFileSystem, | ||
destination: destination | ||
) | ||
|
||
MockURLProtocol.onRequest(request) { request in | ||
XCTAssertEqual(request.allHTTPHeaderFields?["Authorization"], testAuthHeader) | ||
MockURLProtocol.sendResponse(statusCode: 302, headers: ["Location": redirectURL.absoluteString], for: request) | ||
MockURLProtocol.sendCompletion(for: request) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'll need to add a new method to MockURLProtocol to handle the redirecting, and then you can update this config to:
let request = HTTPClient.Request.download( | |
url: url, | |
options: options, | |
fileSystem: localFileSystem, | |
destination: destination | |
) | |
MockURLProtocol.onRequest(request) { request in | |
XCTAssertEqual(request.allHTTPHeaderFields?["Authorization"], testAuthHeader) | |
MockURLProtocol.sendResponse(statusCode: 302, headers: ["Location": redirectURL.absoluteString], for: request) | |
MockURLProtocol.sendCompletion(for: request) | |
} | |
let request = HTTPClient.Request.download( | |
url: url, | |
options: options, | |
fileSystem: localFileSystem, | |
destination: destination | |
) | |
let redirectRequest = HTTPClient.Request.download( | |
url: redirectURL, | |
options: options, | |
fileSystem: localFileSystem, | |
destination: destination | |
) | |
MockURLProtocol.onRequest(redirectRequest) { request in | |
XCTAssertEqual(request.allHTTPHeaderFields?["Authorization"], testAuthHeader) | |
MockURLProtocol.sendResponse(statusCode: 200, headers: ["Content-Length": "1024"], for: request) | |
MockURLProtocol.sendData(Data(repeating: 0xBE, count: 512), for: request) | |
MockURLProtocol.sendData(Data(repeating: 0xEF, count: 512), for: request) | |
MockURLProtocol.sendCompletion(for: request) | |
} | |
MockURLProtocol.onRequest(request) { request in | |
XCTAssertEqual(request.allHTTPHeaderFields?["Authorization"], testAuthHeader) | |
MockURLProtocol.sendResponse(statusCode: 302, headers: ["Location": redirectURL.absoluteString], for: request) | |
MockURLProtocol.sendRedirect(for: request, to: URLRequest(url: redirectURL)) | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned above, this would go in URLSessionHTTPClientTests.swift to mock out url redirection:
static func sendRedirect(for request: URLRequest, to newRequest: URLRequest) {
let key = Key(request.httpMethod!, request.url!)
self.sendRedirect(newRequest: newRequest, for: key)
}
private static func sendRedirect(newRequest: URLRequest, for key: Key) {
guard let request = self.requests[key] else {
return XCTFail("url did not start loading")
}
let response = HTTPURLResponse(url: key.url, statusCode: 302, httpVersion: "1.1", headerFields: nil)!
request.client?.urlProtocol(request, wasRedirectedTo: newRequest, redirectResponse: response)
}
// https://github.com/apple/swift-corelibs-foundation/pull/2593 tries to address the latter part | ||
try XCTSkipIf(true, "test is only supported on macOS") | ||
#endif | ||
let netrcContent = "machine async-protected.downloader-tests.com login anonymous password qwerty" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let netrcContent = "machine async-protected.downloader-tests.com login anonymous password qwerty" | |
let netrcContent = """ | |
machine async-protected.downloader-tests.com login anonymous password qwerty | |
machine cdn-async.downloader-tests.com login anonymous password qwerty | |
""" |
XCTAssertEqual(response.statusCode, 302) | ||
//XCTAssertEqual(response.headers.get("Location"), redirectURL.absoluteString) | ||
|
||
XCTAssertFileExists(destination) | ||
|
||
//let bytes = ByteString(Array(repeating: 0xBE, count: 512) + Array(repeating: 0xEF, count: 512)) | ||
//XCTAssertEqual(try! localFileSystem.readFileContents(destination), bytes) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
XCTAssertEqual(response.statusCode, 302) | |
//XCTAssertEqual(response.headers.get("Location"), redirectURL.absoluteString) | |
XCTAssertFileExists(destination) | |
//let bytes = ByteString(Array(repeating: 0xBE, count: 512) + Array(repeating: 0xEF, count: 512)) | |
//XCTAssertEqual(try! localFileSystem.readFileContents(destination), bytes) | |
XCTAssertEqual(response.statusCode, 200) | |
XCTAssertFileExists(destination) | |
let bytes = ByteString(Array(repeating: 0xBE, count: 512) + Array(repeating: 0xEF, count: 512)) | |
XCTAssertEqual(try! localFileSystem.readFileContents(destination), bytes) |
Fix download of private artifact bundles on GitHub; which now involves a redirect to another domain to which authentication header should not be sent. Fixes #8946
Motivation:
GitHub API redirects private artifact bundle downloads to Azure Storage, but GitHub authentication headers are sent to Azure Storage domain, resulting in
403
errors:The issue occurs because Azure Storage rejects GitHub authentication that was set for api.github.com domain; there should be no header set as the redirect comes with a SAS token.
Modifications:
DataTaskManager has a redirect handler, but DownloadTaskManager does not. Pass authorizationProvider to DownloadTaskManager in order to set correct authorisation headers for cases when there is a redirect to a new domain.
Result:
Private GitHub artifact bundle downloads succeed by properly handling cross-domain redirects without leaking authentication headers.