forked from swiftlang/swift-corelibs-foundation
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathURLCredentialStorage.swift
261 lines (222 loc) · 10.9 KB
/
URLCredentialStorage.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
import SwiftFoundation
#else
import Foundation
#endif
/*!
@class URLCredential.Storage
@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 { return _shared }
private let _lock: NSLock
private var _credentials: [URLProtectionSpace: [String: URLCredential]]
private var _defaultCredentials: [URLProtectionSpace: URLCredential]
public override init() {
_lock = NSLock()
_credentials = [:]
_defaultCredentials = [:]
}
convenience init(ephemeral: Bool) {
// Some URLCredentialStorages must be ephemeral, to support ephemeral URLSessions. They should not write anything to persistent storage.
// All URLCredentialStorage instances are _currently_ ephemeral, so there's no need to record the value of 'ephemeral' here, but if we implement persistent storage in the future using platform secure storage, implementers of that functionality will have to heed this flag here.
self.init()
}
/*!
@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]? {
_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]] {
_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.
@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) {
guard credential.persistence != .synchronizable else {
// Do what logged-out-from-iCloud Darwin does, and refuse to save synchronizable credentials when a sync service is not available (which, in s-c-f, is always)
return
}
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.
@param credential The credential to remove.
@param space The protection space for which a credential should be removed
@discussion The credential is removed from both persistent and temporary storage. A credential that
has a persistence policy of URLCredential.Persistence.synchronizable will fail.
See removeCredential:forProtectionSpace:options.
*/
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.
@param credential The credential to remove.
@param space The protection space for which a credential should be removed
@param options A dictionary containing options to consider when removing the credential. This should
be used when trying to delete a credential that has the URLCredential.Persistence.synchronizable policy.
Please note that when URLCredential objects that have a URLCredential.Persistence.synchronizable policy
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]? = [:]) {
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? {
_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) {
guard credential.persistence != .synchronizable else {
return
}
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) {
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 {
/*!
@const NSURLCredentialStorageChangedNotification
@abstract This notification is sent on the main thread whenever
the set of stored credentials changes.
*/
public static let NSURLCredentialStorageChanged = NSNotification.Name(rawValue: "NSURLCredentialStorageChangedNotification")
}
/*
* NSURLCredentialStorageRemoveSynchronizableCredentials - (NSNumber value)
* A key that indicates either @YES or @NO that credentials which contain the NSURLCredentialPersistenceSynchronizable
* attribute should be removed. If the key is missing or the value is @NO, then no attempt will be made
* to remove such a credential.
*/
public let NSURLCredentialStorageRemoveSynchronizableCredentials: String = "NSURLCredentialStorageRemoveSynchronizableCredentials"