Add encrypted credential storage
This commit is contained in:
parent
595db517a7
commit
aaa4342494
|
@ -241,9 +241,100 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
|
||||
// MARK: - API
|
||||
|
||||
public func storeCredentials(_ credentials: Credentials) {
|
||||
self.username = credentials.username
|
||||
// self.password = password
|
||||
public func storeCredentials(_ credentials: Credentials) throws {
|
||||
|
||||
guard let username = credentials.username, let password = credentials.password, let server = delegate.server else {
|
||||
throw CredentialsError.incompleteCredentials
|
||||
}
|
||||
|
||||
self.username = username
|
||||
|
||||
let passwordData = password.data(using: String.Encoding.utf8)!
|
||||
|
||||
let updateQuery: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
|
||||
kSecAttrAccount as String: username,
|
||||
kSecAttrServer as String: server]
|
||||
let attributes: [String: Any] = [kSecValueData as String: passwordData]
|
||||
let status = SecItemUpdate(updateQuery as CFDictionary, attributes as CFDictionary)
|
||||
|
||||
switch status {
|
||||
case errSecSuccess:
|
||||
return
|
||||
case errSecItemNotFound:
|
||||
break
|
||||
default:
|
||||
throw CredentialsError.unhandledError(status: status)
|
||||
}
|
||||
|
||||
guard status == errSecItemNotFound else {
|
||||
return
|
||||
}
|
||||
|
||||
let addQuery: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
|
||||
kSecAttrAccount as String: username,
|
||||
kSecAttrServer as String: server,
|
||||
kSecValueData as String: passwordData]
|
||||
let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
|
||||
if addStatus != errSecSuccess {
|
||||
throw CredentialsError.unhandledError(status: status)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public func retrieveCredentials() throws -> Credentials? {
|
||||
|
||||
guard let username = self.username, let server = delegate.server else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
|
||||
kSecAttrAccount as String: username,
|
||||
kSecAttrServer as String: server,
|
||||
kSecMatchLimit as String: kSecMatchLimitOne,
|
||||
kSecReturnAttributes as String: true,
|
||||
kSecReturnData as String: true]
|
||||
|
||||
var item: CFTypeRef?
|
||||
let status = SecItemCopyMatching(query as CFDictionary, &item)
|
||||
|
||||
guard status != errSecItemNotFound else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard status == errSecSuccess else {
|
||||
throw CredentialsError.unhandledError(status: status)
|
||||
}
|
||||
|
||||
guard let existingItem = item as? [String : Any],
|
||||
let passwordData = existingItem[kSecValueData as String] as? Data,
|
||||
let password = String(data: passwordData, encoding: String.Encoding.utf8) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return BasicCredentials(username: username, password: password)
|
||||
|
||||
}
|
||||
|
||||
public func removeCredentials() throws {
|
||||
|
||||
guard let username = self.username, let server = delegate.server else {
|
||||
return
|
||||
}
|
||||
|
||||
let query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
|
||||
kSecAttrAccount as String: username,
|
||||
kSecAttrServer as String: server,
|
||||
kSecMatchLimit as String: kSecMatchLimitOne,
|
||||
kSecReturnAttributes as String: true,
|
||||
kSecReturnData as String: true]
|
||||
|
||||
let status = SecItemDelete(query as CFDictionary)
|
||||
guard status == errSecSuccess || status == errSecItemNotFound else {
|
||||
throw CredentialsError.unhandledError(status: status)
|
||||
}
|
||||
|
||||
self.username = nil
|
||||
|
||||
}
|
||||
|
||||
public static func validateCredentials(transport: Transport = URLSession.webserviceTransport(), type: AccountType, credentials: Credentials, completionHandler handler: @escaping (Result<Bool, Error>) -> Void) {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
//
|
||||
|
||||
import XCTest
|
||||
import RSWeb
|
||||
@testable import Account
|
||||
|
||||
class AccountCredentialsTest: XCTestCase {
|
||||
|
@ -21,8 +22,67 @@ class AccountCredentialsTest: XCTestCase {
|
|||
TestAccountManager.shared.deleteAccount(account)
|
||||
}
|
||||
|
||||
func testExample() {
|
||||
func testCreateRetrieveDelete() {
|
||||
|
||||
// Make sure any left over from failed tests are gone
|
||||
do {
|
||||
try account.removeCredentials()
|
||||
} catch {
|
||||
XCTFail(error.localizedDescription)
|
||||
}
|
||||
|
||||
var credentials: Credentials? = BasicCredentials(username: "maurice", password: "hardpasswd")
|
||||
|
||||
// Store the credentials
|
||||
do {
|
||||
try account.storeCredentials(credentials!)
|
||||
} catch {
|
||||
XCTFail(error.localizedDescription)
|
||||
}
|
||||
|
||||
// Retrieve them
|
||||
credentials = nil
|
||||
do {
|
||||
credentials = try account.retrieveCredentials()
|
||||
} catch {
|
||||
XCTFail(error.localizedDescription)
|
||||
}
|
||||
XCTAssertEqual("maurice", credentials!.username)
|
||||
XCTAssertEqual("hardpasswd", credentials!.password)
|
||||
|
||||
// Update them
|
||||
credentials?.password = "easypasswd"
|
||||
do {
|
||||
try account.storeCredentials(credentials!)
|
||||
} catch {
|
||||
XCTFail(error.localizedDescription)
|
||||
}
|
||||
|
||||
// Retrieve them again
|
||||
credentials = nil
|
||||
do {
|
||||
credentials = try account.retrieveCredentials()
|
||||
} catch {
|
||||
XCTFail(error.localizedDescription)
|
||||
}
|
||||
XCTAssertEqual("maurice", credentials!.username)
|
||||
XCTAssertEqual("easypasswd", credentials!.password)
|
||||
|
||||
// Delete them
|
||||
do {
|
||||
try account.removeCredentials()
|
||||
} catch {
|
||||
XCTFail(error.localizedDescription)
|
||||
}
|
||||
|
||||
// Make sure they are gone
|
||||
do {
|
||||
try credentials = account.retrieveCredentials()
|
||||
} catch {
|
||||
XCTFail(error.localizedDescription)
|
||||
}
|
||||
|
||||
XCTAssertNil(credentials)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -67,9 +67,13 @@ class AccountsAddFeedbinWindowController: NSWindowController, NSTextFieldDelegat
|
|||
|
||||
if authenticated {
|
||||
let account = AccountManager.shared.createAccount(type: .feedbin)
|
||||
account.storeCredentials(credentials)
|
||||
do {
|
||||
try account.storeCredentials(credentials)
|
||||
self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK)
|
||||
} catch {
|
||||
self.errorMessageLabel.stringValue = NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error")
|
||||
}
|
||||
|
||||
self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK)
|
||||
} else {
|
||||
self.errorMessageLabel.stringValue = NSLocalizedString("Invalid email/password combination.", comment: "Credentials Error")
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit eae66d829ff8beaf4ce0694d768bd5cb242fbacd
|
||||
Subproject commit 731711fff487f923d5be8ea1f4a9c19a58f059c3
|
Loading…
Reference in New Issue