Move credentials classes to the Account framework in NetNewsWire
This commit is contained in:
parent
53370ff0d3
commit
7ca2226669
|
@ -18,6 +18,9 @@
|
|||
5144EA49227B497600D19003 /* FeedbinAPICaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA48227B497600D19003 /* FeedbinAPICaller.swift */; };
|
||||
5144EA4E227B829A00D19003 /* FeedbinAccountDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA4D227B829A00D19003 /* FeedbinAccountDelegate.swift */; };
|
||||
5154367B228EEB28005E1CDF /* FeedbinImportResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5154367A228EEB28005E1CDF /* FeedbinImportResult.swift */; };
|
||||
515E4EB52324FF8C0057B0E7 /* CredentialsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515E4EB22324FF8C0057B0E7 /* CredentialsManager.swift */; };
|
||||
515E4EB62324FF8C0057B0E7 /* URLRequest+RSWeb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515E4EB32324FF8C0057B0E7 /* URLRequest+RSWeb.swift */; };
|
||||
515E4EB72324FF8C0057B0E7 /* Credentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515E4EB42324FF8C0057B0E7 /* Credentials.swift */; };
|
||||
5165D7122282080C00D9D53D /* AccountFolderContentsSyncTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5165D7112282080C00D9D53D /* AccountFolderContentsSyncTest.swift */; };
|
||||
5165D71622821C2400D9D53D /* taggings_delete.json in Resources */ = {isa = PBXBuildFile; fileRef = 5165D71322821C2400D9D53D /* taggings_delete.json */; };
|
||||
5165D71722821C2400D9D53D /* taggings_add.json in Resources */ = {isa = PBXBuildFile; fileRef = 5165D71422821C2400D9D53D /* taggings_add.json */; };
|
||||
|
@ -119,6 +122,9 @@
|
|||
5144EA48227B497600D19003 /* FeedbinAPICaller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinAPICaller.swift; sourceTree = "<group>"; };
|
||||
5144EA4D227B829A00D19003 /* FeedbinAccountDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinAccountDelegate.swift; sourceTree = "<group>"; };
|
||||
5154367A228EEB28005E1CDF /* FeedbinImportResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinImportResult.swift; sourceTree = "<group>"; };
|
||||
515E4EB22324FF8C0057B0E7 /* CredentialsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CredentialsManager.swift; sourceTree = "<group>"; };
|
||||
515E4EB32324FF8C0057B0E7 /* URLRequest+RSWeb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URLRequest+RSWeb.swift"; sourceTree = "<group>"; };
|
||||
515E4EB42324FF8C0057B0E7 /* Credentials.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Credentials.swift; sourceTree = "<group>"; };
|
||||
5165D7112282080C00D9D53D /* AccountFolderContentsSyncTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFolderContentsSyncTest.swift; sourceTree = "<group>"; };
|
||||
5165D71322821C2400D9D53D /* taggings_delete.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = taggings_delete.json; sourceTree = "<group>"; };
|
||||
5165D71422821C2400D9D53D /* taggings_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = taggings_add.json; sourceTree = "<group>"; };
|
||||
|
@ -196,6 +202,16 @@
|
|||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
515E4EB12324FF7D0057B0E7 /* Credentials */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
515E4EB42324FF8C0057B0E7 /* Credentials.swift */,
|
||||
515E4EB22324FF8C0057B0E7 /* CredentialsManager.swift */,
|
||||
515E4EB32324FF8C0057B0E7 /* URLRequest+RSWeb.swift */,
|
||||
);
|
||||
path = Credentials;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5165D71F22835E9800D9D53D /* FeedFinder */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -300,6 +316,7 @@
|
|||
841974001F6DD1EC006346C4 /* Folder.swift */,
|
||||
844B297E210CE37E004020B3 /* UnreadCountProvider.swift */,
|
||||
5165D71F22835E9800D9D53D /* FeedFinder */,
|
||||
515E4EB12324FF7D0057B0E7 /* Credentials */,
|
||||
8419742B1F6DDE84006346C4 /* LocalAccount */,
|
||||
84245C7D1FDDD2580074AFBB /* Feedbin */,
|
||||
848935031F62484F00CEBD24 /* AccountTests */,
|
||||
|
@ -524,9 +541,11 @@
|
|||
841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */,
|
||||
5165D73122837F3400D9D53D /* InitialFeedDownloader.swift in Sources */,
|
||||
846E77541F6F00E300A165E2 /* AccountManager.swift in Sources */,
|
||||
515E4EB72324FF8C0057B0E7 /* Credentials.swift in Sources */,
|
||||
51E490362288C37100C791F0 /* FeedbinDate.swift in Sources */,
|
||||
5165D72922835F7A00D9D53D /* FeedSpecifier.swift in Sources */,
|
||||
844B297D2106C7EC004020B3 /* Feed.swift in Sources */,
|
||||
515E4EB62324FF8C0057B0E7 /* URLRequest+RSWeb.swift in Sources */,
|
||||
5154367B228EEB28005E1CDF /* FeedbinImportResult.swift in Sources */,
|
||||
84B2D4D02238CD8A00498ADA /* FeedMetadata.swift in Sources */,
|
||||
5144EA49227B497600D19003 /* FeedbinAPICaller.swift in Sources */,
|
||||
|
@ -542,6 +561,7 @@
|
|||
5165D72A22835F7D00D9D53D /* HTMLFeedFinder.swift in Sources */,
|
||||
841974011F6DD1EC006346C4 /* Folder.swift in Sources */,
|
||||
846E774F1F6EF9C000A165E2 /* LocalAccountDelegate.swift in Sources */,
|
||||
515E4EB52324FF8C0057B0E7 /* CredentialsManager.swift in Sources */,
|
||||
844B297F210CE37E004020B3 /* UnreadCountProvider.swift in Sources */,
|
||||
84F1F06E2243524700DA0616 /* AccountMetadata.swift in Sources */,
|
||||
84245C851FDDD8CB0074AFBB /* FeedbinSubscription.swift in Sources */,
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// Credentials.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Brent Simmons on 12/9/17.
|
||||
// Copyright © 2017 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum CredentialsError: Error {
|
||||
case incompleteCredentials
|
||||
case unhandledError(status: OSStatus)
|
||||
}
|
||||
|
||||
public enum Credentials {
|
||||
case basic(username: String, password: String)
|
||||
case readerAPIBasicLogin(username: String, password: String)
|
||||
case readerAPIAuthLogin(username: String, apiKey: String)
|
||||
case oauthAccessToken(username: String, token: String)
|
||||
case oauthRefreshToken(username: String, token: String)
|
||||
}
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
//
|
||||
// CredentialsManager.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Maurice Parker on 5/5/19.
|
||||
// Copyright © 2019 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct CredentialsManager {
|
||||
|
||||
public static func storeCredentials(_ credentials: Credentials, server: String) throws {
|
||||
|
||||
switch credentials {
|
||||
case .basic(let username, let password):
|
||||
try storeBasicCredentials(server: server, username: username, password: password)
|
||||
case .readerAPIBasicLogin(let username, let password):
|
||||
try storeBasicCredentials(server: server, username: username, password: password)
|
||||
case .readerAPIAuthLogin(let username, let apiKey):
|
||||
try storeBasicCredentials(server: server, username: username, password: apiKey)
|
||||
case .oauthAccessToken(let username, let token):
|
||||
try storeBasicCredentials(server: server, username: username, password: token)
|
||||
case .oauthRefreshToken(let username, let token):
|
||||
try storeBasicCredentials(server: server, username: username, password: token)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static func retrieveBasicCredentials(server: String, username: String) throws -> Credentials? {
|
||||
|
||||
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 Credentials.basic(username: username, password: password)
|
||||
|
||||
}
|
||||
|
||||
public static func removeBasicCredentials(server: String, username: String) throws {
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static func retrieveReaderAPIAuthCredentials(server: String, username: String) throws -> Credentials? {
|
||||
|
||||
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 Credentials.readerAPIAuthLogin(username: username, apiKey: password)
|
||||
|
||||
}
|
||||
|
||||
public static func removeReaderAPIAuthCredentials(server: String, username: String) throws {
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
extension CredentialsManager {
|
||||
|
||||
static func storeBasicCredentials(server: String, username: String, password: String) throws {
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
//
|
||||
// URLRequest+RSWeb.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Brent Simmons on 12/27/16.
|
||||
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import RSWeb
|
||||
|
||||
public extension URLRequest {
|
||||
|
||||
init(url: URL, credentials: Credentials?, conditionalGet: HTTPConditionalGetInfo? = nil) {
|
||||
|
||||
self.init(url: url)
|
||||
|
||||
guard let credentials = credentials else {
|
||||
return
|
||||
}
|
||||
|
||||
switch credentials {
|
||||
case .basic(let username, let password):
|
||||
let data = "\(username):\(password)".data(using: .utf8)
|
||||
let base64 = data?.base64EncodedString()
|
||||
let auth = "Basic \(base64 ?? "")"
|
||||
setValue(auth, forHTTPHeaderField: HTTPRequestHeader.authorization)
|
||||
case .readerAPIBasicLogin(let username, let password):
|
||||
setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
|
||||
httpMethod = "POST"
|
||||
let postData = "Email=\(username)&Passwd=\(password)"
|
||||
httpBody = postData.data(using: String.Encoding.utf8)
|
||||
case .readerAPIAuthLogin(_, let apiKey):
|
||||
let auth = "GoogleLogin auth=\(apiKey)"
|
||||
setValue(auth, forHTTPHeaderField: HTTPRequestHeader.authorization)
|
||||
case .oauthAccessToken(_, let token):
|
||||
let auth = "OAuth \(token)"
|
||||
setValue(auth, forHTTPHeaderField: "Authorization")
|
||||
case .oauthRefreshToken:
|
||||
// While both access and refresh tokens are credentials, it seems the `Credentials` cases
|
||||
// enumerates how the identity of the user can be proved rather than
|
||||
// credentials-in-general, such as in this refresh token case,
|
||||
// the authority to prove an identity.
|
||||
// TODO: Refactor as usage becomes clearer.
|
||||
assertionFailure("Refresh tokens are used to replace expired access tokens. Did you mean to use `accessToken` instead?")
|
||||
break
|
||||
}
|
||||
|
||||
guard let conditionalGet = conditionalGet else {
|
||||
return
|
||||
}
|
||||
|
||||
// Bug seen in the wild: lastModified with last possible 32-bit date, which is in 2038. Ignore those.
|
||||
// TODO: drop this check in late 2037.
|
||||
if let lastModified = conditionalGet.lastModified, !lastModified.contains("2038") {
|
||||
setValue(lastModified, forHTTPHeaderField: HTTPRequestHeader.ifModifiedSince)
|
||||
}
|
||||
if let etag = conditionalGet.etag {
|
||||
setValue(etag, forHTTPHeaderField: HTTPRequestHeader.ifNoneMatch)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1027,6 +1027,14 @@
|
|||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
515E4EA82324FF710057B0E7 /* Credentials */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
name = Credentials;
|
||||
path = ../Frameworks/Account/Credentials;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5183CCDB226F1EEB0010922C /* Progress */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1579,6 +1587,7 @@
|
|||
84C9FC6822629C9A00D921D6 /* Shared */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
515E4EA82324FF710057B0E7 /* Credentials */,
|
||||
846E77301F6EF5D600A165E2 /* Account.xcodeproj */,
|
||||
841D4D542106B3D500DD04E6 /* Articles.xcodeproj */,
|
||||
841D4D5E2106B3E100DD04E6 /* ArticlesDatabase.xcodeproj */,
|
||||
|
@ -1951,12 +1960,12 @@
|
|||
ORGANIZATIONNAME = "Ranchero Software";
|
||||
TargetAttributes = {
|
||||
6581C73220CED60000F4AD34 = {
|
||||
DevelopmentTeam = M8L2WTLA8W;
|
||||
ProvisioningStyle = Manual;
|
||||
DevelopmentTeam = SHJK2V3AJG;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
840D617B2029031C009BC708 = {
|
||||
CreatedOnToolsVersion = 9.3;
|
||||
DevelopmentTeam = M8L2WTLA8W;
|
||||
DevelopmentTeam = SHJK2V3AJG;
|
||||
ProvisioningStyle = Automatic;
|
||||
SystemCapabilities = {
|
||||
com.apple.BackgroundModes = {
|
||||
|
@ -1972,8 +1981,8 @@
|
|||
};
|
||||
849C645F1ED37A5D003D8FC0 = {
|
||||
CreatedOnToolsVersion = 8.2.1;
|
||||
DevelopmentTeam = M8L2WTLA8W;
|
||||
ProvisioningStyle = Manual;
|
||||
DevelopmentTeam = SHJK2V3AJG;
|
||||
ProvisioningStyle = Automatic;
|
||||
SystemCapabilities = {
|
||||
com.apple.HardenedRuntime = {
|
||||
enabled = 1;
|
||||
|
@ -1982,7 +1991,7 @@
|
|||
};
|
||||
849C64701ED37A5D003D8FC0 = {
|
||||
CreatedOnToolsVersion = 8.2.1;
|
||||
DevelopmentTeam = 9C84TZ7Q6Z;
|
||||
DevelopmentTeam = SHJK2V3AJG;
|
||||
ProvisioningStyle = Automatic;
|
||||
TestTargetID = 849C645F1ED37A5D003D8FC0;
|
||||
};
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit bbb58ff2afb539ff65816793754933ae9db8f259
|
||||
Subproject commit 168ce1a628847d986d032247498b00293e7659f2
|
Loading…
Reference in New Issue