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, @unchecked Sendable {
private static let _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.isSynchronizable 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.isSynchronizable {
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.isSynchronizable 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"
|