Skip to content

Commit 9e505a9

Browse files
authored
Merge pull request #2334 from drodriguez/url-credential-storage
Basic implementation of URLCredentialStorage
2 parents ac15bae + ddeab1d commit 9e505a9

File tree

5 files changed

+727
-26
lines changed

5 files changed

+727
-26
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,7 @@ if(ENABLE_TESTING)
519519
TestFoundation/TestUnit.swift
520520
TestFoundation/TestURLCache.swift
521521
TestFoundation/TestURLCredential.swift
522+
TestFoundation/TestURLCredentialStorage.swift
522523
TestFoundation/TestURLProtectionSpace.swift
523524
TestFoundation/TestURLProtocol.swift
524525
TestFoundation/TestURLRequest.swift

Foundation/URLCredential.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,6 @@ open class URLCredential : NSObject, NSSecureCoding, NSCopying {
5151
@result The initialized URLCredential
5252
*/
5353
public init(user: String, password: String, persistence: Persistence) {
54-
guard persistence != .permanent && persistence != .synchronizable else {
55-
NSUnimplemented()
56-
}
5754
_user = user
5855
_password = password
5956
_persistence = persistence

Foundation/URLCredentialStorage.swift

Lines changed: 155 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,42 +18,78 @@ import Foundation
1818
@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.
1919
*/
2020
open class URLCredentialStorage: NSObject {
21-
21+
22+
private static var _shared = URLCredentialStorage()
23+
2224
/*!
2325
@method sharedCredentialStorage
2426
@abstract Get the shared singleton authentication storage
2527
@result the shared authentication storage
2628
*/
27-
open class var shared: URLCredentialStorage { get { NSUnimplemented() } }
28-
29+
open class var shared: URLCredentialStorage { return _shared }
30+
31+
private let _lock: NSLock
32+
private var _credentials: [URLProtectionSpace: [String: URLCredential]]
33+
private var _defaultCredentials: [URLProtectionSpace: URLCredential]
34+
35+
public override init() {
36+
_lock = NSLock()
37+
_credentials = [:]
38+
_defaultCredentials = [:]
39+
}
40+
2941
/*!
3042
@method credentialsForProtectionSpace:
3143
@abstract Get a dictionary mapping usernames to credentials for the specified protection space.
3244
@param protectionSpace An URLProtectionSpace indicating the protection space for which to get credentials
3345
@result A dictionary where the keys are usernames and the values are the corresponding URLCredentials.
3446
*/
35-
open func credentials(for space: URLProtectionSpace) -> [String : URLCredential]? { NSUnimplemented() }
36-
47+
open func credentials(for space: URLProtectionSpace) -> [String : URLCredential]? {
48+
_lock.lock()
49+
defer { _lock.unlock() }
50+
return _credentials[space]
51+
}
52+
3753
/*!
3854
@method allCredentials
3955
@abstract Get a dictionary mapping URLProtectionSpaces to dictionaries which map usernames to URLCredentials
4056
@result an NSDictionary where the keys are URLProtectionSpaces
4157
and the values are dictionaries, in which the keys are usernames
4258
and the values are URLCredentials
4359
*/
44-
open var allCredentials: [URLProtectionSpace : [String : URLCredential]] { NSUnimplemented() }
45-
60+
open var allCredentials: [URLProtectionSpace : [String : URLCredential]] {
61+
_lock.lock()
62+
defer { _lock.unlock() }
63+
return _credentials
64+
}
65+
4666
/*!
4767
@method setCredential:forProtectionSpace:
4868
@abstract Add a new credential to the set for the specified protection space or replace an existing one.
4969
@param credential The credential to set.
50-
@param space The protection space for which to add it.
70+
@param space The protection space for which to add it.
5171
@discussion Multiple credentials may be set for a given protection space, but each must have
5272
a distinct user. If a credential with the same user is already set for the protection space,
5373
the new one will replace it.
5474
*/
55-
open func set(_ credential: URLCredential, for space: URLProtectionSpace) { NSUnimplemented() }
56-
75+
open func set(_ credential: URLCredential, for space: URLProtectionSpace) {
76+
guard credential.persistence != .synchronizable else {
77+
NSUnimplemented()
78+
}
79+
80+
guard credential.persistence != .none else {
81+
return
82+
}
83+
84+
_lock.lock()
85+
let needsNotification = _setWhileLocked(credential, for: space)
86+
_lock.unlock()
87+
88+
if needsNotification {
89+
_sendNotificationWhileUnlocked()
90+
}
91+
}
92+
5793
/*!
5894
@method removeCredential:forProtectionSpace:
5995
@abstract Remove the credential from the set for the specified protection space.
@@ -63,8 +99,10 @@ open class URLCredentialStorage: NSObject {
6399
has a persistence policy of URLCredential.Persistence.synchronizable will fail.
64100
See removeCredential:forProtectionSpace:options.
65101
*/
66-
open func remove(_ credential: URLCredential, for space: URLProtectionSpace) { NSUnimplemented() }
67-
102+
open func remove(_ credential: URLCredential, for space: URLProtectionSpace) {
103+
remove(credential, for: space, options: nil)
104+
}
105+
68106
/*!
69107
@method removeCredential:forProtectionSpace:options
70108
@abstract Remove the credential from the set for the specified protection space based on options.
@@ -76,31 +114,126 @@ open class URLCredentialStorage: NSObject {
76114
are removed, the credential will be removed on all devices that contain this credential.
77115
@discussion The credential is removed from both persistent and temporary storage.
78116
*/
79-
open func remove(_ credential: URLCredential, for space: URLProtectionSpace, options: [String : AnyObject]? = [:]) { NSUnimplemented() }
80-
117+
open func remove(_ credential: URLCredential, for space: URLProtectionSpace, options: [String : AnyObject]? = [:]) {
118+
if credential.persistence == .synchronizable {
119+
guard let options = options,
120+
let removeSynchronizable = options[NSURLCredentialStorageRemoveSynchronizableCredentials] as? NSNumber,
121+
removeSynchronizable.boolValue == true else {
122+
return
123+
}
124+
}
125+
126+
var needsNotification = false
127+
128+
_lock.lock()
129+
130+
if let user = credential.user {
131+
if _credentials[space]?[user] == credential {
132+
_credentials[space]?[user] = nil
133+
needsNotification = true
134+
// If we remove the last entry, remove the protection space.
135+
if _credentials[space]?.count == 0 {
136+
_credentials[space] = nil
137+
}
138+
}
139+
}
140+
// Also, look for a default object, if it exists, but check equality.
141+
if let defaultCredential = _defaultCredentials[space],
142+
defaultCredential == credential {
143+
_defaultCredentials[space] = nil
144+
needsNotification = true
145+
}
146+
147+
_lock.unlock()
148+
149+
if needsNotification {
150+
_sendNotificationWhileUnlocked()
151+
}
152+
}
153+
81154
/*!
82155
@method defaultCredentialForProtectionSpace:
83156
@abstract Get the default credential for the specified protection space.
84157
@param space The protection space for which to get the default credential.
85158
*/
86-
open func defaultCredential(for space: URLProtectionSpace) -> URLCredential? { NSUnimplemented() }
87-
159+
open func defaultCredential(for space: URLProtectionSpace) -> URLCredential? {
160+
_lock.lock()
161+
defer { _lock.unlock() }
162+
163+
return _defaultCredentials[space]
164+
}
165+
88166
/*!
89167
@method setDefaultCredential:forProtectionSpace:
90168
@abstract Set the default credential for the specified protection space.
91169
@param credential The credential to set as default.
92170
@param space The protection space for which the credential should be set as default.
93171
@discussion If the credential is not yet in the set for the protection space, it will be added to it.
94172
*/
95-
open func setDefaultCredential(_ credential: URLCredential, for space: URLProtectionSpace) { NSUnimplemented() }
173+
open func setDefaultCredential(_ credential: URLCredential, for space: URLProtectionSpace) {
174+
guard credential.persistence != .synchronizable else {
175+
NSUnimplemented()
176+
}
177+
178+
guard credential.persistence != .none else {
179+
return
180+
}
181+
182+
_lock.lock()
183+
let needsNotification = _setWhileLocked(credential, for: space, isDefault: true)
184+
_lock.unlock()
185+
186+
if needsNotification {
187+
_sendNotificationWhileUnlocked()
188+
}
189+
}
190+
191+
private func _setWhileLocked(_ credential: URLCredential, for space: URLProtectionSpace, isDefault: Bool = false) -> Bool {
192+
var modified = false
193+
194+
if let user = credential.user {
195+
if _credentials[space] == nil {
196+
_credentials[space] = [:]
197+
}
198+
199+
modified = _credentials[space]![user] != credential
200+
_credentials[space]![user] = credential
201+
}
202+
203+
if isDefault || _defaultCredentials[space] == nil {
204+
modified = modified || _defaultCredentials[space] != credential
205+
_defaultCredentials[space] = credential
206+
}
207+
208+
return modified
209+
}
210+
211+
private func _sendNotificationWhileUnlocked() {
212+
let notification = Notification(name: .NSURLCredentialStorageChanged, object: self, userInfo: nil)
213+
NotificationCenter.default.post(notification)
214+
}
96215
}
97216

98217
extension URLCredentialStorage {
99-
public func getCredentials(for protectionSpace: URLProtectionSpace, task: URLSessionTask, completionHandler: ([String : URLCredential]?) -> Void) { NSUnimplemented() }
100-
public func set(_ credential: URLCredential, for protectionSpace: URLProtectionSpace, task: URLSessionTask) { NSUnimplemented() }
101-
public func remove(_ credential: URLCredential, for protectionSpace: URLProtectionSpace, options: [String : AnyObject]? = [:], task: URLSessionTask) { NSUnimplemented() }
102-
public func getDefaultCredential(for space: URLProtectionSpace, task: URLSessionTask, completionHandler: (URLCredential?) -> Void) { NSUnimplemented() }
103-
public func setDefaultCredential(_ credential: URLCredential, for protectionSpace: URLProtectionSpace, task: URLSessionTask) { NSUnimplemented() }
218+
public func getCredentials(for protectionSpace: URLProtectionSpace, task: URLSessionTask, completionHandler: ([String : URLCredential]?) -> Void) {
219+
completionHandler(credentials(for: protectionSpace))
220+
}
221+
222+
public func set(_ credential: URLCredential, for protectionSpace: URLProtectionSpace, task: URLSessionTask) {
223+
set(credential, for: protectionSpace)
224+
}
225+
226+
public func remove(_ credential: URLCredential, for protectionSpace: URLProtectionSpace, options: [String : AnyObject]? = [:], task: URLSessionTask) {
227+
remove(credential, for: protectionSpace, options: options)
228+
}
229+
230+
public func getDefaultCredential(for space: URLProtectionSpace, task: URLSessionTask, completionHandler: (URLCredential?) -> Void) {
231+
completionHandler(defaultCredential(for: space))
232+
}
233+
234+
public func setDefaultCredential(_ credential: URLCredential, for protectionSpace: URLProtectionSpace, task: URLSessionTask) {
235+
setDefaultCredential(credential, for: protectionSpace)
236+
}
104237
}
105238

106239
extension Notification.Name {
@@ -119,4 +252,3 @@ extension Notification.Name {
119252
* to remove such a credential.
120253
*/
121254
public let NSURLCredentialStorageRemoveSynchronizableCredentials: String = "NSURLCredentialStorageRemoveSynchronizableCredentials"
122-

0 commit comments

Comments
 (0)