Skip to content

Commit 66a8372

Browse files
committed
Basic implementation of URLCredentialStorage
A basic implementation of URLCredentialStorage, mainly oriented to credentials for the session. It is an in-memory storage and ignores any usage of permantent or synchronizable persistency. This should cover many usage cases, and should allow to built on top of it. Includes tests.
1 parent 1dd5289 commit 66a8372

File tree

5 files changed

+595
-21
lines changed

5 files changed

+595
-21
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,7 @@ if(ENABLE_TESTING)
518518
TestFoundation/TestUnitConverter.swift
519519
TestFoundation/TestUnit.swift
520520
TestFoundation/TestURLCredential.swift
521+
TestFoundation/TestURLCredentialStorage.swift
521522
TestFoundation/TestURLProtectionSpace.swift
522523
TestFoundation/TestURLProtocol.swift
523524
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: 128 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,42 +18,74 @@ 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 != .none else {
77+
return
78+
}
79+
80+
_lock.lock()
81+
let needsNotification = _setWhileLocked(credential, for: space)
82+
_lock.unlock()
83+
84+
if needsNotification {
85+
_sendNotificationWhileUnlocked()
86+
}
87+
}
88+
5789
/*!
5890
@method removeCredential:forProtectionSpace:
5991
@abstract Remove the credential from the set for the specified protection space.
@@ -63,8 +95,10 @@ open class URLCredentialStorage: NSObject {
6395
has a persistence policy of URLCredential.Persistence.synchronizable will fail.
6496
See removeCredential:forProtectionSpace:options.
6597
*/
66-
open func remove(_ credential: URLCredential, for space: URLProtectionSpace) { NSUnimplemented() }
67-
98+
open func remove(_ credential: URLCredential, for space: URLProtectionSpace) {
99+
remove(credential, for: space, options: nil)
100+
}
101+
68102
/*!
69103
@method removeCredential:forProtectionSpace:options
70104
@abstract Remove the credential from the set for the specified protection space based on options.
@@ -76,23 +110,100 @@ open class URLCredentialStorage: NSObject {
76110
are removed, the credential will be removed on all devices that contain this credential.
77111
@discussion The credential is removed from both persistent and temporary storage.
78112
*/
79-
open func remove(_ credential: URLCredential, for space: URLProtectionSpace, options: [String : AnyObject]? = [:]) { NSUnimplemented() }
80-
113+
open func remove(_ credential: URLCredential, for space: URLProtectionSpace, options: [String : AnyObject]? = [:]) {
114+
if credential.persistence == .synchronizable {
115+
guard let options = options,
116+
let removeSynchronizable = options[NSURLCredentialStorageRemoveSynchronizableCredentials] as? NSNumber,
117+
removeSynchronizable.boolValue == true else {
118+
return
119+
}
120+
}
121+
122+
var needsNotification = false
123+
124+
_lock.lock()
125+
126+
if let user = credential.user {
127+
if _credentials[space]?[user] == credential {
128+
_credentials[space]?[user] = nil
129+
needsNotification = true
130+
// If we remove the last entry, remove the protection space.
131+
if _credentials[space]?.count == 0 {
132+
_credentials[space] = nil
133+
}
134+
}
135+
}
136+
// Also, look for a default object, if it exists, but check equality.
137+
if let defaultCredential = _defaultCredentials[space],
138+
defaultCredential == credential {
139+
_defaultCredentials[space] = nil
140+
needsNotification = true
141+
}
142+
143+
_lock.unlock()
144+
145+
if needsNotification {
146+
_sendNotificationWhileUnlocked()
147+
}
148+
}
149+
81150
/*!
82151
@method defaultCredentialForProtectionSpace:
83152
@abstract Get the default credential for the specified protection space.
84153
@param space The protection space for which to get the default credential.
85154
*/
86-
open func defaultCredential(for space: URLProtectionSpace) -> URLCredential? { NSUnimplemented() }
87-
155+
open func defaultCredential(for space: URLProtectionSpace) -> URLCredential? {
156+
_lock.lock()
157+
defer { _lock.unlock() }
158+
159+
return _defaultCredentials[space]
160+
}
161+
88162
/*!
89163
@method setDefaultCredential:forProtectionSpace:
90164
@abstract Set the default credential for the specified protection space.
91165
@param credential The credential to set as default.
92166
@param space The protection space for which the credential should be set as default.
93167
@discussion If the credential is not yet in the set for the protection space, it will be added to it.
94168
*/
95-
open func setDefaultCredential(_ credential: URLCredential, for space: URLProtectionSpace) { NSUnimplemented() }
169+
open func setDefaultCredential(_ credential: URLCredential, for space: URLProtectionSpace) {
170+
guard credential.persistence != .none else {
171+
return
172+
}
173+
174+
_lock.lock()
175+
let needsNotification = _setWhileLocked(credential, for: space, isDefault: true)
176+
_lock.unlock()
177+
178+
if needsNotification {
179+
_sendNotificationWhileUnlocked()
180+
}
181+
}
182+
183+
private func _setWhileLocked(_ credential: URLCredential, for space: URLProtectionSpace, isDefault: Bool = false) -> Bool {
184+
var modified = false
185+
186+
if let user = credential.user {
187+
if _credentials[space] == nil {
188+
_credentials[space] = [:]
189+
}
190+
191+
modified = _credentials[space]![user] != credential
192+
_credentials[space]![user] = credential
193+
}
194+
195+
if isDefault || _defaultCredentials[space] == nil {
196+
modified = modified || _defaultCredentials[space] != credential
197+
_defaultCredentials[space] = credential
198+
}
199+
200+
return modified
201+
}
202+
203+
private func _sendNotificationWhileUnlocked() {
204+
let notification = Notification(name: .NSURLCredentialStorageChanged, object: self, userInfo: nil)
205+
NotificationCenter.default.post(notification)
206+
}
96207
}
97208

98209
extension URLCredentialStorage {
@@ -119,4 +230,3 @@ extension Notification.Name {
119230
* to remove such a credential.
120231
*/
121232
public let NSURLCredentialStorageRemoveSynchronizableCredentials: String = "NSURLCredentialStorageRemoveSynchronizableCredentials"
122-

0 commit comments

Comments
 (0)