From bd455a52e5e1ba3274924f50fbce8aff8479e4b8 Mon Sep 17 00:00:00 2001 From: Pushkar Kulkarni Date: Thu, 25 Jan 2018 15:57:23 +0530 Subject: [PATCH] Implementing Basic authentication with URLSession --- Foundation/URLSession/NativeProtocol.swift | 2 +- Foundation/URLSession/URLSessionTask.swift | 61 ++++++++++++++++++- .../URLSession/http/HTTPURLProtocol.swift | 24 +++++++- 3 files changed, 84 insertions(+), 3 deletions(-) diff --git a/Foundation/URLSession/NativeProtocol.swift b/Foundation/URLSession/NativeProtocol.swift index 4f79094b5e..f21e18a304 100644 --- a/Foundation/URLSession/NativeProtocol.swift +++ b/Foundation/URLSession/NativeProtocol.swift @@ -329,7 +329,7 @@ internal class _NativeProtocol: URLProtocol, _EasyHandleDelegate { } self.internalState = .transferReady(createTransferState(url: url, workQueue: t.workQueue)) - configureEasyHandle(for: request) + configureEasyHandle(for: task?.authRequest ?? request) if (t.suspendCount) < 1 { resume() } diff --git a/Foundation/URLSession/URLSessionTask.swift b/Foundation/URLSession/URLSessionTask.swift index 95a24807ad..a1d0a01a52 100644 --- a/Foundation/URLSession/URLSessionTask.swift +++ b/Foundation/URLSession/URLSessionTask.swift @@ -106,6 +106,12 @@ open class URLSessionTask : NSObject, NSCopying { /// May be nil if this is a stream task /*@NSCopying*/ open let originalRequest: URLRequest? + + /// If there's an authentication failure, we'd need to create a new request with the credentials supplied by the user + var authRequest: URLRequest? = nil + + /// Authentication failure count + fileprivate var previousFailureCount = 0 /// May differ from originalRequest due to http server redirection /*@NSCopying*/ open internal(set) var currentRequest: URLRequest? { @@ -531,6 +537,16 @@ extension _ProtocolClient : URLProtocolClient { func urlProtocolDidFinishLoading(_ protocol: URLProtocol) { guard let task = `protocol`.task else { fatalError() } guard let session = task.session as? URLSession else { fatalError() } + if let response = task.response as? HTTPURLResponse, response.statusCode == 401 { + //TODO: Fetch and set proposed credentials if they exist + let urlProtectionSpace = URLProtectionSpace.create(using: response) + let authenticationChallenge = URLAuthenticationChallenge(protectionSpace: urlProtectionSpace, proposedCredential: nil, + previousFailureCount: task.previousFailureCount, failureResponse: response, + error: nil, sender: `protocol` as! _HTTPURLProtocol) + task.previousFailureCount += 1 + urlProtocol(`protocol`, didReceive: authenticationChallenge) + return + } switch session.behaviour(for: task) { case .taskDelegate(let delegate): if let downloadDelegate = delegate as? URLSessionDownloadDelegate, let downloadTask = task as? URLSessionDownloadTask { @@ -569,7 +585,24 @@ extension _ProtocolClient : URLProtocolClient { } func urlProtocol(_ protocol: URLProtocol, didReceive challenge: URLAuthenticationChallenge) { - NSUnimplemented() + guard let task = `protocol`.task else { fatalError() } + guard let session = task.session as? URLSession else { fatalError() } + switch session.behaviour(for: task) { + case .taskDelegate(let delegate): + session.delegateQueue.addOperation { + let authScheme = challenge.protectionSpace.authenticationMethod + delegate.urlSession(session, task: task, didReceive: challenge) { disposition, credential in + task.suspend() + guard let handler = URLSessionTask.authHandler(for: authScheme) else { + fatalError("\(authScheme) is not supported") + } + handler(task, disposition, credential) + task._protocol = _HTTPURLProtocol(task: task, cachedResponse: nil, client: nil) + task.resume() + } + } + default: return + } } func urlProtocol(_ protocol: URLProtocol, didLoad data: Data) { @@ -639,3 +672,29 @@ extension URLProtocol { case temporaryFileURL } } + +extension URLSessionTask { + typealias _AuthHandler = ((URLSessionTask, URLSession.AuthChallengeDisposition, URLCredential?) -> ()) + + static func authHandler(for authScheme: String) -> _AuthHandler? { + let handlers: [String : _AuthHandler] = [ + "Basic" : basicAuth, + "Digest": digestAuth + ] + return handlers[authScheme] + } + + //Authentication handlers + static func basicAuth(_ task: URLSessionTask, _ disposition: URLSession.AuthChallengeDisposition, _ credential: URLCredential?) { + //TODO: Handle disposition. For now, we default to .useCredential + let user = credential?.user ?? "" + let password = credential?.password ?? "" + let encodedString = "\(user):\(password)".data(using: .utf8)?.base64EncodedString() + task.authRequest = task.originalRequest + task.authRequest?.setValue("Basic \(encodedString!)", forHTTPHeaderField: "Authorization") + } + + static func digestAuth(_ task: URLSessionTask, _ disposition: URLSession.AuthChallengeDisposition, _ credential: URLCredential?) { + NSUnimplemented() + } +} diff --git a/Foundation/URLSession/http/HTTPURLProtocol.swift b/Foundation/URLSession/http/HTTPURLProtocol.swift index 60a4afc72a..d527e51a40 100644 --- a/Foundation/URLSession/http/HTTPURLProtocol.swift +++ b/Foundation/URLSession/http/HTTPURLProtocol.swift @@ -117,7 +117,7 @@ internal class _HTTPURLProtocol: _NativeProtocol { httpHeaders = hh } - if let hh = self.task?.originalRequest?.allHTTPHeaderFields { + if let hh = request.allHTTPHeaderFields { if httpHeaders == nil { httpHeaders = hh } else { @@ -428,3 +428,25 @@ fileprivate extension HTTPURLResponse { return nil } } + +extension _HTTPURLProtocol : URLAuthenticationChallengeSender { + func cancel(_ challenge: URLAuthenticationChallenge) { + NSUnimplemented() + } + + func continueWithoutCredential(for challenge: URLAuthenticationChallenge) { + NSUnimplemented() + } + + func use(_ credential: URLCredential, for challenge: URLAuthenticationChallenge) { + NSUnimplemented() + } + + func performDefaultHandling(for challenge: URLAuthenticationChallenge) { + NSUnimplemented() + } + + func rejectProtectionSpaceAndContinue(with challenge: URLAuthenticationChallenge) { + NSUnimplemented() + } +}