Skip to content

Basic implementation of URLCredentialStorage #2334

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

Merged
merged 1 commit into from
Jul 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,7 @@ if(ENABLE_TESTING)
TestFoundation/TestUnitConverter.swift
TestFoundation/TestUnit.swift
TestFoundation/TestURLCredential.swift
TestFoundation/TestURLCredentialStorage.swift
TestFoundation/TestURLProtectionSpace.swift
TestFoundation/TestURLProtocol.swift
TestFoundation/TestURLRequest.swift
Expand Down
3 changes: 0 additions & 3 deletions Foundation/URLCredential.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,6 @@ open class URLCredential : NSObject, NSSecureCoding, NSCopying {
@result The initialized URLCredential
*/
public init(user: String, password: String, persistence: Persistence) {
guard persistence != .permanent && persistence != .synchronizable else {
NSUnimplemented()
}
_user = user
_password = password
_persistence = persistence
Expand Down
178 changes: 155 additions & 23 deletions Foundation/URLCredentialStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,42 +18,78 @@ import Foundation
@discussion URLCredential.Storage implements a singleton object (shared instance) which manages the shared credentials cache. Note: Whereas in Mac OS X any application can access any credential with a persistence of URLCredential.Persistence.permanent provided the user gives permission, in iPhone OS an application can access only its own credentials.
*/
open class URLCredentialStorage: NSObject {


private static var _shared = URLCredentialStorage()

/*!
@method sharedCredentialStorage
@abstract Get the shared singleton authentication storage
@result the shared authentication storage
*/
open class var shared: URLCredentialStorage { get { NSUnimplemented() } }

open class var shared: URLCredentialStorage { return _shared }

private let _lock: NSLock
private var _credentials: [URLProtectionSpace: [String: URLCredential]]
private var _defaultCredentials: [URLProtectionSpace: URLCredential]

public override init() {
_lock = NSLock()
_credentials = [:]
_defaultCredentials = [:]
}

/*!
@method credentialsForProtectionSpace:
@abstract Get a dictionary mapping usernames to credentials for the specified protection space.
@param protectionSpace An URLProtectionSpace indicating the protection space for which to get credentials
@result A dictionary where the keys are usernames and the values are the corresponding URLCredentials.
*/
open func credentials(for space: URLProtectionSpace) -> [String : URLCredential]? { NSUnimplemented() }

open func credentials(for space: URLProtectionSpace) -> [String : URLCredential]? {
_lock.lock()
defer { _lock.unlock() }
return _credentials[space]
}

/*!
@method allCredentials
@abstract Get a dictionary mapping URLProtectionSpaces to dictionaries which map usernames to URLCredentials
@result an NSDictionary where the keys are URLProtectionSpaces
and the values are dictionaries, in which the keys are usernames
and the values are URLCredentials
*/
open var allCredentials: [URLProtectionSpace : [String : URLCredential]] { NSUnimplemented() }

open var allCredentials: [URLProtectionSpace : [String : URLCredential]] {
_lock.lock()
defer { _lock.unlock() }
return _credentials
}

/*!
@method setCredential:forProtectionSpace:
@abstract Add a new credential to the set for the specified protection space or replace an existing one.
@param credential The credential to set.
@param space The protection space for which to add it.
@param space The protection space for which to add it.
@discussion Multiple credentials may be set for a given protection space, but each must have
a distinct user. If a credential with the same user is already set for the protection space,
the new one will replace it.
*/
open func set(_ credential: URLCredential, for space: URLProtectionSpace) { NSUnimplemented() }

open func set(_ credential: URLCredential, for space: URLProtectionSpace) {
guard credential.persistence != .synchronizable else {
NSUnimplemented()
}

guard credential.persistence != .none else {
return
}

_lock.lock()
let needsNotification = _setWhileLocked(credential, for: space)
_lock.unlock()

if needsNotification {
_sendNotificationWhileUnlocked()
}
}

/*!
@method removeCredential:forProtectionSpace:
@abstract Remove the credential from the set for the specified protection space.
Expand All @@ -63,8 +99,10 @@ open class URLCredentialStorage: NSObject {
has a persistence policy of URLCredential.Persistence.synchronizable will fail.
See removeCredential:forProtectionSpace:options.
*/
open func remove(_ credential: URLCredential, for space: URLProtectionSpace) { NSUnimplemented() }

open func remove(_ credential: URLCredential, for space: URLProtectionSpace) {
remove(credential, for: space, options: nil)
}

/*!
@method removeCredential:forProtectionSpace:options
@abstract Remove the credential from the set for the specified protection space based on options.
Expand All @@ -76,31 +114,126 @@ open class URLCredentialStorage: NSObject {
are removed, the credential will be removed on all devices that contain this credential.
@discussion The credential is removed from both persistent and temporary storage.
*/
open func remove(_ credential: URLCredential, for space: URLProtectionSpace, options: [String : AnyObject]? = [:]) { NSUnimplemented() }

open func remove(_ credential: URLCredential, for space: URLProtectionSpace, options: [String : AnyObject]? = [:]) {
if credential.persistence == .synchronizable {
guard let options = options,
let removeSynchronizable = options[NSURLCredentialStorageRemoveSynchronizableCredentials] as? NSNumber,
removeSynchronizable.boolValue == true else {
return
}
}

var needsNotification = false

_lock.lock()

if let user = credential.user {
if _credentials[space]?[user] == credential {
_credentials[space]?[user] = nil
needsNotification = true
// If we remove the last entry, remove the protection space.
if _credentials[space]?.count == 0 {
_credentials[space] = nil
}
}
}
// Also, look for a default object, if it exists, but check equality.
if let defaultCredential = _defaultCredentials[space],
defaultCredential == credential {
_defaultCredentials[space] = nil
needsNotification = true
}

_lock.unlock()

if needsNotification {
_sendNotificationWhileUnlocked()
}
}

/*!
@method defaultCredentialForProtectionSpace:
@abstract Get the default credential for the specified protection space.
@param space The protection space for which to get the default credential.
*/
open func defaultCredential(for space: URLProtectionSpace) -> URLCredential? { NSUnimplemented() }

open func defaultCredential(for space: URLProtectionSpace) -> URLCredential? {
_lock.lock()
defer { _lock.unlock() }

return _defaultCredentials[space]
}

/*!
@method setDefaultCredential:forProtectionSpace:
@abstract Set the default credential for the specified protection space.
@param credential The credential to set as default.
@param space The protection space for which the credential should be set as default.
@discussion If the credential is not yet in the set for the protection space, it will be added to it.
*/
open func setDefaultCredential(_ credential: URLCredential, for space: URLProtectionSpace) { NSUnimplemented() }
open func setDefaultCredential(_ credential: URLCredential, for space: URLProtectionSpace) {
guard credential.persistence != .synchronizable else {
NSUnimplemented()
}

guard credential.persistence != .none else {
return
}

_lock.lock()
let needsNotification = _setWhileLocked(credential, for: space, isDefault: true)
_lock.unlock()

if needsNotification {
_sendNotificationWhileUnlocked()
}
}

private func _setWhileLocked(_ credential: URLCredential, for space: URLProtectionSpace, isDefault: Bool = false) -> Bool {
var modified = false

if let user = credential.user {
if _credentials[space] == nil {
_credentials[space] = [:]
}

modified = _credentials[space]![user] != credential
_credentials[space]![user] = credential
}

if isDefault || _defaultCredentials[space] == nil {
modified = modified || _defaultCredentials[space] != credential
_defaultCredentials[space] = credential
}

return modified
}

private func _sendNotificationWhileUnlocked() {
let notification = Notification(name: .NSURLCredentialStorageChanged, object: self, userInfo: nil)
NotificationCenter.default.post(notification)
}
}

extension URLCredentialStorage {
public func getCredentials(for protectionSpace: URLProtectionSpace, task: URLSessionTask, completionHandler: ([String : URLCredential]?) -> Void) { NSUnimplemented() }
public func set(_ credential: URLCredential, for protectionSpace: URLProtectionSpace, task: URLSessionTask) { NSUnimplemented() }
public func remove(_ credential: URLCredential, for protectionSpace: URLProtectionSpace, options: [String : AnyObject]? = [:], task: URLSessionTask) { NSUnimplemented() }
public func getDefaultCredential(for space: URLProtectionSpace, task: URLSessionTask, completionHandler: (URLCredential?) -> Void) { NSUnimplemented() }
public func setDefaultCredential(_ credential: URLCredential, for protectionSpace: URLProtectionSpace, task: URLSessionTask) { NSUnimplemented() }
public func getCredentials(for protectionSpace: URLProtectionSpace, task: URLSessionTask, completionHandler: ([String : URLCredential]?) -> Void) {
completionHandler(credentials(for: protectionSpace))
}

public func set(_ credential: URLCredential, for protectionSpace: URLProtectionSpace, task: URLSessionTask) {
set(credential, for: protectionSpace)
}

public func remove(_ credential: URLCredential, for protectionSpace: URLProtectionSpace, options: [String : AnyObject]? = [:], task: URLSessionTask) {
remove(credential, for: protectionSpace, options: options)
}

public func getDefaultCredential(for space: URLProtectionSpace, task: URLSessionTask, completionHandler: (URLCredential?) -> Void) {
completionHandler(defaultCredential(for: space))
}

public func setDefaultCredential(_ credential: URLCredential, for protectionSpace: URLProtectionSpace, task: URLSessionTask) {
setDefaultCredential(credential, for: protectionSpace)
}
}

extension Notification.Name {
Expand All @@ -119,4 +252,3 @@ extension Notification.Name {
* to remove such a credential.
*/
public let NSURLCredentialStorageRemoveSynchronizableCredentials: String = "NSURLCredentialStorageRemoveSynchronizableCredentials"

Loading