Get rid of SecretsManager. It wasn’t thread-safe, and it existed only for tests (and it wasn’t thread-safe for tests either). Pass SecretsProvider parameter where it’s needed.
This commit is contained in:
parent
13403df8f1
commit
78047fcaf7
@ -258,7 +258,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||||||
return delegate.refreshProgress
|
return delegate.refreshProgress
|
||||||
}
|
}
|
||||||
|
|
||||||
init(dataFolder: String, type: AccountType, accountID: String, transport: Transport? = nil) {
|
init(dataFolder: String, type: AccountType, accountID: String, secretsProvider: SecretsProvider, transport: Transport? = nil) {
|
||||||
switch type {
|
switch type {
|
||||||
case .onMyMac:
|
case .onMyMac:
|
||||||
self.delegate = LocalAccountDelegate()
|
self.delegate = LocalAccountDelegate()
|
||||||
@ -267,17 +267,17 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||||||
case .feedbin:
|
case .feedbin:
|
||||||
self.delegate = FeedbinAccountDelegate(dataFolder: dataFolder, transport: transport)
|
self.delegate = FeedbinAccountDelegate(dataFolder: dataFolder, transport: transport)
|
||||||
case .feedly:
|
case .feedly:
|
||||||
self.delegate = FeedlyAccountDelegate(dataFolder: dataFolder, transport: transport, api: FeedlyAccountDelegate.environment)
|
self.delegate = FeedlyAccountDelegate(dataFolder: dataFolder, transport: transport, api: FeedlyAccountDelegate.environment, secretsProvider: secretsProvider)
|
||||||
case .newsBlur:
|
case .newsBlur:
|
||||||
self.delegate = NewsBlurAccountDelegate(dataFolder: dataFolder, transport: transport)
|
self.delegate = NewsBlurAccountDelegate(dataFolder: dataFolder, transport: transport)
|
||||||
case .freshRSS:
|
case .freshRSS:
|
||||||
self.delegate = ReaderAPIAccountDelegate(dataFolder: dataFolder, transport: transport, variant: .freshRSS)
|
self.delegate = ReaderAPIAccountDelegate(dataFolder: dataFolder, transport: transport, variant: .freshRSS, secretsProvider: secretsProvider)
|
||||||
case .inoreader:
|
case .inoreader:
|
||||||
self.delegate = ReaderAPIAccountDelegate(dataFolder: dataFolder, transport: transport, variant: .inoreader)
|
self.delegate = ReaderAPIAccountDelegate(dataFolder: dataFolder, transport: transport, variant: .inoreader, secretsProvider: secretsProvider)
|
||||||
case .bazQux:
|
case .bazQux:
|
||||||
self.delegate = ReaderAPIAccountDelegate(dataFolder: dataFolder, transport: transport, variant: .bazQux)
|
self.delegate = ReaderAPIAccountDelegate(dataFolder: dataFolder, transport: transport, variant: .bazQux, secretsProvider: secretsProvider)
|
||||||
case .theOldReader:
|
case .theOldReader:
|
||||||
self.delegate = ReaderAPIAccountDelegate(dataFolder: dataFolder, transport: transport, variant: .theOldReader)
|
self.delegate = ReaderAPIAccountDelegate(dataFolder: dataFolder, transport: transport, variant: .theOldReader, secretsProvider: secretsProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.delegate.accountMetadata = metadata
|
self.delegate.accountMetadata = metadata
|
||||||
@ -367,29 +367,29 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||||||
try CredentialsManager.removeCredentials(type: type, server: server, username: username)
|
try CredentialsManager.removeCredentials(type: type, server: server, username: username)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func validateCredentials(transport: Transport = URLSession.webserviceTransport(), type: AccountType, credentials: Credentials, endpoint: URL? = nil, completion: @escaping (Result<Credentials?, Error>) -> Void) {
|
public static func validateCredentials(transport: Transport = URLSession.webserviceTransport(), type: AccountType, credentials: Credentials, endpoint: URL? = nil, secretsProvider: SecretsProvider, completion: @escaping (Result<Credentials?, Error>) -> Void) {
|
||||||
switch type {
|
switch type {
|
||||||
case .feedbin:
|
case .feedbin:
|
||||||
FeedbinAccountDelegate.validateCredentials(transport: transport, credentials: credentials, completion: completion)
|
FeedbinAccountDelegate.validateCredentials(transport: transport, credentials: credentials, secretsProvider: secretsProvider, completion: completion)
|
||||||
case .newsBlur:
|
case .newsBlur:
|
||||||
NewsBlurAccountDelegate.validateCredentials(transport: transport, credentials: credentials, completion: completion)
|
NewsBlurAccountDelegate.validateCredentials(transport: transport, credentials: credentials, secretsProvider: secretsProvider, completion: completion)
|
||||||
case .freshRSS, .inoreader, .bazQux, .theOldReader:
|
case .freshRSS, .inoreader, .bazQux, .theOldReader:
|
||||||
ReaderAPIAccountDelegate.validateCredentials(transport: transport, credentials: credentials, endpoint: endpoint, completion: completion)
|
ReaderAPIAccountDelegate.validateCredentials(transport: transport, credentials: credentials, endpoint: endpoint, secretsProvider: secretsProvider, completion: completion)
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static func oauthAuthorizationClient(for type: AccountType) -> OAuthAuthorizationClient {
|
internal static func oauthAuthorizationClient(for type: AccountType, secretsProvider: SecretsProvider) -> OAuthAuthorizationClient {
|
||||||
switch type {
|
switch type {
|
||||||
case .feedly:
|
case .feedly:
|
||||||
return FeedlyAccountDelegate.environment.oauthAuthorizationClient
|
return FeedlyAccountDelegate.environment.oauthAuthorizationClient(secretsProvider: secretsProvider)
|
||||||
default:
|
default:
|
||||||
fatalError("\(type) is not a client for OAuth authorization code granting.")
|
fatalError("\(type) is not a client for OAuth authorization code granting.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func oauthAuthorizationCodeGrantRequest(for type: AccountType) -> URLRequest {
|
public static func oauthAuthorizationCodeGrantRequest(for type: AccountType, secretsProvider: SecretsProvider) -> URLRequest {
|
||||||
let grantingType: OAuthAuthorizationGranting.Type
|
let grantingType: OAuthAuthorizationGranting.Type
|
||||||
switch type {
|
switch type {
|
||||||
case .feedly:
|
case .feedly:
|
||||||
@ -398,13 +398,14 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||||||
fatalError("\(type) does not support OAuth authorization code granting.")
|
fatalError("\(type) does not support OAuth authorization code granting.")
|
||||||
}
|
}
|
||||||
|
|
||||||
return grantingType.oauthAuthorizationCodeGrantRequest()
|
return grantingType.oauthAuthorizationCodeGrantRequest(secretsProvider: secretsProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func requestOAuthAccessToken(with response: OAuthAuthorizationResponse,
|
public static func requestOAuthAccessToken(with response: OAuthAuthorizationResponse,
|
||||||
client: OAuthAuthorizationClient,
|
client: OAuthAuthorizationClient,
|
||||||
accountType: AccountType,
|
accountType: AccountType,
|
||||||
transport: Transport = URLSession.webserviceTransport(),
|
transport: Transport = URLSession.webserviceTransport(),
|
||||||
|
secretsProvider: SecretsProvider,
|
||||||
completion: @escaping (Result<OAuthAuthorizationGrant, Error>) -> ()) {
|
completion: @escaping (Result<OAuthAuthorizationGrant, Error>) -> ()) {
|
||||||
let grantingType: OAuthAuthorizationGranting.Type
|
let grantingType: OAuthAuthorizationGranting.Type
|
||||||
|
|
||||||
@ -415,7 +416,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||||||
fatalError("\(accountType) does not support OAuth authorization code granting.")
|
fatalError("\(accountType) does not support OAuth authorization code granting.")
|
||||||
}
|
}
|
||||||
|
|
||||||
grantingType.requestOAuthAccessToken(with: response, transport: transport, completion: completion)
|
grantingType.requestOAuthAccessToken(with: response, transport: transport, secretsProvider: secretsProvider, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func receiveRemoteNotification(userInfo: [AnyHashable : Any], completion: @escaping () -> Void) {
|
public func receiveRemoteNotification(userInfo: [AnyHashable : Any], completion: @escaping () -> Void) {
|
||||||
|
@ -52,7 +52,7 @@ protocol AccountDelegate {
|
|||||||
|
|
||||||
func accountWillBeDeleted(_ account: Account)
|
func accountWillBeDeleted(_ account: Account)
|
||||||
|
|
||||||
static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL?, completion: @escaping (Result<Credentials?, Error>) -> Void)
|
static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL?, secretsProvider: SecretsProvider, completion: @escaping (Result<Credentials?, Error>) -> Void)
|
||||||
|
|
||||||
/// Suspend all network activity
|
/// Suspend all network activity
|
||||||
func suspendNetwork()
|
func suspendNetwork()
|
||||||
|
@ -12,6 +12,7 @@ import RSWeb
|
|||||||
import Articles
|
import Articles
|
||||||
import ArticlesDatabase
|
import ArticlesDatabase
|
||||||
import Database
|
import Database
|
||||||
|
import Secrets
|
||||||
|
|
||||||
// Main thread only.
|
// Main thread only.
|
||||||
|
|
||||||
@ -29,6 +30,8 @@ public final class AccountManager: UnreadCountProvider {
|
|||||||
private let defaultAccountFolderName = "OnMyMac"
|
private let defaultAccountFolderName = "OnMyMac"
|
||||||
private let defaultAccountIdentifier = "OnMyMac"
|
private let defaultAccountIdentifier = "OnMyMac"
|
||||||
|
|
||||||
|
private let secretsProvider: SecretsProvider
|
||||||
|
|
||||||
public var isSuspended = false
|
public var isSuspended = false
|
||||||
public var isUnreadCountsInitialized: Bool {
|
public var isUnreadCountsInitialized: Bool {
|
||||||
for account in activeAccounts {
|
for account in activeAccounts {
|
||||||
@ -94,9 +97,11 @@ public final class AccountManager: UnreadCountProvider {
|
|||||||
return CombinedRefreshProgress(downloadProgressArray: downloadProgressArray)
|
return CombinedRefreshProgress(downloadProgressArray: downloadProgressArray)
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(accountsFolder: String) {
|
public init(accountsFolder: String, secretsProvider: SecretsProvider) {
|
||||||
self.accountsFolder = accountsFolder
|
|
||||||
|
|
||||||
|
self.accountsFolder = accountsFolder
|
||||||
|
self.secretsProvider = secretsProvider
|
||||||
|
|
||||||
// The local "On My Mac" account must always exist, even if it's empty.
|
// The local "On My Mac" account must always exist, even if it's empty.
|
||||||
let localAccountFolder = (accountsFolder as NSString).appendingPathComponent("OnMyMac")
|
let localAccountFolder = (accountsFolder as NSString).appendingPathComponent("OnMyMac")
|
||||||
do {
|
do {
|
||||||
@ -107,7 +112,7 @@ public final class AccountManager: UnreadCountProvider {
|
|||||||
abort()
|
abort()
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultAccount = Account(dataFolder: localAccountFolder, type: .onMyMac, accountID: defaultAccountIdentifier)
|
defaultAccount = Account(dataFolder: localAccountFolder, type: .onMyMac, accountID: defaultAccountIdentifier, secretsProvider: secretsProvider)
|
||||||
accountsDictionary[defaultAccount.accountID] = defaultAccount
|
accountsDictionary[defaultAccount.accountID] = defaultAccount
|
||||||
|
|
||||||
readAccountsFromDisk()
|
readAccountsFromDisk()
|
||||||
@ -134,7 +139,7 @@ public final class AccountManager: UnreadCountProvider {
|
|||||||
abort()
|
abort()
|
||||||
}
|
}
|
||||||
|
|
||||||
let account = Account(dataFolder: accountFolder, type: type, accountID: accountID)
|
let account = Account(dataFolder: accountFolder, type: type, accountID: accountID, secretsProvider: secretsProvider)
|
||||||
accountsDictionary[accountID] = account
|
accountsDictionary[accountID] = account
|
||||||
|
|
||||||
var userInfo = [String: Any]()
|
var userInfo = [String: Any]()
|
||||||
@ -430,7 +435,7 @@ private extension AccountManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadAccount(_ accountSpecifier: AccountSpecifier) -> Account? {
|
func loadAccount(_ accountSpecifier: AccountSpecifier) -> Account? {
|
||||||
return Account(dataFolder: accountSpecifier.folderPath, type: accountSpecifier.type, accountID: accountSpecifier.identifier)
|
return Account(dataFolder: accountSpecifier.folderPath, type: accountSpecifier.type, accountID: accountSpecifier.identifier, secretsProvider: secretsProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadAccount(_ filename: String) -> Account? {
|
func loadAccount(_ filename: String) -> Account? {
|
||||||
|
@ -451,7 +451,7 @@ final class CloudKitAccountDelegate: AccountDelegate {
|
|||||||
articlesZone.resetChangeToken()
|
articlesZone.resetChangeToken()
|
||||||
}
|
}
|
||||||
|
|
||||||
static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL? = nil, completion: (Result<Credentials?, Error>) -> Void) {
|
static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL? = nil, secretsProvider: SecretsProvider, completion: (Result<Credentials?, Error>) -> Void) {
|
||||||
return completion(.success(nil))
|
return completion(.success(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -583,7 +583,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
|||||||
func accountWillBeDeleted(_ account: Account) {
|
func accountWillBeDeleted(_ account: Account) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL? = nil, completion: @escaping (Result<Credentials?, Error>) -> Void) {
|
static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL? = nil, secretsProvider: SecretsProvider, completion: @escaping (Result<Credentials?, Error>) -> Void) {
|
||||||
|
|
||||||
let caller = FeedbinAPICaller(transport: transport)
|
let caller = FeedbinAPICaller(transport: transport)
|
||||||
caller.credentials = credentials
|
caller.credentials = credentials
|
||||||
|
@ -37,12 +37,12 @@ final class FeedlyAPICaller {
|
|||||||
return components
|
return components
|
||||||
}
|
}
|
||||||
|
|
||||||
var oauthAuthorizationClient: OAuthAuthorizationClient {
|
func oauthAuthorizationClient(secretsProvider: SecretsProvider) -> OAuthAuthorizationClient {
|
||||||
switch self {
|
switch self {
|
||||||
case .sandbox:
|
case .sandbox:
|
||||||
return .feedlySandboxClient
|
return .feedlySandboxClient
|
||||||
case .cloud:
|
case .cloud:
|
||||||
return .feedlyCloudClient
|
return OAuthAuthorizationClient.feedlyCloudClient(secretsProvider: secretsProvider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,11 +50,13 @@ final class FeedlyAPICaller {
|
|||||||
private let transport: Transport
|
private let transport: Transport
|
||||||
private let baseUrlComponents: URLComponents
|
private let baseUrlComponents: URLComponents
|
||||||
private let uriComponentAllowed: CharacterSet
|
private let uriComponentAllowed: CharacterSet
|
||||||
|
private let secretsProvider: SecretsProvider
|
||||||
|
|
||||||
init(transport: Transport, api: API) {
|
init(transport: Transport, api: API, secretsProvider: SecretsProvider) {
|
||||||
self.transport = transport
|
self.transport = transport
|
||||||
self.baseUrlComponents = api.baseUrlComponents
|
self.baseUrlComponents = api.baseUrlComponents
|
||||||
|
self.secretsProvider = secretsProvider
|
||||||
|
|
||||||
var urlHostAllowed = CharacterSet.urlHostAllowed
|
var urlHostAllowed = CharacterSet.urlHostAllowed
|
||||||
urlHostAllowed.remove("+")
|
urlHostAllowed.remove("+")
|
||||||
uriComponentAllowed = urlHostAllowed
|
uriComponentAllowed = urlHostAllowed
|
||||||
|
@ -25,11 +25,11 @@ public struct FeedlyOAuthAccessTokenResponse: Decodable, OAuthAccessTokenRespons
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension FeedlyAccountDelegate: OAuthAuthorizationGranting {
|
extension FeedlyAccountDelegate: OAuthAuthorizationGranting {
|
||||||
|
|
||||||
private static let oauthAuthorizationGrantScope = "https://cloud.feedly.com/subscriptions"
|
private static let oauthAuthorizationGrantScope = "https://cloud.feedly.com/subscriptions"
|
||||||
|
|
||||||
static func oauthAuthorizationCodeGrantRequest() -> URLRequest {
|
static func oauthAuthorizationCodeGrantRequest(secretsProvider: SecretsProvider) -> URLRequest {
|
||||||
let client = environment.oauthAuthorizationClient
|
let client = environment.oauthAuthorizationClient(secretsProvider: secretsProvider)
|
||||||
let authorizationRequest = OAuthAuthorizationRequest(clientId: client.id,
|
let authorizationRequest = OAuthAuthorizationRequest(clientId: client.id,
|
||||||
redirectUri: client.redirectUri,
|
redirectUri: client.redirectUri,
|
||||||
scope: oauthAuthorizationGrantScope,
|
scope: oauthAuthorizationGrantScope,
|
||||||
@ -38,12 +38,12 @@ extension FeedlyAccountDelegate: OAuthAuthorizationGranting {
|
|||||||
return FeedlyAPICaller.authorizationCodeUrlRequest(for: authorizationRequest, baseUrlComponents: baseURLComponents)
|
return FeedlyAPICaller.authorizationCodeUrlRequest(for: authorizationRequest, baseUrlComponents: baseURLComponents)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func requestOAuthAccessToken(with response: OAuthAuthorizationResponse, transport: Transport, completion: @escaping (Result<OAuthAuthorizationGrant, Error>) -> ()) {
|
static func requestOAuthAccessToken(with response: OAuthAuthorizationResponse, transport: Transport, secretsProvider: SecretsProvider, completion: @escaping (Result<OAuthAuthorizationGrant, Error>) -> ()) {
|
||||||
let client = environment.oauthAuthorizationClient
|
let client = environment.oauthAuthorizationClient(secretsProvider: secretsProvider)
|
||||||
let request = OAuthAccessTokenRequest(authorizationResponse: response,
|
let request = OAuthAccessTokenRequest(authorizationResponse: response,
|
||||||
scope: oauthAuthorizationGrantScope,
|
scope: oauthAuthorizationGrantScope,
|
||||||
client: client)
|
client: client)
|
||||||
let caller = FeedlyAPICaller(transport: transport, api: environment)
|
let caller = FeedlyAPICaller(transport: transport, api: environment, secretsProvider: secretsProvider)
|
||||||
caller.requestAccessToken(request) { result in
|
caller.requestAccessToken(request) { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let response):
|
case .success(let response):
|
||||||
|
@ -67,15 +67,15 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
|||||||
private weak var currentSyncAllOperation: MainThreadOperation?
|
private weak var currentSyncAllOperation: MainThreadOperation?
|
||||||
private let operationQueue = MainThreadOperationQueue()
|
private let operationQueue = MainThreadOperationQueue()
|
||||||
|
|
||||||
init(dataFolder: String, transport: Transport?, api: FeedlyAPICaller.API) {
|
init(dataFolder: String, transport: Transport?, api: FeedlyAPICaller.API, secretsProvider: SecretsProvider) {
|
||||||
// Many operations have their own operation queues, such as the sync all operation.
|
// Many operations have their own operation queues, such as the sync all operation.
|
||||||
// Making this a serial queue at this higher level of abstraction means we can ensure,
|
// Making this a serial queue at this higher level of abstraction means we can ensure,
|
||||||
// for example, a `FeedlyRefreshAccessTokenOperation` occurs before a `FeedlySyncAllOperation`,
|
// for example, a `FeedlyRefreshAccessTokenOperation` occurs before a `FeedlySyncAllOperation`,
|
||||||
// improving our ability to debug, reason about and predict the behaviour of the code.
|
// improving our ability to debug, reason about and predict the behaviour of the code.
|
||||||
|
|
||||||
if let transport = transport {
|
if let transport = transport {
|
||||||
self.caller = FeedlyAPICaller(transport: transport, api: api)
|
self.caller = FeedlyAPICaller(transport: transport, api: api, secretsProvider: secretsProvider)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
let sessionConfiguration = URLSessionConfiguration.default
|
let sessionConfiguration = URLSessionConfiguration.default
|
||||||
@ -92,12 +92,12 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let session = URLSession(configuration: sessionConfiguration)
|
let session = URLSession(configuration: sessionConfiguration)
|
||||||
self.caller = FeedlyAPICaller(transport: session, api: api)
|
self.caller = FeedlyAPICaller(transport: session, api: api, secretsProvider: secretsProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
let databaseFilePath = (dataFolder as NSString).appendingPathComponent("Sync.sqlite3")
|
let databaseFilePath = (dataFolder as NSString).appendingPathComponent("Sync.sqlite3")
|
||||||
self.database = SyncDatabase(databaseFilePath: databaseFilePath)
|
self.database = SyncDatabase(databaseFilePath: databaseFilePath)
|
||||||
self.oauthAuthorizationClient = api.oauthAuthorizationClient
|
self.oauthAuthorizationClient = api.oauthAuthorizationClient(secretsProvider: secretsProvider)
|
||||||
|
|
||||||
self.caller.delegate = self
|
self.caller.delegate = self
|
||||||
}
|
}
|
||||||
@ -539,7 +539,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
|||||||
MainThreadOperationQueue.shared.add(logout)
|
MainThreadOperationQueue.shared.add(logout)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL?, completion: @escaping (Result<Credentials?, Error>) -> Void) {
|
static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL?, secretsProvider: SecretsProvider, completion: @escaping (Result<Credentials?, Error>) -> Void) {
|
||||||
assertionFailure("An `account` instance should enqueue an \(FeedlyRefreshAccessTokenOperation.self) instead.")
|
assertionFailure("An `account` instance should enqueue an \(FeedlyRefreshAccessTokenOperation.self) instead.")
|
||||||
completion(.success(credentials))
|
completion(.success(credentials))
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import AuthenticationServices
|
import AuthenticationServices
|
||||||
import RSCore
|
import RSCore
|
||||||
|
import Secrets
|
||||||
|
|
||||||
public protocol OAuthAccountAuthorizationOperationDelegate: AnyObject {
|
public protocol OAuthAccountAuthorizationOperationDelegate: AnyObject {
|
||||||
func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didCreate account: Account)
|
func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didCreate account: Account)
|
||||||
@ -42,17 +43,19 @@ public enum OAuthAccountAuthorizationOperationError: LocalizedError {
|
|||||||
private let accountType: AccountType
|
private let accountType: AccountType
|
||||||
private let oauthClient: OAuthAuthorizationClient
|
private let oauthClient: OAuthAuthorizationClient
|
||||||
private var session: ASWebAuthenticationSession?
|
private var session: ASWebAuthenticationSession?
|
||||||
|
private let secretsProvider: SecretsProvider
|
||||||
public init(accountType: AccountType) {
|
|
||||||
|
public init(accountType: AccountType, secretsProvider: SecretsProvider) {
|
||||||
self.accountType = accountType
|
self.accountType = accountType
|
||||||
self.oauthClient = Account.oauthAuthorizationClient(for: accountType)
|
self.secretsProvider = secretsProvider
|
||||||
|
self.oauthClient = Account.oauthAuthorizationClient(for: accountType, secretsProvider: secretsProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func run() {
|
public func run() {
|
||||||
assert(presentationAnchor != nil, "\(self) outlived presentation anchor.")
|
assert(presentationAnchor != nil, "\(self) outlived presentation anchor.")
|
||||||
|
|
||||||
let request = Account.oauthAuthorizationCodeGrantRequest(for: accountType)
|
let request = Account.oauthAuthorizationCodeGrantRequest(for: accountType, secretsProvider: secretsProvider)
|
||||||
|
|
||||||
guard let url = request.url else {
|
guard let url = request.url else {
|
||||||
return DispatchQueue.main.async {
|
return DispatchQueue.main.async {
|
||||||
self.didEndAuthentication(url: nil, error: URLError(.badURL))
|
self.didEndAuthentication(url: nil, error: URLError(.badURL))
|
||||||
@ -113,7 +116,7 @@ public enum OAuthAccountAuthorizationOperationError: LocalizedError {
|
|||||||
|
|
||||||
let response = try OAuthAuthorizationResponse(url: url, client: oauthClient)
|
let response = try OAuthAuthorizationResponse(url: url, client: oauthClient)
|
||||||
|
|
||||||
Account.requestOAuthAccessToken(with: response, client: oauthClient, accountType: accountType, completion: didEndRequestingAccessToken(_:))
|
Account.requestOAuthAccessToken(with: response, client: oauthClient, accountType: accountType, secretsProvider: secretsProvider, completion: didEndRequestingAccessToken(_:))
|
||||||
|
|
||||||
} catch is ASWebAuthenticationSessionError {
|
} catch is ASWebAuthenticationSessionError {
|
||||||
didFinish() // Primarily, cancellation.
|
didFinish() // Primarily, cancellation.
|
||||||
|
@ -11,14 +11,14 @@ import Secrets
|
|||||||
|
|
||||||
extension OAuthAuthorizationClient {
|
extension OAuthAuthorizationClient {
|
||||||
|
|
||||||
static var feedlyCloudClient: OAuthAuthorizationClient {
|
static func feedlyCloudClient(secretsProvider: SecretsProvider) -> OAuthAuthorizationClient {
|
||||||
/// Models private NetNewsWire client secrets.
|
/// Models private NetNewsWire client secrets.
|
||||||
/// These placeholders are substituted at build time using a Run Script phase with build settings.
|
/// These placeholders are substituted at build time using a Run Script phase with build settings.
|
||||||
/// https://developer.feedly.com/v3/auth/#authenticating-a-user-and-obtaining-an-auth-code
|
/// https://developer.feedly.com/v3/auth/#authenticating-a-user-and-obtaining-an-auth-code
|
||||||
return OAuthAuthorizationClient(id: SecretsManager.provider.feedlyClientId,
|
return OAuthAuthorizationClient(id: secretsProvider.feedlyClientId,
|
||||||
redirectUri: "netnewswire://auth/feedly",
|
redirectUri: "netnewswire://auth/feedly",
|
||||||
state: nil,
|
state: nil,
|
||||||
secret: SecretsManager.provider.feedlyClientSecret)
|
secret: secretsProvider.feedlyClientSecret)
|
||||||
}
|
}
|
||||||
|
|
||||||
static var feedlySandboxClient: OAuthAuthorizationClient {
|
static var feedlySandboxClient: OAuthAuthorizationClient {
|
||||||
|
@ -17,7 +17,7 @@ public struct OAuthAuthorizationClient: Equatable {
|
|||||||
public var redirectUri: String
|
public var redirectUri: String
|
||||||
public var state: String?
|
public var state: String?
|
||||||
public var secret: String
|
public var secret: String
|
||||||
|
|
||||||
public init(id: String, redirectUri: String, state: String?, secret: String) {
|
public init(id: String, redirectUri: String, state: String?, secret: String) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.redirectUri = redirectUri
|
self.redirectUri = redirectUri
|
||||||
@ -167,7 +167,7 @@ public protocol OAuthAuthorizationCodeGrantRequesting {
|
|||||||
|
|
||||||
protocol OAuthAuthorizationGranting: AccountDelegate {
|
protocol OAuthAuthorizationGranting: AccountDelegate {
|
||||||
|
|
||||||
static func oauthAuthorizationCodeGrantRequest() -> URLRequest
|
static func oauthAuthorizationCodeGrantRequest(secretsProvider: SecretsProvider) -> URLRequest
|
||||||
|
|
||||||
static func requestOAuthAccessToken(with response: OAuthAuthorizationResponse, transport: Transport, completion: @escaping (Result<OAuthAuthorizationGrant, Error>) -> ())
|
static func requestOAuthAccessToken(with response: OAuthAuthorizationResponse, transport: Transport, secretsProvider: SecretsProvider, completion: @escaping (Result<OAuthAuthorizationGrant, Error>) -> ())
|
||||||
}
|
}
|
||||||
|
@ -197,7 +197,7 @@ final class LocalAccountDelegate: AccountDelegate {
|
|||||||
func accountWillBeDeleted(_ account: Account) {
|
func accountWillBeDeleted(_ account: Account) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL? = nil, completion: (Result<Credentials?, Error>) -> Void) {
|
static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL? = nil, secretsProvider: SecretsProvider, completion: (Result<Credentials?, Error>) -> Void) {
|
||||||
return completion(.success(nil))
|
return completion(.success(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -612,7 +612,7 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
|||||||
caller.logout() { _ in }
|
caller.logout() { _ in }
|
||||||
}
|
}
|
||||||
|
|
||||||
class func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL? = nil, completion: @escaping (Result<Credentials?, Error>) -> ()) {
|
class func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL? = nil, secretsProvider: SecretsProvider, completion: @escaping (Result<Credentials?, Error>) -> ()) {
|
||||||
let caller = NewsBlurAPICaller(transport: transport)
|
let caller = NewsBlurAPICaller(transport: transport)
|
||||||
caller.credentials = credentials
|
caller.credentials = credentials
|
||||||
caller.validateCredentials() { result in
|
caller.validateCredentials() { result in
|
||||||
|
@ -74,12 +74,12 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
|||||||
|
|
||||||
var refreshProgress = DownloadProgress(numberOfTasks: 0)
|
var refreshProgress = DownloadProgress(numberOfTasks: 0)
|
||||||
|
|
||||||
init(dataFolder: String, transport: Transport?, variant: ReaderAPIVariant) {
|
init(dataFolder: String, transport: Transport?, variant: ReaderAPIVariant, secretsProvider: SecretsProvider) {
|
||||||
let databaseFilePath = (dataFolder as NSString).appendingPathComponent("Sync.sqlite3")
|
let databaseFilePath = (dataFolder as NSString).appendingPathComponent("Sync.sqlite3")
|
||||||
database = SyncDatabase(databaseFilePath: databaseFilePath)
|
database = SyncDatabase(databaseFilePath: databaseFilePath)
|
||||||
|
|
||||||
if transport != nil {
|
if transport != nil {
|
||||||
caller = ReaderAPICaller(transport: transport!)
|
caller = ReaderAPICaller(transport: transport!, secretsProvider: secretsProvider)
|
||||||
} else {
|
} else {
|
||||||
let sessionConfiguration = URLSessionConfiguration.default
|
let sessionConfiguration = URLSessionConfiguration.default
|
||||||
sessionConfiguration.requestCachePolicy = .reloadIgnoringLocalCacheData
|
sessionConfiguration.requestCachePolicy = .reloadIgnoringLocalCacheData
|
||||||
@ -94,7 +94,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
|||||||
sessionConfiguration.httpAdditionalHeaders = userAgentHeaders
|
sessionConfiguration.httpAdditionalHeaders = userAgentHeaders
|
||||||
}
|
}
|
||||||
|
|
||||||
caller = ReaderAPICaller(transport: URLSession(configuration: sessionConfiguration))
|
caller = ReaderAPICaller(transport: URLSession(configuration: sessionConfiguration), secretsProvider: secretsProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
caller.variant = variant
|
caller.variant = variant
|
||||||
@ -636,13 +636,13 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
|||||||
func accountWillBeDeleted(_ account: Account) {
|
func accountWillBeDeleted(_ account: Account) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL?, completion: @escaping (Result<Credentials?, Error>) -> Void) {
|
static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL?, secretsProvider: SecretsProvider, completion: @escaping (Result<Credentials?, Error>) -> Void) {
|
||||||
guard let endpoint = endpoint else {
|
guard let endpoint = endpoint else {
|
||||||
completion(.failure(TransportError.noURL))
|
completion(.failure(TransportError.noURL))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let caller = ReaderAPICaller(transport: transport)
|
let caller = ReaderAPICaller(transport: transport, secretsProvider: secretsProvider)
|
||||||
caller.credentials = credentials
|
caller.credentials = credentials
|
||||||
caller.validateCredentials(endpoint: endpoint) { result in
|
caller.validateCredentials(endpoint: endpoint) { result in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
@ -48,6 +48,7 @@ final class ReaderAPICaller: NSObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var transport: Transport!
|
private var transport: Transport!
|
||||||
|
private let secretsProvider: SecretsProvider
|
||||||
private let uriComponentAllowed: CharacterSet
|
private let uriComponentAllowed: CharacterSet
|
||||||
|
|
||||||
private var accessToken: String?
|
private var accessToken: String?
|
||||||
@ -77,9 +78,10 @@ final class ReaderAPICaller: NSObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(transport: Transport) {
|
init(transport: Transport, secretsProvider: SecretsProvider) {
|
||||||
self.transport = transport
|
self.transport = transport
|
||||||
|
self.secretsProvider = secretsProvider
|
||||||
|
|
||||||
var urlHostAllowed = CharacterSet.urlHostAllowed
|
var urlHostAllowed = CharacterSet.urlHostAllowed
|
||||||
urlHostAllowed.remove("+")
|
urlHostAllowed.remove("+")
|
||||||
urlHostAllowed.remove("&")
|
urlHostAllowed.remove("&")
|
||||||
@ -693,8 +695,8 @@ private extension ReaderAPICaller {
|
|||||||
|
|
||||||
func addVariantHeaders(_ request: inout URLRequest) {
|
func addVariantHeaders(_ request: inout URLRequest) {
|
||||||
if variant == .inoreader {
|
if variant == .inoreader {
|
||||||
request.addValue(SecretsManager.provider.inoreaderAppId, forHTTPHeaderField: "AppId")
|
request.addValue(secretsProvider.inoreaderAppId, forHTTPHeaderField: "AppId")
|
||||||
request.addValue(SecretsManager.provider.inoreaderAppKey, forHTTPHeaderField: "AppKey")
|
request.addValue(secretsProvider.inoreaderAppKey, forHTTPHeaderField: "AppKey")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,10 +19,6 @@ class FeedlyTestSupport {
|
|||||||
var refreshToken = Credentials(type: .oauthRefreshToken, username: "Test", secret: "t3st-refresh-tok3n")
|
var refreshToken = Credentials(type: .oauthRefreshToken, username: "Test", secret: "t3st-refresh-tok3n")
|
||||||
var transport = TestTransport()
|
var transport = TestTransport()
|
||||||
|
|
||||||
init() {
|
|
||||||
SecretsManager.provider = FeedlyTestSecrets()
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeMockNetworkStack() -> (TestTransport, FeedlyAPICaller) {
|
func makeMockNetworkStack() -> (TestTransport, FeedlyAPICaller) {
|
||||||
let caller = FeedlyAPICaller(transport: transport, api: .sandbox)
|
let caller = FeedlyAPICaller(transport: transport, api: .sandbox)
|
||||||
caller.credentials = accessToken
|
caller.credentials = accessToken
|
||||||
|
@ -108,6 +108,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidat
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
private var themeImportPath: String?
|
private var themeImportPath: String?
|
||||||
|
private let secretsProvider = Secrets()
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
NSWindow.allowsAutomaticWindowTabbing = false
|
NSWindow.allowsAutomaticWindowTabbing = false
|
||||||
@ -119,8 +120,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidat
|
|||||||
crashReporter.enable()
|
crashReporter.enable()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
SecretsManager.provider = Secrets()
|
AccountManager.shared = AccountManager(accountsFolder: Platform.dataSubfolder(forApplication: nil, folderName: "Accounts")!, secretsProvider: secretsProvider)
|
||||||
AccountManager.shared = AccountManager(accountsFolder: Platform.dataSubfolder(forApplication: nil, folderName: "Accounts")!)
|
|
||||||
ArticleThemesManager.shared = ArticleThemesManager(folderPath: Platform.dataSubfolder(forApplication: nil, folderName: "Themes")!)
|
ArticleThemesManager.shared = ArticleThemesManager(folderPath: Platform.dataSubfolder(forApplication: nil, folderName: "Themes")!)
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
|
||||||
|
@ -1257,7 +1257,7 @@ private extension MainWindowController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func startArticleExtractorForCurrentLink() {
|
func startArticleExtractorForCurrentLink() {
|
||||||
if let link = currentLink, let extractor = ArticleExtractor(link) {
|
if let link = currentLink, let extractor = ArticleExtractor(link, secretsProvider: Secrets()) {
|
||||||
extractor.delegate = self
|
extractor.delegate = self
|
||||||
extractor.process()
|
extractor.process()
|
||||||
articleExtractor = extractor
|
articleExtractor = extractor
|
||||||
|
@ -79,8 +79,8 @@ class AccountsFeedbinWindowController: NSWindowController {
|
|||||||
progressIndicator.startAnimation(self)
|
progressIndicator.startAnimation(self)
|
||||||
|
|
||||||
let credentials = Credentials(type: .basic, username: usernameTextField.stringValue, secret: passwordTextField.stringValue)
|
let credentials = Credentials(type: .basic, username: usernameTextField.stringValue, secret: passwordTextField.stringValue)
|
||||||
Account.validateCredentials(type: .feedbin, credentials: credentials) { [weak self] result in
|
Account.validateCredentials(type: .feedbin, credentials: credentials, secretsProvider: Secrets()) { [weak self] result in
|
||||||
|
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
|
||||||
self.actionButton.isEnabled = true
|
self.actionButton.isEnabled = true
|
||||||
|
@ -76,7 +76,7 @@ class AccountsNewsBlurWindowController: NSWindowController {
|
|||||||
progressIndicator.startAnimation(self)
|
progressIndicator.startAnimation(self)
|
||||||
|
|
||||||
let credentials = Credentials(type: .newsBlurBasic, username: usernameTextField.stringValue, secret: passwordTextField.stringValue)
|
let credentials = Credentials(type: .newsBlurBasic, username: usernameTextField.stringValue, secret: passwordTextField.stringValue)
|
||||||
Account.validateCredentials(type: .newsBlur, credentials: credentials) { [weak self] result in
|
Account.validateCredentials(type: .newsBlur, credentials: credentials, secretsProvider: Secrets()) { [weak self] result in
|
||||||
|
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
@ -174,7 +174,7 @@ extension AccountsPreferencesViewController: AccountsPreferencesAddAccountDelega
|
|||||||
accountsReaderAPIWindowController.runSheetOnWindow(self.view.window!)
|
accountsReaderAPIWindowController.runSheetOnWindow(self.view.window!)
|
||||||
addAccountWindowController = accountsReaderAPIWindowController
|
addAccountWindowController = accountsReaderAPIWindowController
|
||||||
case .feedly:
|
case .feedly:
|
||||||
let addAccount = OAuthAccountAuthorizationOperation(accountType: .feedly)
|
let addAccount = OAuthAccountAuthorizationOperation(accountType: .feedly, secretsProvider: Secrets())
|
||||||
addAccount.delegate = self
|
addAccount.delegate = self
|
||||||
addAccount.presentationAnchor = self.view.window!
|
addAccount.presentationAnchor = self.view.window!
|
||||||
runAwaitingFeedlyLoginAlertModal(forLifetimeOf: addAccount)
|
runAwaitingFeedlyLoginAlertModal(forLifetimeOf: addAccount)
|
||||||
|
@ -131,8 +131,8 @@ class AccountsReaderAPIWindowController: NSWindowController {
|
|||||||
progressIndicator.startAnimation(self)
|
progressIndicator.startAnimation(self)
|
||||||
|
|
||||||
let credentials = Credentials(type: .readerBasic, username: usernameTextField.stringValue, secret: passwordTextField.stringValue)
|
let credentials = Credentials(type: .readerBasic, username: usernameTextField.stringValue, secret: passwordTextField.stringValue)
|
||||||
Account.validateCredentials(type: accountType, credentials: credentials, endpoint: apiURL) { [weak self] result in
|
Account.validateCredentials(type: accountType, credentials: credentials, endpoint: apiURL, secretsProvider: Secrets()) { [weak self] result in
|
||||||
|
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
|
||||||
self.actionButton.isEnabled = true
|
self.actionButton.isEnabled = true
|
||||||
|
@ -2,20 +2,23 @@
|
|||||||
import PackageDescription
|
import PackageDescription
|
||||||
|
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "Secrets",
|
name: "Secrets",
|
||||||
platforms: [.macOS(.v14), .iOS(.v17)],
|
platforms: [.macOS(.v14), .iOS(.v17)],
|
||||||
products: [
|
products: [
|
||||||
.library(
|
.library(
|
||||||
name: "Secrets",
|
name: "Secrets",
|
||||||
type: .dynamic,
|
type: .dynamic,
|
||||||
targets: ["Secrets"]
|
targets: ["Secrets"]
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
dependencies: [],
|
dependencies: [],
|
||||||
targets: [
|
targets: [
|
||||||
.target(
|
.target(
|
||||||
name: "Secrets",
|
name: "Secrets",
|
||||||
dependencies: []
|
dependencies: [],
|
||||||
)
|
swiftSettings: [
|
||||||
]
|
.enableExperimentalFeature("StrictConcurrency")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
//
|
|
||||||
// SecretsManager.swift
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Created by Maurice Parker on 7/30/20.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
public class SecretsManager {
|
|
||||||
public static var provider: SecretsProvider!
|
|
||||||
}
|
|
@ -34,15 +34,15 @@ class ArticleExtractor {
|
|||||||
|
|
||||||
private var url: URL!
|
private var url: URL!
|
||||||
|
|
||||||
public init?(_ articleLink: String) {
|
public init?(_ articleLink: String, secretsProvider: SecretsProvider) {
|
||||||
self.articleLink = articleLink
|
self.articleLink = articleLink
|
||||||
|
|
||||||
let clientURL = "https://extract.feedbin.com/parser"
|
let clientURL = "https://extract.feedbin.com/parser"
|
||||||
let username = SecretsManager.provider.mercuryClientId
|
let username = secretsProvider.mercuryClientId
|
||||||
let signiture = articleLink.hmacUsingSHA1(key: SecretsManager.provider.mercuryClientSecret)
|
let signature = articleLink.hmacUsingSHA1(key: secretsProvider.mercuryClientSecret)
|
||||||
|
|
||||||
if let base64URL = articleLink.data(using: .utf8)?.base64EncodedString() {
|
if let base64URL = articleLink.data(using: .utf8)?.base64EncodedString() {
|
||||||
let fullURL = "\(clientURL)/\(username)/\(signiture)?base64_url=\(base64URL)"
|
let fullURL = "\(clientURL)/\(username)/\(signature)?base64_url=\(base64URL)"
|
||||||
if let url = URL(string: fullURL) {
|
if let url = URL(string: fullURL) {
|
||||||
self.url = url
|
self.url = url
|
||||||
return
|
return
|
||||||
|
@ -30,6 +30,8 @@ class FeedbinAccountViewController: UITableViewController {
|
|||||||
weak var account: Account?
|
weak var account: Account?
|
||||||
weak var delegate: AddAccountDismissDelegate?
|
weak var delegate: AddAccountDismissDelegate?
|
||||||
|
|
||||||
|
var secretsProvider: SecretsProvider!
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
setupFooter()
|
setupFooter()
|
||||||
@ -120,7 +122,7 @@ class FeedbinAccountViewController: UITableViewController {
|
|||||||
setNavigationEnabled(to: false)
|
setNavigationEnabled(to: false)
|
||||||
|
|
||||||
let credentials = Credentials(type: .basic, username: trimmedEmail, secret: password)
|
let credentials = Credentials(type: .basic, username: trimmedEmail, secret: password)
|
||||||
Account.validateCredentials(type: .feedbin, credentials: credentials) { result in
|
Account.validateCredentials(type: .feedbin, credentials: credentials, secretsProvider: secretsProvider) { result in
|
||||||
self.toggleActivityIndicatorAnimation(visible: false)
|
self.toggleActivityIndicatorAnimation(visible: false)
|
||||||
self.setNavigationEnabled(to: true)
|
self.setNavigationEnabled(to: true)
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ class NewsBlurAccountViewController: UITableViewController {
|
|||||||
|
|
||||||
weak var account: Account?
|
weak var account: Account?
|
||||||
weak var delegate: AddAccountDismissDelegate?
|
weak var delegate: AddAccountDismissDelegate?
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
setupFooter()
|
setupFooter()
|
||||||
@ -105,7 +105,7 @@ class NewsBlurAccountViewController: UITableViewController {
|
|||||||
disableNavigation()
|
disableNavigation()
|
||||||
|
|
||||||
let basicCredentials = Credentials(type: .newsBlurBasic, username: trimmedUsername, secret: password)
|
let basicCredentials = Credentials(type: .newsBlurBasic, username: trimmedUsername, secret: password)
|
||||||
Account.validateCredentials(type: .newsBlur, credentials: basicCredentials) { result in
|
Account.validateCredentials(type: .newsBlur, credentials: basicCredentials, secretsProvider: Secrets()) { result in
|
||||||
|
|
||||||
self.stopAnimatingActivityIndicator()
|
self.stopAnimatingActivityIndicator()
|
||||||
self.enableNavigation()
|
self.enableNavigation()
|
||||||
@ -147,7 +147,6 @@ class NewsBlurAccountViewController: UITableViewController {
|
|||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
self.showError(error.localizedDescription)
|
self.showError(error.localizedDescription)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,7 +157,7 @@ class ReaderAPIAccountViewController: UITableViewController {
|
|||||||
disableNavigation()
|
disableNavigation()
|
||||||
|
|
||||||
let credentials = Credentials(type: .readerBasic, username: trimmedUsername, secret: password)
|
let credentials = Credentials(type: .readerBasic, username: trimmedUsername, secret: password)
|
||||||
Account.validateCredentials(type: type, credentials: credentials, endpoint: url) { result in
|
Account.validateCredentials(type: type, credentials: credentials, endpoint: url, secretsProvider: Secrets()) { result in
|
||||||
|
|
||||||
self.stopAnimatingActivityIndicator()
|
self.stopAnimatingActivityIndicator()
|
||||||
self.enableNavigation()
|
self.enableNavigation()
|
||||||
@ -199,7 +199,6 @@ class ReaderAPIAccountViewController: UITableViewController {
|
|||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
self.showError(error.localizedDescription)
|
self.showError(error.localizedDescription)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,16 +58,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
|||||||
var isSyncArticleStatusRunning = false
|
var isSyncArticleStatusRunning = false
|
||||||
var isWaitingForSyncTasks = false
|
var isWaitingForSyncTasks = false
|
||||||
|
|
||||||
|
private var secretsProvider = Secrets()
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
appDelegate = self
|
appDelegate = self
|
||||||
|
|
||||||
SecretsManager.provider = Secrets()
|
|
||||||
let documentFolder = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
let documentFolder = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||||
let documentAccountsFolder = documentFolder.appendingPathComponent("Accounts").absoluteString
|
let documentAccountsFolder = documentFolder.appendingPathComponent("Accounts").absoluteString
|
||||||
let documentAccountsFolderPath = String(documentAccountsFolder.suffix(from: documentAccountsFolder.index(documentAccountsFolder.startIndex, offsetBy: 7)))
|
let documentAccountsFolderPath = String(documentAccountsFolder.suffix(from: documentAccountsFolder.index(documentAccountsFolder.startIndex, offsetBy: 7)))
|
||||||
AccountManager.shared = AccountManager(accountsFolder: documentAccountsFolderPath)
|
AccountManager.shared = AccountManager(accountsFolder: documentAccountsFolderPath, secretsProvider: secretsProvider)
|
||||||
|
|
||||||
let documentThemesFolder = documentFolder.appendingPathComponent("Themes").absoluteString
|
let documentThemesFolder = documentFolder.appendingPathComponent("Themes").absoluteString
|
||||||
let documentThemesFolderPath = String(documentThemesFolder.suffix(from: documentAccountsFolder.index(documentThemesFolder.startIndex, offsetBy: 7)))
|
let documentThemesFolderPath = String(documentThemesFolder.suffix(from: documentAccountsFolder.index(documentThemesFolder.startIndex, offsetBy: 7)))
|
||||||
ArticleThemesManager.shared = ArticleThemesManager(folderPath: documentThemesFolderPath)
|
ArticleThemesManager.shared = ArticleThemesManager(folderPath: documentThemesFolderPath)
|
||||||
|
@ -657,7 +657,7 @@ private extension WebViewController {
|
|||||||
|
|
||||||
func startArticleExtractor() {
|
func startArticleExtractor() {
|
||||||
guard articleExtractor == nil else { return }
|
guard articleExtractor == nil else { return }
|
||||||
if let link = article?.preferredLink, let extractor = ArticleExtractor(link) {
|
if let link = article?.preferredLink, let extractor = ArticleExtractor(link, secretsProvider: Secrets()) {
|
||||||
extractor.delegate = self
|
extractor.delegate = self
|
||||||
extractor.process()
|
extractor.process()
|
||||||
articleExtractor = extractor
|
articleExtractor = extractor
|
||||||
|
@ -197,7 +197,7 @@ class AddAccountViewController: UITableViewController, AddAccountDismissDelegate
|
|||||||
addViewController.delegate = self
|
addViewController.delegate = self
|
||||||
present(navController, animated: true)
|
present(navController, animated: true)
|
||||||
case .feedly:
|
case .feedly:
|
||||||
let addAccount = OAuthAccountAuthorizationOperation(accountType: .feedly)
|
let addAccount = OAuthAccountAuthorizationOperation(accountType: .feedly, secretsProvider: Secrets())
|
||||||
addAccount.delegate = self
|
addAccount.delegate = self
|
||||||
addAccount.presentationAnchor = self.view.window!
|
addAccount.presentationAnchor = self.view.window!
|
||||||
MainThreadOperationQueue.shared.add(addAccount)
|
MainThreadOperationQueue.shared.add(addAccount)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user