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
|
||||
}
|
||||
|
||||
init(dataFolder: String, type: AccountType, accountID: String, transport: Transport? = nil) {
|
||||
init(dataFolder: String, type: AccountType, accountID: String, secretsProvider: SecretsProvider, transport: Transport? = nil) {
|
||||
switch type {
|
||||
case .onMyMac:
|
||||
self.delegate = LocalAccountDelegate()
|
||||
@ -267,17 +267,17 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
case .feedbin:
|
||||
self.delegate = FeedbinAccountDelegate(dataFolder: dataFolder, transport: transport)
|
||||
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:
|
||||
self.delegate = NewsBlurAccountDelegate(dataFolder: dataFolder, transport: transport)
|
||||
case .freshRSS:
|
||||
self.delegate = ReaderAPIAccountDelegate(dataFolder: dataFolder, transport: transport, variant: .freshRSS)
|
||||
self.delegate = ReaderAPIAccountDelegate(dataFolder: dataFolder, transport: transport, variant: .freshRSS, secretsProvider: secretsProvider)
|
||||
case .inoreader:
|
||||
self.delegate = ReaderAPIAccountDelegate(dataFolder: dataFolder, transport: transport, variant: .inoreader)
|
||||
self.delegate = ReaderAPIAccountDelegate(dataFolder: dataFolder, transport: transport, variant: .inoreader, secretsProvider: secretsProvider)
|
||||
case .bazQux:
|
||||
self.delegate = ReaderAPIAccountDelegate(dataFolder: dataFolder, transport: transport, variant: .bazQux)
|
||||
self.delegate = ReaderAPIAccountDelegate(dataFolder: dataFolder, transport: transport, variant: .bazQux, secretsProvider: secretsProvider)
|
||||
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
|
||||
@ -367,29 +367,29 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
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 {
|
||||
case .feedbin:
|
||||
FeedbinAccountDelegate.validateCredentials(transport: transport, credentials: credentials, completion: completion)
|
||||
FeedbinAccountDelegate.validateCredentials(transport: transport, credentials: credentials, secretsProvider: secretsProvider, completion: completion)
|
||||
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:
|
||||
ReaderAPIAccountDelegate.validateCredentials(transport: transport, credentials: credentials, endpoint: endpoint, completion: completion)
|
||||
ReaderAPIAccountDelegate.validateCredentials(transport: transport, credentials: credentials, endpoint: endpoint, secretsProvider: secretsProvider, completion: completion)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
internal static func oauthAuthorizationClient(for type: AccountType) -> OAuthAuthorizationClient {
|
||||
internal static func oauthAuthorizationClient(for type: AccountType, secretsProvider: SecretsProvider) -> OAuthAuthorizationClient {
|
||||
switch type {
|
||||
case .feedly:
|
||||
return FeedlyAccountDelegate.environment.oauthAuthorizationClient
|
||||
return FeedlyAccountDelegate.environment.oauthAuthorizationClient(secretsProvider: secretsProvider)
|
||||
default:
|
||||
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
|
||||
switch type {
|
||||
case .feedly:
|
||||
@ -398,13 +398,14 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
fatalError("\(type) does not support OAuth authorization code granting.")
|
||||
}
|
||||
|
||||
return grantingType.oauthAuthorizationCodeGrantRequest()
|
||||
return grantingType.oauthAuthorizationCodeGrantRequest(secretsProvider: secretsProvider)
|
||||
}
|
||||
|
||||
public static func requestOAuthAccessToken(with response: OAuthAuthorizationResponse,
|
||||
client: OAuthAuthorizationClient,
|
||||
accountType: AccountType,
|
||||
transport: Transport = URLSession.webserviceTransport(),
|
||||
secretsProvider: SecretsProvider,
|
||||
completion: @escaping (Result<OAuthAuthorizationGrant, Error>) -> ()) {
|
||||
let grantingType: OAuthAuthorizationGranting.Type
|
||||
|
||||
@ -415,7 +416,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
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) {
|
||||
|
@ -52,7 +52,7 @@ protocol AccountDelegate {
|
||||
|
||||
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
|
||||
func suspendNetwork()
|
||||
|
@ -12,6 +12,7 @@ import RSWeb
|
||||
import Articles
|
||||
import ArticlesDatabase
|
||||
import Database
|
||||
import Secrets
|
||||
|
||||
// Main thread only.
|
||||
|
||||
@ -29,6 +30,8 @@ public final class AccountManager: UnreadCountProvider {
|
||||
private let defaultAccountFolderName = "OnMyMac"
|
||||
private let defaultAccountIdentifier = "OnMyMac"
|
||||
|
||||
private let secretsProvider: SecretsProvider
|
||||
|
||||
public var isSuspended = false
|
||||
public var isUnreadCountsInitialized: Bool {
|
||||
for account in activeAccounts {
|
||||
@ -94,9 +97,11 @@ public final class AccountManager: UnreadCountProvider {
|
||||
return CombinedRefreshProgress(downloadProgressArray: downloadProgressArray)
|
||||
}
|
||||
|
||||
public init(accountsFolder: String) {
|
||||
self.accountsFolder = accountsFolder
|
||||
public init(accountsFolder: String, secretsProvider: SecretsProvider) {
|
||||
|
||||
self.accountsFolder = accountsFolder
|
||||
self.secretsProvider = secretsProvider
|
||||
|
||||
// The local "On My Mac" account must always exist, even if it's empty.
|
||||
let localAccountFolder = (accountsFolder as NSString).appendingPathComponent("OnMyMac")
|
||||
do {
|
||||
@ -107,7 +112,7 @@ public final class AccountManager: UnreadCountProvider {
|
||||
abort()
|
||||
}
|
||||
|
||||
defaultAccount = Account(dataFolder: localAccountFolder, type: .onMyMac, accountID: defaultAccountIdentifier)
|
||||
defaultAccount = Account(dataFolder: localAccountFolder, type: .onMyMac, accountID: defaultAccountIdentifier, secretsProvider: secretsProvider)
|
||||
accountsDictionary[defaultAccount.accountID] = defaultAccount
|
||||
|
||||
readAccountsFromDisk()
|
||||
@ -134,7 +139,7 @@ public final class AccountManager: UnreadCountProvider {
|
||||
abort()
|
||||
}
|
||||
|
||||
let account = Account(dataFolder: accountFolder, type: type, accountID: accountID)
|
||||
let account = Account(dataFolder: accountFolder, type: type, accountID: accountID, secretsProvider: secretsProvider)
|
||||
accountsDictionary[accountID] = account
|
||||
|
||||
var userInfo = [String: Any]()
|
||||
@ -430,7 +435,7 @@ private extension AccountManager {
|
||||
}
|
||||
|
||||
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? {
|
||||
|
@ -451,7 +451,7 @@ final class CloudKitAccountDelegate: AccountDelegate {
|
||||
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))
|
||||
}
|
||||
|
||||
|
@ -583,7 +583,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
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)
|
||||
caller.credentials = credentials
|
||||
|
@ -37,12 +37,12 @@ final class FeedlyAPICaller {
|
||||
return components
|
||||
}
|
||||
|
||||
var oauthAuthorizationClient: OAuthAuthorizationClient {
|
||||
func oauthAuthorizationClient(secretsProvider: SecretsProvider) -> OAuthAuthorizationClient {
|
||||
switch self {
|
||||
case .sandbox:
|
||||
return .feedlySandboxClient
|
||||
case .cloud:
|
||||
return .feedlyCloudClient
|
||||
return OAuthAuthorizationClient.feedlyCloudClient(secretsProvider: secretsProvider)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -50,11 +50,13 @@ final class FeedlyAPICaller {
|
||||
private let transport: Transport
|
||||
private let baseUrlComponents: URLComponents
|
||||
private let uriComponentAllowed: CharacterSet
|
||||
private let secretsProvider: SecretsProvider
|
||||
|
||||
init(transport: Transport, api: API) {
|
||||
init(transport: Transport, api: API, secretsProvider: SecretsProvider) {
|
||||
self.transport = transport
|
||||
self.baseUrlComponents = api.baseUrlComponents
|
||||
|
||||
self.secretsProvider = secretsProvider
|
||||
|
||||
var urlHostAllowed = CharacterSet.urlHostAllowed
|
||||
urlHostAllowed.remove("+")
|
||||
uriComponentAllowed = urlHostAllowed
|
||||
|
@ -25,11 +25,11 @@ public struct FeedlyOAuthAccessTokenResponse: Decodable, OAuthAccessTokenRespons
|
||||
}
|
||||
|
||||
extension FeedlyAccountDelegate: OAuthAuthorizationGranting {
|
||||
|
||||
|
||||
private static let oauthAuthorizationGrantScope = "https://cloud.feedly.com/subscriptions"
|
||||
|
||||
static func oauthAuthorizationCodeGrantRequest() -> URLRequest {
|
||||
let client = environment.oauthAuthorizationClient
|
||||
|
||||
static func oauthAuthorizationCodeGrantRequest(secretsProvider: SecretsProvider) -> URLRequest {
|
||||
let client = environment.oauthAuthorizationClient(secretsProvider: secretsProvider)
|
||||
let authorizationRequest = OAuthAuthorizationRequest(clientId: client.id,
|
||||
redirectUri: client.redirectUri,
|
||||
scope: oauthAuthorizationGrantScope,
|
||||
@ -38,12 +38,12 @@ extension FeedlyAccountDelegate: OAuthAuthorizationGranting {
|
||||
return FeedlyAPICaller.authorizationCodeUrlRequest(for: authorizationRequest, baseUrlComponents: baseURLComponents)
|
||||
}
|
||||
|
||||
static func requestOAuthAccessToken(with response: OAuthAuthorizationResponse, transport: Transport, completion: @escaping (Result<OAuthAuthorizationGrant, Error>) -> ()) {
|
||||
let client = environment.oauthAuthorizationClient
|
||||
static func requestOAuthAccessToken(with response: OAuthAuthorizationResponse, transport: Transport, secretsProvider: SecretsProvider, completion: @escaping (Result<OAuthAuthorizationGrant, Error>) -> ()) {
|
||||
let client = environment.oauthAuthorizationClient(secretsProvider: secretsProvider)
|
||||
let request = OAuthAccessTokenRequest(authorizationResponse: response,
|
||||
scope: oauthAuthorizationGrantScope,
|
||||
client: client)
|
||||
let caller = FeedlyAPICaller(transport: transport, api: environment)
|
||||
let caller = FeedlyAPICaller(transport: transport, api: environment, secretsProvider: secretsProvider)
|
||||
caller.requestAccessToken(request) { result in
|
||||
switch result {
|
||||
case .success(let response):
|
||||
|
@ -67,15 +67,15 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||
private weak var currentSyncAllOperation: MainThreadOperation?
|
||||
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.
|
||||
// Making this a serial queue at this higher level of abstraction means we can ensure,
|
||||
// for example, a `FeedlyRefreshAccessTokenOperation` occurs before a `FeedlySyncAllOperation`,
|
||||
// improving our ability to debug, reason about and predict the behaviour of the code.
|
||||
|
||||
if let transport = transport {
|
||||
self.caller = FeedlyAPICaller(transport: transport, api: api)
|
||||
|
||||
self.caller = FeedlyAPICaller(transport: transport, api: api, secretsProvider: secretsProvider)
|
||||
|
||||
} else {
|
||||
|
||||
let sessionConfiguration = URLSessionConfiguration.default
|
||||
@ -92,12 +92,12 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||
}
|
||||
|
||||
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")
|
||||
self.database = SyncDatabase(databaseFilePath: databaseFilePath)
|
||||
self.oauthAuthorizationClient = api.oauthAuthorizationClient
|
||||
self.oauthAuthorizationClient = api.oauthAuthorizationClient(secretsProvider: secretsProvider)
|
||||
|
||||
self.caller.delegate = self
|
||||
}
|
||||
@ -539,7 +539,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||
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.")
|
||||
completion(.success(credentials))
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
import Foundation
|
||||
import AuthenticationServices
|
||||
import RSCore
|
||||
import Secrets
|
||||
|
||||
public protocol OAuthAccountAuthorizationOperationDelegate: AnyObject {
|
||||
func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didCreate account: Account)
|
||||
@ -42,17 +43,19 @@ public enum OAuthAccountAuthorizationOperationError: LocalizedError {
|
||||
private let accountType: AccountType
|
||||
private let oauthClient: OAuthAuthorizationClient
|
||||
private var session: ASWebAuthenticationSession?
|
||||
|
||||
public init(accountType: AccountType) {
|
||||
private let secretsProvider: SecretsProvider
|
||||
|
||||
public init(accountType: AccountType, secretsProvider: SecretsProvider) {
|
||||
self.accountType = accountType
|
||||
self.oauthClient = Account.oauthAuthorizationClient(for: accountType)
|
||||
self.secretsProvider = secretsProvider
|
||||
self.oauthClient = Account.oauthAuthorizationClient(for: accountType, secretsProvider: secretsProvider)
|
||||
}
|
||||
|
||||
public func run() {
|
||||
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 {
|
||||
return DispatchQueue.main.async {
|
||||
self.didEndAuthentication(url: nil, error: URLError(.badURL))
|
||||
@ -113,7 +116,7 @@ public enum OAuthAccountAuthorizationOperationError: LocalizedError {
|
||||
|
||||
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 {
|
||||
didFinish() // Primarily, cancellation.
|
||||
|
@ -11,14 +11,14 @@ import Secrets
|
||||
|
||||
extension OAuthAuthorizationClient {
|
||||
|
||||
static var feedlyCloudClient: OAuthAuthorizationClient {
|
||||
static func feedlyCloudClient(secretsProvider: SecretsProvider) -> OAuthAuthorizationClient {
|
||||
/// Models private NetNewsWire client secrets.
|
||||
/// 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
|
||||
return OAuthAuthorizationClient(id: SecretsManager.provider.feedlyClientId,
|
||||
return OAuthAuthorizationClient(id: secretsProvider.feedlyClientId,
|
||||
redirectUri: "netnewswire://auth/feedly",
|
||||
state: nil,
|
||||
secret: SecretsManager.provider.feedlyClientSecret)
|
||||
secret: secretsProvider.feedlyClientSecret)
|
||||
}
|
||||
|
||||
static var feedlySandboxClient: OAuthAuthorizationClient {
|
||||
|
@ -17,7 +17,7 @@ public struct OAuthAuthorizationClient: Equatable {
|
||||
public var redirectUri: String
|
||||
public var state: String?
|
||||
public var secret: String
|
||||
|
||||
|
||||
public init(id: String, redirectUri: String, state: String?, secret: String) {
|
||||
self.id = id
|
||||
self.redirectUri = redirectUri
|
||||
@ -167,7 +167,7 @@ public protocol OAuthAuthorizationCodeGrantRequesting {
|
||||
|
||||
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) {
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
|
@ -612,7 +612,7 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
||||
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)
|
||||
caller.credentials = credentials
|
||||
caller.validateCredentials() { result in
|
||||
|
@ -74,12 +74,12 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
|
||||
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")
|
||||
database = SyncDatabase(databaseFilePath: databaseFilePath)
|
||||
|
||||
if transport != nil {
|
||||
caller = ReaderAPICaller(transport: transport!)
|
||||
caller = ReaderAPICaller(transport: transport!, secretsProvider: secretsProvider)
|
||||
} else {
|
||||
let sessionConfiguration = URLSessionConfiguration.default
|
||||
sessionConfiguration.requestCachePolicy = .reloadIgnoringLocalCacheData
|
||||
@ -94,7 +94,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
sessionConfiguration.httpAdditionalHeaders = userAgentHeaders
|
||||
}
|
||||
|
||||
caller = ReaderAPICaller(transport: URLSession(configuration: sessionConfiguration))
|
||||
caller = ReaderAPICaller(transport: URLSession(configuration: sessionConfiguration), secretsProvider: secretsProvider)
|
||||
}
|
||||
|
||||
caller.variant = variant
|
||||
@ -636,13 +636,13 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
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 {
|
||||
completion(.failure(TransportError.noURL))
|
||||
return
|
||||
}
|
||||
|
||||
let caller = ReaderAPICaller(transport: transport)
|
||||
let caller = ReaderAPICaller(transport: transport, secretsProvider: secretsProvider)
|
||||
caller.credentials = credentials
|
||||
caller.validateCredentials(endpoint: endpoint) { result in
|
||||
DispatchQueue.main.async {
|
||||
|
@ -48,6 +48,7 @@ final class ReaderAPICaller: NSObject {
|
||||
}
|
||||
|
||||
private var transport: Transport!
|
||||
private let secretsProvider: SecretsProvider
|
||||
private let uriComponentAllowed: CharacterSet
|
||||
|
||||
private var accessToken: String?
|
||||
@ -77,9 +78,10 @@ final class ReaderAPICaller: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
init(transport: Transport) {
|
||||
init(transport: Transport, secretsProvider: SecretsProvider) {
|
||||
self.transport = transport
|
||||
|
||||
self.secretsProvider = secretsProvider
|
||||
|
||||
var urlHostAllowed = CharacterSet.urlHostAllowed
|
||||
urlHostAllowed.remove("+")
|
||||
urlHostAllowed.remove("&")
|
||||
@ -693,8 +695,8 @@ private extension ReaderAPICaller {
|
||||
|
||||
func addVariantHeaders(_ request: inout URLRequest) {
|
||||
if variant == .inoreader {
|
||||
request.addValue(SecretsManager.provider.inoreaderAppId, forHTTPHeaderField: "AppId")
|
||||
request.addValue(SecretsManager.provider.inoreaderAppKey, forHTTPHeaderField: "AppKey")
|
||||
request.addValue(secretsProvider.inoreaderAppId, forHTTPHeaderField: "AppId")
|
||||
request.addValue(secretsProvider.inoreaderAppKey, forHTTPHeaderField: "AppKey")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,10 +19,6 @@ class FeedlyTestSupport {
|
||||
var refreshToken = Credentials(type: .oauthRefreshToken, username: "Test", secret: "t3st-refresh-tok3n")
|
||||
var transport = TestTransport()
|
||||
|
||||
init() {
|
||||
SecretsManager.provider = FeedlyTestSecrets()
|
||||
}
|
||||
|
||||
func makeMockNetworkStack() -> (TestTransport, FeedlyAPICaller) {
|
||||
let caller = FeedlyAPICaller(transport: transport, api: .sandbox)
|
||||
caller.credentials = accessToken
|
||||
|
@ -108,6 +108,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidat
|
||||
#endif
|
||||
|
||||
private var themeImportPath: String?
|
||||
private let secretsProvider = Secrets()
|
||||
|
||||
override init() {
|
||||
NSWindow.allowsAutomaticWindowTabbing = false
|
||||
@ -119,8 +120,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidat
|
||||
crashReporter.enable()
|
||||
#endif
|
||||
|
||||
SecretsManager.provider = Secrets()
|
||||
AccountManager.shared = AccountManager(accountsFolder: Platform.dataSubfolder(forApplication: nil, folderName: "Accounts")!)
|
||||
AccountManager.shared = AccountManager(accountsFolder: Platform.dataSubfolder(forApplication: nil, folderName: "Accounts")!, secretsProvider: secretsProvider)
|
||||
ArticleThemesManager.shared = ArticleThemesManager(folderPath: Platform.dataSubfolder(forApplication: nil, folderName: "Themes")!)
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
|
||||
|
@ -1257,7 +1257,7 @@ private extension MainWindowController {
|
||||
}
|
||||
|
||||
func startArticleExtractorForCurrentLink() {
|
||||
if let link = currentLink, let extractor = ArticleExtractor(link) {
|
||||
if let link = currentLink, let extractor = ArticleExtractor(link, secretsProvider: Secrets()) {
|
||||
extractor.delegate = self
|
||||
extractor.process()
|
||||
articleExtractor = extractor
|
||||
|
@ -79,8 +79,8 @@ class AccountsFeedbinWindowController: NSWindowController {
|
||||
progressIndicator.startAnimation(self)
|
||||
|
||||
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 }
|
||||
|
||||
self.actionButton.isEnabled = true
|
||||
|
@ -76,7 +76,7 @@ class AccountsNewsBlurWindowController: NSWindowController {
|
||||
progressIndicator.startAnimation(self)
|
||||
|
||||
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 }
|
||||
|
||||
|
@ -174,7 +174,7 @@ extension AccountsPreferencesViewController: AccountsPreferencesAddAccountDelega
|
||||
accountsReaderAPIWindowController.runSheetOnWindow(self.view.window!)
|
||||
addAccountWindowController = accountsReaderAPIWindowController
|
||||
case .feedly:
|
||||
let addAccount = OAuthAccountAuthorizationOperation(accountType: .feedly)
|
||||
let addAccount = OAuthAccountAuthorizationOperation(accountType: .feedly, secretsProvider: Secrets())
|
||||
addAccount.delegate = self
|
||||
addAccount.presentationAnchor = self.view.window!
|
||||
runAwaitingFeedlyLoginAlertModal(forLifetimeOf: addAccount)
|
||||
|
@ -131,8 +131,8 @@ class AccountsReaderAPIWindowController: NSWindowController {
|
||||
progressIndicator.startAnimation(self)
|
||||
|
||||
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 }
|
||||
|
||||
self.actionButton.isEnabled = true
|
||||
|
@ -2,20 +2,23 @@
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Secrets",
|
||||
name: "Secrets",
|
||||
platforms: [.macOS(.v14), .iOS(.v17)],
|
||||
products: [
|
||||
.library(
|
||||
name: "Secrets",
|
||||
products: [
|
||||
.library(
|
||||
name: "Secrets",
|
||||
type: .dynamic,
|
||||
targets: ["Secrets"]
|
||||
)
|
||||
],
|
||||
dependencies: [],
|
||||
targets: [
|
||||
.target(
|
||||
name: "Secrets",
|
||||
dependencies: []
|
||||
)
|
||||
]
|
||||
targets: ["Secrets"]
|
||||
)
|
||||
],
|
||||
dependencies: [],
|
||||
targets: [
|
||||
.target(
|
||||
name: "Secrets",
|
||||
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!
|
||||
|
||||
public init?(_ articleLink: String) {
|
||||
public init?(_ articleLink: String, secretsProvider: SecretsProvider) {
|
||||
self.articleLink = articleLink
|
||||
|
||||
let clientURL = "https://extract.feedbin.com/parser"
|
||||
let username = SecretsManager.provider.mercuryClientId
|
||||
let signiture = articleLink.hmacUsingSHA1(key: SecretsManager.provider.mercuryClientSecret)
|
||||
|
||||
let username = secretsProvider.mercuryClientId
|
||||
let signature = articleLink.hmacUsingSHA1(key: secretsProvider.mercuryClientSecret)
|
||||
|
||||
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) {
|
||||
self.url = url
|
||||
return
|
||||
|
@ -30,6 +30,8 @@ class FeedbinAccountViewController: UITableViewController {
|
||||
weak var account: Account?
|
||||
weak var delegate: AddAccountDismissDelegate?
|
||||
|
||||
var secretsProvider: SecretsProvider!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
setupFooter()
|
||||
@ -120,7 +122,7 @@ class FeedbinAccountViewController: UITableViewController {
|
||||
setNavigationEnabled(to: false)
|
||||
|
||||
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.setNavigationEnabled(to: true)
|
||||
|
||||
|
@ -29,7 +29,7 @@ class NewsBlurAccountViewController: UITableViewController {
|
||||
|
||||
weak var account: Account?
|
||||
weak var delegate: AddAccountDismissDelegate?
|
||||
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
setupFooter()
|
||||
@ -105,7 +105,7 @@ class NewsBlurAccountViewController: UITableViewController {
|
||||
disableNavigation()
|
||||
|
||||
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.enableNavigation()
|
||||
@ -147,7 +147,6 @@ class NewsBlurAccountViewController: UITableViewController {
|
||||
case .failure(let error):
|
||||
self.showError(error.localizedDescription)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -157,7 +157,7 @@ class ReaderAPIAccountViewController: UITableViewController {
|
||||
disableNavigation()
|
||||
|
||||
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.enableNavigation()
|
||||
@ -199,7 +199,6 @@ class ReaderAPIAccountViewController: UITableViewController {
|
||||
case .failure(let error):
|
||||
self.showError(error.localizedDescription)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,16 +58,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||
var isSyncArticleStatusRunning = false
|
||||
var isWaitingForSyncTasks = false
|
||||
|
||||
private var secretsProvider = Secrets()
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
appDelegate = self
|
||||
|
||||
SecretsManager.provider = Secrets()
|
||||
let documentFolder = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||
let documentAccountsFolder = documentFolder.appendingPathComponent("Accounts").absoluteString
|
||||
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 documentThemesFolderPath = String(documentThemesFolder.suffix(from: documentAccountsFolder.index(documentThemesFolder.startIndex, offsetBy: 7)))
|
||||
ArticleThemesManager.shared = ArticleThemesManager(folderPath: documentThemesFolderPath)
|
||||
|
@ -657,7 +657,7 @@ private extension WebViewController {
|
||||
|
||||
func startArticleExtractor() {
|
||||
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.process()
|
||||
articleExtractor = extractor
|
||||
|
@ -197,7 +197,7 @@ class AddAccountViewController: UITableViewController, AddAccountDismissDelegate
|
||||
addViewController.delegate = self
|
||||
present(navController, animated: true)
|
||||
case .feedly:
|
||||
let addAccount = OAuthAccountAuthorizationOperation(accountType: .feedly)
|
||||
let addAccount = OAuthAccountAuthorizationOperation(accountType: .feedly, secretsProvider: Secrets())
|
||||
addAccount.delegate = self
|
||||
addAccount.presentationAnchor = self.view.window!
|
||||
MainThreadOperationQueue.shared.add(addAccount)
|
||||
|
Loading…
x
Reference in New Issue
Block a user