From 44dcfbee97b0b322aed579f75d20e1c8382e2265 Mon Sep 17 00:00:00 2001 From: Huong Do Date: Tue, 17 Jan 2023 10:36:59 +0700 Subject: [PATCH 01/17] Rename RequestProcessor to ApplicationPasswordRequestProcessor --- Networking/Networking.xcodeproj/project.pbxproj | 8 ++++---- ...or.swift => ApplicationPasswordRequestProcessor.swift} | 8 ++++---- Networking/Networking/Network/AlamofireNetwork.swift | 4 ++-- .../ApplicationPassword/RequestProcessorTests.swift | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) rename Networking/Networking/ApplicationPassword/{RequestProcessor.swift => ApplicationPasswordRequestProcessor.swift} (90%) diff --git a/Networking/Networking.xcodeproj/project.pbxproj b/Networking/Networking.xcodeproj/project.pbxproj index c93f56993ae..292e1a60a6b 100644 --- a/Networking/Networking.xcodeproj/project.pbxproj +++ b/Networking/Networking.xcodeproj/project.pbxproj @@ -752,7 +752,7 @@ DEF13C5E296686AB0024A02B /* orders-load-all-without-data.json in Resources */ = {isa = PBXBuildFile; fileRef = DEF13C5D296686AB0024A02B /* orders-load-all-without-data.json */; }; DEF13C6029668C420024A02B /* order-without-data.json in Resources */ = {isa = PBXBuildFile; fileRef = DEF13C5F29668C420024A02B /* order-without-data.json */; }; DEFBA74E29485A7600C35BA9 /* RESTRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFBA74D29485A7600C35BA9 /* RESTRequest.swift */; }; - DEFBA7542949CE6600C35BA9 /* RequestProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFBA7532949CE6600C35BA9 /* RequestProcessor.swift */; }; + DEFBA7542949CE6600C35BA9 /* ApplicationPasswordRequestProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFBA7532949CE6600C35BA9 /* ApplicationPasswordRequestProcessor.swift */; }; DEFBA7562949D17400C35BA9 /* DefaultRequestAuthenticatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFBA7552949D17300C35BA9 /* DefaultRequestAuthenticatorTests.swift */; }; E12552C526385B05001CEE70 /* ShippingLabelAddressValidationSuccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12552C426385B05001CEE70 /* ShippingLabelAddressValidationSuccess.swift */; }; E137619929151C7400FD098F /* error-wp-rest-forbidden.json in Resources */ = {isa = PBXBuildFile; fileRef = E137619829151C7400FD098F /* error-wp-rest-forbidden.json */; }; @@ -1581,7 +1581,7 @@ DEF13C5D296686AB0024A02B /* orders-load-all-without-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "orders-load-all-without-data.json"; sourceTree = ""; }; DEF13C5F29668C420024A02B /* order-without-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-without-data.json"; sourceTree = ""; }; DEFBA74D29485A7600C35BA9 /* RESTRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTRequest.swift; sourceTree = ""; }; - DEFBA7532949CE6600C35BA9 /* RequestProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestProcessor.swift; sourceTree = ""; }; + DEFBA7532949CE6600C35BA9 /* ApplicationPasswordRequestProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationPasswordRequestProcessor.swift; sourceTree = ""; }; DEFBA7552949D17300C35BA9 /* DefaultRequestAuthenticatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultRequestAuthenticatorTests.swift; sourceTree = ""; }; E12552C426385B05001CEE70 /* ShippingLabelAddressValidationSuccess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShippingLabelAddressValidationSuccess.swift; sourceTree = ""; }; E137619829151C7400FD098F /* error-wp-rest-forbidden.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "error-wp-rest-forbidden.json"; sourceTree = ""; }; @@ -2780,7 +2780,7 @@ EE54C899294777D000A9BF61 /* ApplicationPassword */ = { isa = PBXGroup; children = ( - DEFBA7532949CE6600C35BA9 /* RequestProcessor.swift */, + DEFBA7532949CE6600C35BA9 /* ApplicationPasswordRequestProcessor.swift */, EE54C89E2947782E00A9BF61 /* ApplicationPasswordUseCase.swift */, EE71CC3C2951A8EA0074D908 /* ApplicationPasswordStorage.swift */, EE99814D295AA7430074AE68 /* RequestAuthenticator.swift */, @@ -3361,7 +3361,7 @@ 020D07B823D852BB00FD9580 /* Media.swift in Sources */, B5BB1D0C20A2050300112D92 /* DateFormatter+Woo.swift in Sources */, 743E84EE2217244C00FAC9D7 /* ShipmentTrackingListMapper.swift in Sources */, - DEFBA7542949CE6600C35BA9 /* RequestProcessor.swift in Sources */, + DEFBA7542949CE6600C35BA9 /* ApplicationPasswordRequestProcessor.swift in Sources */, 451A97E5260B631E0059D135 /* ShippingLabelPredefinedPackage.swift in Sources */, BAB373722795A1FB00837B4A /* OrderTaxLine.swift in Sources */, EE54C89F2947782E00A9BF61 /* ApplicationPasswordUseCase.swift in Sources */, diff --git a/Networking/Networking/ApplicationPassword/RequestProcessor.swift b/Networking/Networking/ApplicationPassword/ApplicationPasswordRequestProcessor.swift similarity index 90% rename from Networking/Networking/ApplicationPassword/RequestProcessor.swift rename to Networking/Networking/ApplicationPassword/ApplicationPasswordRequestProcessor.swift index 26360f141e4..0d4d427da53 100644 --- a/Networking/Networking/ApplicationPassword/RequestProcessor.swift +++ b/Networking/Networking/ApplicationPassword/ApplicationPasswordRequestProcessor.swift @@ -3,7 +3,7 @@ import Foundation /// Authenticates and retries requests /// -final class RequestProcessor { +final class ApplicationPasswordRequestProcessor { private var requestsToRetry = [RequestRetryCompletion]() private var isAuthenticating = false @@ -17,7 +17,7 @@ final class RequestProcessor { // MARK: Request Authentication // -extension RequestProcessor: RequestAdapter { +extension ApplicationPasswordRequestProcessor: RequestAdapter { func adapt(_ urlRequest: URLRequest) throws -> URLRequest { return try requestAuthenticator.authenticate(urlRequest) } @@ -25,7 +25,7 @@ extension RequestProcessor: RequestAdapter { // MARK: Retrying Request // -extension RequestProcessor: RequestRetrier { +extension ApplicationPasswordRequestProcessor: RequestRetrier { func should(_ manager: Alamofire.SessionManager, retry request: Alamofire.Request, with error: Error, @@ -48,7 +48,7 @@ extension RequestProcessor: RequestRetrier { // MARK: Helpers // -private extension RequestProcessor { +private extension ApplicationPasswordRequestProcessor { func generateApplicationPassword() { Task(priority: .medium) { isAuthenticating = true diff --git a/Networking/Networking/Network/AlamofireNetwork.swift b/Networking/Networking/Network/AlamofireNetwork.swift index 4cd0f30575e..1ec7f8b53ff 100644 --- a/Networking/Networking/Network/AlamofireNetwork.swift +++ b/Networking/Networking/Network/AlamofireNetwork.swift @@ -28,7 +28,7 @@ public class AlamofireNetwork: Network { /// Authenticator to update requests authorization header if possible. /// - private let requestAuthenticator: RequestProcessor + private let requestAuthenticator: ApplicationPasswordRequestProcessor public var session: URLSession { SessionManager.default.session } @@ -36,7 +36,7 @@ public class AlamofireNetwork: Network { /// public required init(credentials: Credentials?) { self.requestConverter = RequestConverter(credentials: credentials) - self.requestAuthenticator = RequestProcessor(requestAuthenticator: DefaultRequestAuthenticator(credentials: credentials)) + self.requestAuthenticator = ApplicationPasswordRequestProcessor(requestAuthenticator: DefaultRequestAuthenticator(credentials: credentials)) } /// Executes the specified Network Request. Upon completion, the payload will be sent back to the caller as a Data instance. diff --git a/Networking/NetworkingTests/ApplicationPassword/RequestProcessorTests.swift b/Networking/NetworkingTests/ApplicationPassword/RequestProcessorTests.swift index affa03ea631..e3cf391aa9c 100644 --- a/Networking/NetworkingTests/ApplicationPassword/RequestProcessorTests.swift +++ b/Networking/NetworkingTests/ApplicationPassword/RequestProcessorTests.swift @@ -6,7 +6,7 @@ import XCTest /// final class RequestProcessorTests: XCTestCase { private var mockRequestAuthenticator: MockRequestAuthenticator! - private var sut: RequestProcessor! + private var sut: ApplicationPasswordRequestProcessor! private var sessionManager: Alamofire.SessionManager! private let url = URL(string: "https://test.com/")! @@ -16,7 +16,7 @@ final class RequestProcessorTests: XCTestCase { sessionManager = Alamofire.SessionManager(configuration: URLSessionConfiguration.default) mockRequestAuthenticator = MockRequestAuthenticator() - sut = RequestProcessor(requestAuthenticator: mockRequestAuthenticator) + sut = ApplicationPasswordRequestProcessor(requestAuthenticator: mockRequestAuthenticator) } override func tearDown() { From cecb4dc8083f4f0164e4c0e65c9781c8c7c3e102 Mon Sep 17 00:00:00 2001 From: Huong Do Date: Tue, 17 Jan 2023 10:38:33 +0700 Subject: [PATCH 02/17] Rename RequestAuthenticator to ApplicationPasswordRequestAuthenticator --- Networking/Networking.xcodeproj/project.pbxproj | 8 ++++---- ...wift => ApplicationPasswordRequestAuthenticator.swift} | 4 ++-- .../ApplicationPasswordRequestProcessor.swift | 4 ++-- .../ApplicationPassword/RequestProcessorTests.swift | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) rename Networking/Networking/ApplicationPassword/{RequestAuthenticator.swift => ApplicationPasswordRequestAuthenticator.swift} (96%) diff --git a/Networking/Networking.xcodeproj/project.pbxproj b/Networking/Networking.xcodeproj/project.pbxproj index 292e1a60a6b..2a8a094d843 100644 --- a/Networking/Networking.xcodeproj/project.pbxproj +++ b/Networking/Networking.xcodeproj/project.pbxproj @@ -785,7 +785,7 @@ EE80A25029556FBD003591E4 /* coupon-reports-without-data.json in Resources */ = {isa = PBXBuildFile; fileRef = EE80A24F29556FBD003591E4 /* coupon-reports-without-data.json */; }; EE8A86F1286C5226003E8AA4 /* media-update-product-id-in-wordpress-site.json in Resources */ = {isa = PBXBuildFile; fileRef = EE8A86F0286C5226003E8AA4 /* media-update-product-id-in-wordpress-site.json */; }; EE8DE432294B17CD005054E7 /* DefaultApplicationPasswordUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE8DE431294B17CD005054E7 /* DefaultApplicationPasswordUseCaseTests.swift */; }; - EE99814E295AA7430074AE68 /* RequestAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE99814D295AA7430074AE68 /* RequestAuthenticator.swift */; }; + EE99814E295AA7430074AE68 /* ApplicationPasswordRequestAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE99814D295AA7430074AE68 /* ApplicationPasswordRequestAuthenticator.swift */; }; EE998150295AACE10074AE68 /* RequestConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE99814F295AACE10074AE68 /* RequestConverter.swift */; }; EEA6583E2966B41E00112DF0 /* products-load-all-without-data.json in Resources */ = {isa = PBXBuildFile; fileRef = EEA6583D2966B41E00112DF0 /* products-load-all-without-data.json */; }; EEA658402966C05D00112DF0 /* product-search-sku-without-data.json in Resources */ = {isa = PBXBuildFile; fileRef = EEA6583F2966C05D00112DF0 /* product-search-sku-without-data.json */; }; @@ -1614,7 +1614,7 @@ EE80A24F29556FBD003591E4 /* coupon-reports-without-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "coupon-reports-without-data.json"; sourceTree = ""; }; EE8A86F0286C5226003E8AA4 /* media-update-product-id-in-wordpress-site.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "media-update-product-id-in-wordpress-site.json"; sourceTree = ""; }; EE8DE431294B17CD005054E7 /* DefaultApplicationPasswordUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultApplicationPasswordUseCaseTests.swift; sourceTree = ""; }; - EE99814D295AA7430074AE68 /* RequestAuthenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestAuthenticator.swift; sourceTree = ""; }; + EE99814D295AA7430074AE68 /* ApplicationPasswordRequestAuthenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationPasswordRequestAuthenticator.swift; sourceTree = ""; }; EE99814F295AACE10074AE68 /* RequestConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestConverter.swift; sourceTree = ""; }; EEA6583D2966B41E00112DF0 /* products-load-all-without-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "products-load-all-without-data.json"; sourceTree = ""; }; EEA6583F2966C05D00112DF0 /* product-search-sku-without-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "product-search-sku-without-data.json"; sourceTree = ""; }; @@ -2783,7 +2783,7 @@ DEFBA7532949CE6600C35BA9 /* ApplicationPasswordRequestProcessor.swift */, EE54C89E2947782E00A9BF61 /* ApplicationPasswordUseCase.swift */, EE71CC3C2951A8EA0074D908 /* ApplicationPasswordStorage.swift */, - EE99814D295AA7430074AE68 /* RequestAuthenticator.swift */, + EE99814D295AA7430074AE68 /* ApplicationPasswordRequestAuthenticator.swift */, EE99814F295AACE10074AE68 /* RequestConverter.swift */, ); path = ApplicationPassword; @@ -3330,7 +3330,7 @@ 457A574025D1817E000797AD /* ShippingLabelAddressVerification.swift in Sources */, 74ABA1D1213F22CA00FFAD30 /* TopEarnersStatsRemote.swift in Sources */, DEC51AF127699E7A009F3DF4 /* SystemStatus+Page.swift in Sources */, - EE99814E295AA7430074AE68 /* RequestAuthenticator.swift in Sources */, + EE99814E295AA7430074AE68 /* ApplicationPasswordRequestAuthenticator.swift in Sources */, 025CA2C0238EB8CB00B05C81 /* ProductShippingClass.swift in Sources */, 02C1CEF424C6A02B00703EBA /* ProductVariationMapper.swift in Sources */, 3105470C262E27F000C5C02B /* WCPayPaymentIntentStatusEnum.swift in Sources */, diff --git a/Networking/Networking/ApplicationPassword/RequestAuthenticator.swift b/Networking/Networking/ApplicationPassword/ApplicationPasswordRequestAuthenticator.swift similarity index 96% rename from Networking/Networking/ApplicationPassword/RequestAuthenticator.swift rename to Networking/Networking/ApplicationPassword/ApplicationPasswordRequestAuthenticator.swift index 2de5123fe7d..cf0c72b3b54 100644 --- a/Networking/Networking/ApplicationPassword/RequestAuthenticator.swift +++ b/Networking/Networking/ApplicationPassword/ApplicationPasswordRequestAuthenticator.swift @@ -3,7 +3,7 @@ enum RequestAuthenticatorError: Error { case applicationPasswordNotAvailable } -protocol RequestAuthenticator { +protocol ApplicationPasswordRequestAuthenticator { /// Credentials to authenticate the URLRequest /// var credentials: Credentials? { get } @@ -26,7 +26,7 @@ protocol RequestAuthenticator { /// Authenticates request /// -public struct DefaultRequestAuthenticator: RequestAuthenticator { +public struct DefaultRequestAuthenticator: ApplicationPasswordRequestAuthenticator { /// Credentials to authenticate the URLRequest /// let credentials: Credentials? diff --git a/Networking/Networking/ApplicationPassword/ApplicationPasswordRequestProcessor.swift b/Networking/Networking/ApplicationPassword/ApplicationPasswordRequestProcessor.swift index 0d4d427da53..2569315a245 100644 --- a/Networking/Networking/ApplicationPassword/ApplicationPasswordRequestProcessor.swift +++ b/Networking/Networking/ApplicationPassword/ApplicationPasswordRequestProcessor.swift @@ -8,9 +8,9 @@ final class ApplicationPasswordRequestProcessor { private var isAuthenticating = false - private let requestAuthenticator: RequestAuthenticator + private let requestAuthenticator: ApplicationPasswordRequestAuthenticator - init(requestAuthenticator: RequestAuthenticator) { + init(requestAuthenticator: ApplicationPasswordRequestAuthenticator) { self.requestAuthenticator = requestAuthenticator } } diff --git a/Networking/NetworkingTests/ApplicationPassword/RequestProcessorTests.swift b/Networking/NetworkingTests/ApplicationPassword/RequestProcessorTests.swift index e3cf391aa9c..53c460928f3 100644 --- a/Networking/NetworkingTests/ApplicationPassword/RequestProcessorTests.swift +++ b/Networking/NetworkingTests/ApplicationPassword/RequestProcessorTests.swift @@ -221,7 +221,7 @@ private class MockTaskConvertible: TaskConvertible { } } -private class MockRequestAuthenticator: RequestAuthenticator { +private class MockRequestAuthenticator: ApplicationPasswordRequestAuthenticator { var mockedShouldRetryValue: Bool? private(set) var authenticateCalled = false From b08222b4ae990277157f6f4faadc3165019abb39 Mon Sep 17 00:00:00 2001 From: Huong Do Date: Tue, 17 Jan 2023 10:39:14 +0700 Subject: [PATCH 03/17] Rename DefaultRequestAuthenticator to DefaultApplicationPasswordRequestAuthenticator --- .../ApplicationPasswordRequestAuthenticator.swift | 4 ++-- .../Networking/Network/AlamofireNetwork.swift | 2 +- .../Network/DefaultRequestAuthenticatorTests.swift | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Networking/Networking/ApplicationPassword/ApplicationPasswordRequestAuthenticator.swift b/Networking/Networking/ApplicationPassword/ApplicationPasswordRequestAuthenticator.swift index cf0c72b3b54..e5d44328310 100644 --- a/Networking/Networking/ApplicationPassword/ApplicationPasswordRequestAuthenticator.swift +++ b/Networking/Networking/ApplicationPassword/ApplicationPasswordRequestAuthenticator.swift @@ -26,7 +26,7 @@ protocol ApplicationPasswordRequestAuthenticator { /// Authenticates request /// -public struct DefaultRequestAuthenticator: ApplicationPasswordRequestAuthenticator { +public struct DefaultApplicationPasswordRequestAuthenticator: ApplicationPasswordRequestAuthenticator { /// Credentials to authenticate the URLRequest /// let credentials: Credentials? @@ -84,7 +84,7 @@ public struct DefaultRequestAuthenticator: ApplicationPasswordRequestAuthenticat } } -private extension DefaultRequestAuthenticator { +private extension DefaultApplicationPasswordRequestAuthenticator { /// To check whether the given URLRequest is a REST API request /// func isRestAPIRequest(_ urlRequest: URLRequest) -> Bool { diff --git a/Networking/Networking/Network/AlamofireNetwork.swift b/Networking/Networking/Network/AlamofireNetwork.swift index 1ec7f8b53ff..121c9e10129 100644 --- a/Networking/Networking/Network/AlamofireNetwork.swift +++ b/Networking/Networking/Network/AlamofireNetwork.swift @@ -36,7 +36,7 @@ public class AlamofireNetwork: Network { /// public required init(credentials: Credentials?) { self.requestConverter = RequestConverter(credentials: credentials) - self.requestAuthenticator = ApplicationPasswordRequestProcessor(requestAuthenticator: DefaultRequestAuthenticator(credentials: credentials)) + self.requestAuthenticator = ApplicationPasswordRequestProcessor(requestAuthenticator: DefaultApplicationPasswordRequestAuthenticator(credentials: credentials)) } /// Executes the specified Network Request. Upon completion, the payload will be sent back to the caller as a Data instance. diff --git a/Networking/NetworkingTests/Network/DefaultRequestAuthenticatorTests.swift b/Networking/NetworkingTests/Network/DefaultRequestAuthenticatorTests.swift index f2de80abc8c..5d8a02a237e 100644 --- a/Networking/NetworkingTests/Network/DefaultRequestAuthenticatorTests.swift +++ b/Networking/NetworkingTests/Network/DefaultRequestAuthenticatorTests.swift @@ -6,7 +6,7 @@ final class DefaultRequestAuthenticatorTests: XCTestCase { func test_authenticateRequest_returns_unauthenticated_request_for_non_REST_request_without_WPCOM_credentials() throws { // Given - let authenticator = DefaultRequestAuthenticator(credentials: nil) + let authenticator = DefaultApplicationPasswordRequestAuthenticator(credentials: nil) let jetpackRequest = JetpackRequest(wooApiVersion: .mark1, method: .get, siteID: 123, path: "test", availableAsRESTRequest: false) // When @@ -20,7 +20,7 @@ final class DefaultRequestAuthenticatorTests: XCTestCase { func test_authenticatedRequest_returns_authenticated_request_for_non_REST_request_with_WPCOM_credentials() throws { // Given let credentials = Credentials(authToken: "secret") - let authenticator = DefaultRequestAuthenticator(credentials: credentials) + let authenticator = DefaultApplicationPasswordRequestAuthenticator(credentials: credentials) let jetpackRequest = JetpackRequest(wooApiVersion: .mark1, method: .get, siteID: 123, path: "test", availableAsRESTRequest: false) // When @@ -38,7 +38,7 @@ final class DefaultRequestAuthenticatorTests: XCTestCase { let credentials: Credentials = .wporg(username: "admin", password: "supersecret", siteAddress: siteURL) let applicationPassword = ApplicationPassword(wpOrgUsername: credentials.username, password: .init(credentials.secret)) let useCase = MockApplicationPasswordUseCase(mockApplicationPassword: applicationPassword) - let authenticator = DefaultRequestAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) + let authenticator = DefaultApplicationPasswordRequestAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) let wooAPIVersion = WooAPIVersion.mark1 let basePath = RESTRequest.Settings.basePath let restRequest = RESTRequest(siteURL: siteURL, wooApiVersion: wooAPIVersion, method: .get, path: "test") @@ -60,7 +60,7 @@ final class DefaultRequestAuthenticatorTests: XCTestCase { let credentials: Credentials = .wporg(username: "admin", password: "supersecret", siteAddress: siteURL) let applicationPassword = ApplicationPassword(wpOrgUsername: credentials.username, password: .init(credentials.secret)) let useCase = MockApplicationPasswordUseCase(mockGeneratedPassword: applicationPassword) - let authenticator = DefaultRequestAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) + let authenticator = DefaultApplicationPasswordRequestAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) let wooAPIVersion = WooAPIVersion.mark1 let basePath = RESTRequest.Settings.basePath let restRequest = RESTRequest(siteURL: siteURL, wooApiVersion: wooAPIVersion, method: .get, path: "test") @@ -88,7 +88,7 @@ final class DefaultRequestAuthenticatorTests: XCTestCase { let siteURL = "https://test.com/" let credentials: Credentials = .wporg(username: "admin", password: "supersecret", siteAddress: siteURL) let useCase = MockApplicationPasswordUseCase(mockGenerationError: NetworkError.timeout) - let authenticator = DefaultRequestAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) + let authenticator = DefaultApplicationPasswordRequestAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) let wooAPIVersion = WooAPIVersion.mark1 let restRequest = RESTRequest(siteURL: siteURL, wooApiVersion: wooAPIVersion, method: .get, path: "test") @@ -115,7 +115,7 @@ final class DefaultRequestAuthenticatorTests: XCTestCase { let siteURL = "https://test.com/" let credentials: Credentials = .wporg(username: "admin", password: "supersecret", siteAddress: siteURL) let useCase = MockApplicationPasswordUseCase(mockGenerationError: NetworkError.timeout) - let authenticator = DefaultRequestAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) + let authenticator = DefaultApplicationPasswordRequestAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) let wooAPIVersion = WooAPIVersion.mark1 let restRequest = RESTRequest(siteURL: siteURL, wooApiVersion: wooAPIVersion, method: .get, path: "test") @@ -131,7 +131,7 @@ final class DefaultRequestAuthenticatorTests: XCTestCase { let siteURL = "https://test.com/" let credentials: Credentials = .wporg(username: "admin", password: "supersecret", siteAddress: siteURL) let useCase = MockApplicationPasswordUseCase(mockGenerationError: NetworkError.timeout) - let authenticator = DefaultRequestAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) + let authenticator = DefaultApplicationPasswordRequestAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) let jetpackRequest = JetpackRequest(wooApiVersion: .mark1, method: .get, siteID: 123, path: "test", availableAsRESTRequest: false) // When From 3461885e95fbfdc9ea46cf82818cf0c7f3bbdee8 Mon Sep 17 00:00:00 2001 From: Huong Do Date: Tue, 17 Jan 2023 10:40:09 +0700 Subject: [PATCH 04/17] Rename RequestAuthenticatorError to ApplicationPasswordRequestAuthenticatorError --- .../ApplicationPasswordRequestAuthenticator.swift | 6 +++--- .../ApplicationPasswordRequestProcessor.swift | 2 +- .../RequestProcessorTests.swift | 14 +++++++------- .../Network/DefaultRequestAuthenticatorTests.swift | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Networking/Networking/ApplicationPassword/ApplicationPasswordRequestAuthenticator.swift b/Networking/Networking/ApplicationPassword/ApplicationPasswordRequestAuthenticator.swift index e5d44328310..ba5edb733e1 100644 --- a/Networking/Networking/ApplicationPassword/ApplicationPasswordRequestAuthenticator.swift +++ b/Networking/Networking/ApplicationPassword/ApplicationPasswordRequestAuthenticator.swift @@ -1,4 +1,4 @@ -enum RequestAuthenticatorError: Error { +enum ApplicationPasswordRequestAuthenticatorError: Error { case applicationPasswordUseCaseNotAvailable case applicationPasswordNotAvailable } @@ -71,7 +71,7 @@ public struct DefaultApplicationPasswordRequestAuthenticator: ApplicationPasswor /// func generateApplicationPassword() async throws { guard let applicationPasswordUseCase else { - throw RequestAuthenticatorError.applicationPasswordUseCaseNotAvailable + throw ApplicationPasswordRequestAuthenticatorError.applicationPasswordUseCaseNotAvailable } let _ = try await applicationPasswordUseCase.generateNewPassword() return @@ -110,7 +110,7 @@ private extension DefaultApplicationPasswordRequestAuthenticator { /// func authenticateUsingApplicationPasswordIfPossible(_ urlRequest: URLRequest) throws -> URLRequest { guard let applicationPassword = applicationPasswordUseCase?.applicationPassword else { - throw RequestAuthenticatorError.applicationPasswordNotAvailable + throw ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable } return AuthenticatedRESTRequest(applicationPassword: applicationPassword, request: urlRequest).asURLRequest() diff --git a/Networking/Networking/ApplicationPassword/ApplicationPasswordRequestProcessor.swift b/Networking/Networking/ApplicationPassword/ApplicationPasswordRequestProcessor.swift index 2569315a245..9029db226d0 100644 --- a/Networking/Networking/ApplicationPassword/ApplicationPasswordRequestProcessor.swift +++ b/Networking/Networking/ApplicationPassword/ApplicationPasswordRequestProcessor.swift @@ -66,7 +66,7 @@ private extension ApplicationPasswordRequestProcessor { func shouldRetry(_ error: Error) -> Bool { // Need to generate application password - if .applicationPasswordNotAvailable == error as? RequestAuthenticatorError { + if .applicationPasswordNotAvailable == error as? ApplicationPasswordRequestAuthenticatorError { return true } diff --git a/Networking/NetworkingTests/ApplicationPassword/RequestProcessorTests.swift b/Networking/NetworkingTests/ApplicationPassword/RequestProcessorTests.swift index 53c460928f3..e293e6b0689 100644 --- a/Networking/NetworkingTests/ApplicationPassword/RequestProcessorTests.swift +++ b/Networking/NetworkingTests/ApplicationPassword/RequestProcessorTests.swift @@ -50,7 +50,7 @@ final class RequestProcessorTests: XCTestCase { // When request.retryCount = 0 let shouldRetry = waitFor { promise in - self.sut.should(sessionManager, retry: request, with: RequestAuthenticatorError.applicationPasswordNotAvailable) { shouldRetry, timeDelay in + self.sut.should(sessionManager, retry: request, with: ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable) { shouldRetry, timeDelay in promise(shouldRetry) } } @@ -67,7 +67,7 @@ final class RequestProcessorTests: XCTestCase { // When request.retryCount = 1 let shouldRetry = waitFor { promise in - self.sut.should(sessionManager, retry: request, with: RequestAuthenticatorError.applicationPasswordNotAvailable) { shouldRetry, timeDelay in + self.sut.should(sessionManager, retry: request, with: ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable) { shouldRetry, timeDelay in promise(shouldRetry) } } @@ -86,7 +86,7 @@ final class RequestProcessorTests: XCTestCase { // When mockRequestAuthenticator.mockedShouldRetryValue = true let shouldRetry = waitFor { promise in - self.sut.should(sessionManager, retry: request, with: RequestAuthenticatorError.applicationPasswordNotAvailable) { shouldRetry, timeDelay in + self.sut.should(sessionManager, retry: request, with: ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable) { shouldRetry, timeDelay in promise(shouldRetry) } } @@ -103,7 +103,7 @@ final class RequestProcessorTests: XCTestCase { // When mockRequestAuthenticator.mockedShouldRetryValue = false let shouldRetry = waitFor { promise in - self.sut.should(sessionManager, retry: request, with: RequestAuthenticatorError.applicationPasswordNotAvailable) { shouldRetry, timeDelay in + self.sut.should(sessionManager, retry: request, with: ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable) { shouldRetry, timeDelay in promise(shouldRetry) } } @@ -120,7 +120,7 @@ final class RequestProcessorTests: XCTestCase { let request = try mockRequest() // When - let error = RequestAuthenticatorError.applicationPasswordNotAvailable + let error = ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable let shouldRetry = waitFor { promise in self.sut.should(sessionManager, retry: request, with: error) { shouldRetry, timeDelay in promise(shouldRetry) @@ -173,7 +173,7 @@ final class RequestProcessorTests: XCTestCase { let request = try mockRequest() // When - let error = RequestAuthenticatorError.applicationPasswordNotAvailable + let error = ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable waitFor { promise in self.sut.should(sessionManager, retry: request, with: error) { shouldRetry, timeDelay in promise(()) @@ -192,7 +192,7 @@ final class RequestProcessorTests: XCTestCase { // When mockRequestAuthenticator.mockedShouldRetryValue = false waitFor { promise in - self.sut.should(sessionManager, retry: request, with: RequestAuthenticatorError.applicationPasswordNotAvailable) { shouldRetry, timeDelay in + self.sut.should(sessionManager, retry: request, with: ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable) { shouldRetry, timeDelay in promise(()) } } diff --git a/Networking/NetworkingTests/Network/DefaultRequestAuthenticatorTests.swift b/Networking/NetworkingTests/Network/DefaultRequestAuthenticatorTests.swift index 5d8a02a237e..904d3dcfc7a 100644 --- a/Networking/NetworkingTests/Network/DefaultRequestAuthenticatorTests.swift +++ b/Networking/NetworkingTests/Network/DefaultRequestAuthenticatorTests.swift @@ -70,7 +70,7 @@ final class DefaultRequestAuthenticatorTests: XCTestCase { do { let _ = try authenticator.authenticate(request) - } catch RequestAuthenticatorError.applicationPasswordNotAvailable { + } catch ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable { try await authenticator.generateApplicationPassword() } @@ -98,7 +98,7 @@ final class DefaultRequestAuthenticatorTests: XCTestCase { let request = try restRequest.asURLRequest() do { let _ = try authenticator.authenticate(request) - } catch RequestAuthenticatorError.applicationPasswordNotAvailable { + } catch ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable { // Then do { try await authenticator.generateApplicationPassword() From 09c047a84b10967a1c72f9a49cbc226337171412 Mon Sep 17 00:00:00 2001 From: Huong Do Date: Tue, 17 Jan 2023 11:32:58 +0700 Subject: [PATCH 05/17] Fix line limit for tests --- .../RequestProcessorTests.swift | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Networking/NetworkingTests/ApplicationPassword/RequestProcessorTests.swift b/Networking/NetworkingTests/ApplicationPassword/RequestProcessorTests.swift index e293e6b0689..7db65101c18 100644 --- a/Networking/NetworkingTests/ApplicationPassword/RequestProcessorTests.swift +++ b/Networking/NetworkingTests/ApplicationPassword/RequestProcessorTests.swift @@ -50,7 +50,8 @@ final class RequestProcessorTests: XCTestCase { // When request.retryCount = 0 let shouldRetry = waitFor { promise in - self.sut.should(sessionManager, retry: request, with: ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable) { shouldRetry, timeDelay in + let error = ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable + self.sut.should(sessionManager, retry: request, with: error) { shouldRetry, timeDelay in promise(shouldRetry) } } @@ -67,7 +68,8 @@ final class RequestProcessorTests: XCTestCase { // When request.retryCount = 1 let shouldRetry = waitFor { promise in - self.sut.should(sessionManager, retry: request, with: ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable) { shouldRetry, timeDelay in + let error = ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable + self.sut.should(sessionManager, retry: request, with: error) { shouldRetry, timeDelay in promise(shouldRetry) } } @@ -86,7 +88,8 @@ final class RequestProcessorTests: XCTestCase { // When mockRequestAuthenticator.mockedShouldRetryValue = true let shouldRetry = waitFor { promise in - self.sut.should(sessionManager, retry: request, with: ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable) { shouldRetry, timeDelay in + let error = ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable + self.sut.should(sessionManager, retry: request, with: error) { shouldRetry, timeDelay in promise(shouldRetry) } } @@ -103,7 +106,8 @@ final class RequestProcessorTests: XCTestCase { // When mockRequestAuthenticator.mockedShouldRetryValue = false let shouldRetry = waitFor { promise in - self.sut.should(sessionManager, retry: request, with: ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable) { shouldRetry, timeDelay in + let error = ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable + self.sut.should(sessionManager, retry: request, with: error) { shouldRetry, timeDelay in promise(shouldRetry) } } @@ -192,7 +196,8 @@ final class RequestProcessorTests: XCTestCase { // When mockRequestAuthenticator.mockedShouldRetryValue = false waitFor { promise in - self.sut.should(sessionManager, retry: request, with: ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable) { shouldRetry, timeDelay in + let error = ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable + self.sut.should(sessionManager, retry: request, with: error) { shouldRetry, timeDelay in promise(()) } } From c8900ffffe2a9c7a4adb2bf7310504dcd8ff032c Mon Sep 17 00:00:00 2001 From: Huong Do Date: Tue, 17 Jan 2023 11:34:05 +0700 Subject: [PATCH 06/17] Add custom CookieNonceAuthenticator --- .../Networking.xcodeproj/project.pbxproj | 12 ++ .../CookieNonceAuthenticator.swift | 169 ++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift diff --git a/Networking/Networking.xcodeproj/project.pbxproj b/Networking/Networking.xcodeproj/project.pbxproj index 2a8a094d843..bf38b8b75bd 100644 --- a/Networking/Networking.xcodeproj/project.pbxproj +++ b/Networking/Networking.xcodeproj/project.pbxproj @@ -716,6 +716,7 @@ DE50296328C609DE00551736 /* jetpack-user-not-connected.json in Resources */ = {isa = PBXBuildFile; fileRef = DE50296228C609DE00551736 /* jetpack-user-not-connected.json */; }; DE50296528C60A8000551736 /* JetpackUserMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE50296428C60A8000551736 /* JetpackUserMapperTests.swift */; }; DE5CA111288A3E080077BEF9 /* product-malformed-variations-and-image-alt.json in Resources */ = {isa = PBXBuildFile; fileRef = DE5CA110288A3E080077BEF9 /* product-malformed-variations-and-image-alt.json */; }; + DE66C5532976508300DAA978 /* CookieNonceAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE66C5522976508300DAA978 /* CookieNonceAuthenticator.swift */; }; DE6F308727966FEF004E1C9A /* CouponReportListMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE6F308627966FEF004E1C9A /* CouponReportListMapperTests.swift */; }; DE74F29A27E08F5A0002FE59 /* SiteSettingMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE74F29927E08F5A0002FE59 /* SiteSettingMapper.swift */; }; DE74F29C27E0A1D00002FE59 /* setting-coupon.json in Resources */ = {isa = PBXBuildFile; fileRef = DE74F29B27E0A1D00002FE59 /* setting-coupon.json */; }; @@ -1545,6 +1546,7 @@ DE50296228C609DE00551736 /* jetpack-user-not-connected.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "jetpack-user-not-connected.json"; sourceTree = ""; }; DE50296428C60A8000551736 /* JetpackUserMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackUserMapperTests.swift; sourceTree = ""; }; DE5CA110288A3E080077BEF9 /* product-malformed-variations-and-image-alt.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "product-malformed-variations-and-image-alt.json"; sourceTree = ""; }; + DE66C5522976508300DAA978 /* CookieNonceAuthenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CookieNonceAuthenticator.swift; sourceTree = ""; }; DE6F308627966FEF004E1C9A /* CouponReportListMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CouponReportListMapperTests.swift; sourceTree = ""; }; DE74F29927E08F5A0002FE59 /* SiteSettingMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteSettingMapper.swift; sourceTree = ""; }; DE74F29B27E0A1D00002FE59 /* setting-coupon.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "setting-coupon.json"; sourceTree = ""; }; @@ -1953,6 +1955,7 @@ B557D9E5209753AA005962F4 /* Networking */ = { isa = PBXGroup; children = ( + DE66C5512976507100DAA978 /* CookieNonce */, EE54C899294777D000A9BF61 /* ApplicationPassword */, B5A0369F214C0F4C00774E2C /* Internal */, B5BB1D0A20A204F400112D92 /* Extensions */, @@ -2746,6 +2749,14 @@ path = Product; sourceTree = ""; }; + DE66C5512976507100DAA978 /* CookieNonce */ = { + isa = PBXGroup; + children = ( + DE66C5522976508300DAA978 /* CookieNonceAuthenticator.swift */, + ); + path = CookieNonce; + sourceTree = ""; + }; DE97C3902861B8CD0042E973 /* Encoder */ = { isa = PBXGroup; children = ( @@ -3345,6 +3356,7 @@ 451A97E9260B657D0059D135 /* ShippingLabelPredefinedOption.swift in Sources */, 02C2548425635BD000A04423 /* ShippingLabelPaperSize.swift in Sources */, CE132BBC223859710029DB6C /* ProductTag.swift in Sources */, + DE66C5532976508300DAA978 /* CookieNonceAuthenticator.swift in Sources */, 26650332261FFA1A0079A159 /* ProductAddOnEnvelope.swift in Sources */, D88D5A47230BC838007B6E01 /* ProductReview.swift in Sources */, DEFBA74E29485A7600C35BA9 /* RESTRequest.swift in Sources */, diff --git a/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift b/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift new file mode 100644 index 00000000000..5657308a44c --- /dev/null +++ b/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift @@ -0,0 +1,169 @@ +import Alamofire +import Foundation + +public class CookieNonceAuthenticator: RequestRetrier & RequestAdapter { + private let username: String + private let password: String + private let loginURL: URL + private let adminURL: URL + private var nonce: String? + // If we can't get this to work once, don't retry for the same site + // It is likely that there is something preventing us from extracting a nonce + private var canRetry = true + private var isAuthenticating = false + private var requestsToRetry = [RequestRetryCompletion]() + + public init(configuration: CookieNonceAuthenticatorConfiguration) { + self.username = configuration.username + self.password = configuration.password + self.loginURL = configuration.loginURL + self.adminURL = configuration.adminURL + } + + // MARK: Request Adapter + + public func adapt(_ urlRequest: URLRequest) throws -> URLRequest { + guard let nonce = nonce else { + return urlRequest + } + var adaptedRequest = urlRequest + adaptedRequest.addValue(nonce, forHTTPHeaderField: "X-WP-Nonce") + return adaptedRequest + } + + // MARK: Retrier + public func should(_ manager: SessionManager, retry request: Alamofire.Request, with error: Swift.Error, completion: @escaping RequestRetryCompletion) { + guard + canRetry, + // Only retry once + request.retryCount == 0, + // And don't retry the login request + request.request?.url != loginURL, + // Only retry because of failed authorization + case .responseValidationFailed(reason: .unacceptableStatusCode(code: 401)) = error as? AFError + else { + return completion(false, 0.0) + } + + requestsToRetry.append(completion) + if !isAuthenticating { + startLoginSequence(manager: manager) + } + } + + enum Error: Swift.Error { + case invalidNewPostURL + case postLoginFailed(Swift.Error) + case missingNonce + case unknown(Swift.Error) + } +} + +// MARK: Private helpers +private extension CookieNonceAuthenticator { + + func startLoginSequence(manager: SessionManager) { + DDLogInfo("Starting Cookie+Nonce login sequence for \(loginURL)") + guard let nonceRetrievalURL = buildNonceRequestURL(base: adminURL), + let nonceRequest = try? URLRequest(url: nonceRetrievalURL, method: .get) else { + return invalidateLoginSequence(error: .invalidNewPostURL) + } + Task(priority: .medium) { + do { + try await handleSiteCredentialLogin(manager: manager) + let page = try await handleNonceRetrieval(request: nonceRequest, manager: manager) + guard let nonce = readNonceFromAjaxAction(html: page) else { + throw CookieNonceAuthenticator.Error.missingNonce + } + self.nonce = nonce + self.successfulLoginSequence() + } catch let error as CookieNonceAuthenticator.Error { + self.invalidateLoginSequence(error: error) + } catch { + DDLogError("⛔️ Cookie nonce authenticator failed with uncaught error: \(error)") + } + } + } + + func handleSiteCredentialLogin(manager: SessionManager) async throws { + let request = authenticatedRequest() + return try await withCheckedThrowingContinuation { continuation in + manager.request(request) + .validate() + .response { response in + if let error = response.error { + continuation.resume(throwing: error) + } else { + continuation.resume(returning: ()) + } + } + } + } + + func handleNonceRetrieval(request: URLRequest, manager: SessionManager) async throws -> String { + try await withCheckedThrowingContinuation { continuation -> Void in + manager.request(request) + .validate() + .responseString { response in + switch response.result { + case .failure(let error): + continuation.resume(throwing: error) + case .success(let page): + continuation.resume(returning: page) + } + } + } + } + + func successfulLoginSequence() { + DDLogInfo("Completed Cookie+Nonce login sequence for \(loginURL)") + completeRequests(true) + } + + func invalidateLoginSequence(error: Error) { + canRetry = false + if case .postLoginFailed(let originalError) = error { + let nsError = originalError as NSError + if nsError.domain == NSURLErrorDomain, nsError.code == NSURLErrorNotConnectedToInternet { + canRetry = true + } + } + DDLogInfo("Aborting Cookie+Nonce login sequence for \(loginURL)") + completeRequests(false) + isAuthenticating = false + } + + func completeRequests(_ shouldRetry: Bool) { + requestsToRetry.forEach { (completion) in + completion(shouldRetry, 0.0) + } + requestsToRetry.removeAll() + } + + func authenticatedRequest() -> URLRequest { + var request = URLRequest(url: loginURL) + + request.httpMethod = "POST" + request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") + + var parameters = [URLQueryItem]() + parameters.append(URLQueryItem(name: "log", value: username)) + parameters.append(URLQueryItem(name: "pwd", value: password)) + parameters.append(URLQueryItem(name: "rememberme", value: "true")) + var components = URLComponents() + components.queryItems = parameters + request.httpBody = components.percentEncodedQuery?.data(using: .utf8) + return request + } + + func readNonceFromAjaxAction(html: String) -> String? { + guard !html.isEmpty else { + return nil + } + return html + } + + func buildNonceRequestURL(base: URL) -> URL? { + URL(string: "admin-ajax.php?action=rest-nonce", relativeTo: base) + } +} From 01b921bbe68bc47db77c933d419d895883ff644a Mon Sep 17 00:00:00 2001 From: Huong Do Date: Tue, 17 Jan 2023 11:35:59 +0700 Subject: [PATCH 07/17] Update initializer for WordPressOrgNetwork to use configuration --- .../ApplicationPasswordUseCase.swift | 13 ++++----- .../Network/WordPressOrgNetwork.swift | 23 ++++++++++++--- .../SiteCredentialLoginViewModel.swift | 16 ++++------ .../WrongAccountErrorViewModel.swift | 4 +-- ...ordPressOrgCredentials+Authenticator.swift | 29 +++++-------------- 5 files changed, 39 insertions(+), 46 deletions(-) diff --git a/Networking/Networking/ApplicationPassword/ApplicationPasswordUseCase.swift b/Networking/Networking/ApplicationPassword/ApplicationPasswordUseCase.swift index ab9f1eecc45..8404da29d21 100644 --- a/Networking/Networking/ApplicationPassword/ApplicationPasswordUseCase.swift +++ b/Networking/Networking/ApplicationPassword/ApplicationPasswordUseCase.swift @@ -84,13 +84,11 @@ final public class DefaultApplicationPasswordUseCase: ApplicationPasswordUseCase throw ApplicationPasswordUseCaseError.failedToConstructLoginOrAdminURLUsingSiteAddress } // Prepares the authenticator with username and password - let authenticator = CookieNonceAuthenticator(username: username, - password: password, - loginURL: loginURL, - adminURL: adminURL, - version: Constants.defaultWPVersion, - nonce: nil) - self.network = WordPressOrgNetwork(authenticator: authenticator) + let config = CookieNonceAuthenticatorConfiguration(username: username, + password: password, + loginURL: loginURL, + adminURL: adminURL) + self.network = WordPressOrgNetwork(configuration: config) } } @@ -218,6 +216,5 @@ private extension DefaultApplicationPasswordUseCase { enum Constants { static let loginPath = "/wp-login.php" static let adminPath = "/wp-admin/" - static let defaultWPVersion = "5.6.0" // a default version that supports Ajax nonce retrieval } } diff --git a/Networking/Networking/Network/WordPressOrgNetwork.swift b/Networking/Networking/Network/WordPressOrgNetwork.swift index 0735891da04..271db38ab5f 100644 --- a/Networking/Networking/Network/WordPressOrgNetwork.swift +++ b/Networking/Networking/Network/WordPressOrgNetwork.swift @@ -1,13 +1,28 @@ import Alamofire import Combine import Foundation -import WordPressKit + +/// Configuration for handling cookie nonce authentication. +/// +public struct CookieNonceAuthenticatorConfiguration { + let username: String + let password: String + let loginURL: URL + let adminURL: URL + + public init(username: String, password: String, loginURL: URL, adminURL: URL) { + self.username = username + self.password = password + self.loginURL = loginURL + self.adminURL = adminURL + } +} /// Class to handle WP.org REST API requests. /// public final class WordPressOrgNetwork: Network { - private let authenticator: Authenticator? + private let authenticator: CookieNonceAuthenticator private let userAgent: String? private lazy var sessionManager: Alamofire.SessionManager = { @@ -27,8 +42,8 @@ public final class WordPressOrgNetwork: Network { public var session: URLSession { sessionManager.session } - public init(authenticator: Authenticator? = nil, userAgent: String = UserAgent.defaultUserAgent) { - self.authenticator = authenticator + public init(configuration: CookieNonceAuthenticatorConfiguration, userAgent: String = UserAgent.defaultUserAgent) { + self.authenticator = CookieNonceAuthenticator(configuration: configuration) self.userAgent = userAgent } diff --git a/WooCommerce/Classes/Authentication/Jetpack Setup/Site Credential Login/SiteCredentialLoginViewModel.swift b/WooCommerce/Classes/Authentication/Jetpack Setup/Site Credential Login/SiteCredentialLoginViewModel.swift index 61b175de9ed..ea28ea2f6ea 100644 --- a/WooCommerce/Classes/Authentication/Jetpack Setup/Site Credential Login/SiteCredentialLoginViewModel.swift +++ b/WooCommerce/Classes/Authentication/Jetpack Setup/Site Credential Login/SiteCredentialLoginViewModel.swift @@ -1,9 +1,8 @@ import Foundation import Yosemite -import WordPressKit import WordPressAuthenticator import enum Alamofire.AFError -import class Networking.UserAgent +import struct Networking.CookieNonceAuthenticatorConfiguration import class Networking.WordPressOrgNetwork /// View model for `SiteCredentialLoginView`. @@ -78,13 +77,11 @@ private extension SiteCredentialLoginViewModel { return } // Prepares the authenticator with username and password - let authenticator = CookieNonceAuthenticator(username: username, - password: password, - loginURL: loginURL, - adminURL: adminURL, - version: Constants.defaultWPVersion, - nonce: nil) - let network = WordPressOrgNetwork(authenticator: authenticator) + let config = CookieNonceAuthenticatorConfiguration(username: username, + password: password, + loginURL: loginURL, + adminURL: adminURL) + let network = WordPressOrgNetwork(configuration: config) let authenticationAction = JetpackConnectionAction.authenticate(siteURL: siteURL, network: network) stores.dispatch(authenticationAction) } @@ -141,6 +138,5 @@ extension SiteCredentialLoginViewModel { enum Constants { static let loginPath = "/wp-login.php" static let adminPath = "/wp-admin/" - static let defaultWPVersion = "5.6.0" // a default version that supports Ajax nonce retrieval } } diff --git a/WooCommerce/Classes/Authentication/Navigation Exceptions/WrongAccountErrorViewModel.swift b/WooCommerce/Classes/Authentication/Navigation Exceptions/WrongAccountErrorViewModel.swift index 49364a6522c..86128cf30f4 100644 --- a/WooCommerce/Classes/Authentication/Navigation Exceptions/WrongAccountErrorViewModel.swift +++ b/WooCommerce/Classes/Authentication/Navigation Exceptions/WrongAccountErrorViewModel.swift @@ -244,10 +244,10 @@ private extension WrongAccountErrorViewModel { /// Prepares `JetpackConnectionStore` to authenticate subsequent requests to WP.org API. /// func authenticate(with credentials: WordPressOrgCredentials) { - guard let authenticator = credentials.makeCookieNonceAuthenticator() else { + guard let config = credentials.makeCookieNonceAuthenticatorConfig() else { return } - let network = WordPressOrgNetwork(authenticator: authenticator) + let network = WordPressOrgNetwork(configuration: config) let action = JetpackConnectionAction.authenticate(siteURL: siteURL, network: network) storesManager.dispatch(action) } diff --git a/WooCommerce/Classes/Authentication/WordPressOrgCredentials+Authenticator.swift b/WooCommerce/Classes/Authentication/WordPressOrgCredentials+Authenticator.swift index 4009828bf4c..0a0add10237 100644 --- a/WooCommerce/Classes/Authentication/WordPressOrgCredentials+Authenticator.swift +++ b/WooCommerce/Classes/Authentication/WordPressOrgCredentials+Authenticator.swift @@ -1,6 +1,6 @@ import Foundation -import WordPressKit import WordPressAuthenticator +import struct Networking.CookieNonceAuthenticatorConfiguration /// Extension to create cookie nonce authenticator from WP.org credentials. /// @@ -15,31 +15,17 @@ extension WordPressOrgCredentials { return value ?? siteURL + Strings.adminPath } - var version: String { - let value = optionValue(for: Key.version.rawValue) - if let stringValue = value as? String { - return stringValue - } - - if let numberValue = value as? NSNumber { - return numberValue.stringValue - } - - return "" - } - - /// Returns a cookie nonce authenticator based on the current credentials + /// Returns a cookie nonce authenticator configuration based on the current credentials /// - func makeCookieNonceAuthenticator() -> CookieNonceAuthenticator? { + func makeCookieNonceAuthenticatorConfig() -> CookieNonceAuthenticatorConfiguration? { guard let loginURL = URL(string: loginURL), let adminURL = URL(string: adminURL) else { return nil } - return CookieNonceAuthenticator(username: username, - password: password, - loginURL: loginURL, - adminURL: adminURL, - version: version) + return CookieNonceAuthenticatorConfiguration(username: username, + password: password, + loginURL: loginURL, + adminURL: adminURL) } } @@ -65,7 +51,6 @@ private extension WordPressOrgCredentials { enum Key: String { case loginURL = "login_url" case adminURL = "admin_url" - case version = "software_version" case value } } From eabb34e77d4032f9809ea091dc683b058e78915a Mon Sep 17 00:00:00 2001 From: Huong Do Date: Tue, 17 Jan 2023 11:37:26 +0700 Subject: [PATCH 08/17] Make CookieNonceAuthenticator final and internal --- .../Networking/CookieNonce/CookieNonceAuthenticator.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift b/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift index 5657308a44c..7d9de2eb8a0 100644 --- a/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift +++ b/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift @@ -1,7 +1,7 @@ import Alamofire import Foundation -public class CookieNonceAuthenticator: RequestRetrier & RequestAdapter { +final class CookieNonceAuthenticator: RequestRetrier & RequestAdapter { private let username: String private let password: String private let loginURL: URL @@ -13,7 +13,7 @@ public class CookieNonceAuthenticator: RequestRetrier & RequestAdapter { private var isAuthenticating = false private var requestsToRetry = [RequestRetryCompletion]() - public init(configuration: CookieNonceAuthenticatorConfiguration) { + init(configuration: CookieNonceAuthenticatorConfiguration) { self.username = configuration.username self.password = configuration.password self.loginURL = configuration.loginURL @@ -22,7 +22,7 @@ public class CookieNonceAuthenticator: RequestRetrier & RequestAdapter { // MARK: Request Adapter - public func adapt(_ urlRequest: URLRequest) throws -> URLRequest { + func adapt(_ urlRequest: URLRequest) throws -> URLRequest { guard let nonce = nonce else { return urlRequest } @@ -32,7 +32,7 @@ public class CookieNonceAuthenticator: RequestRetrier & RequestAdapter { } // MARK: Retrier - public func should(_ manager: SessionManager, retry request: Alamofire.Request, with error: Swift.Error, completion: @escaping RequestRetryCompletion) { + func should(_ manager: SessionManager, retry request: Alamofire.Request, with error: Swift.Error, completion: @escaping RequestRetryCompletion) { guard canRetry, // Only retry once From 4613f4a111ef716057ca671c2124497d9019be52 Mon Sep 17 00:00:00 2001 From: Huong Do Date: Tue, 17 Jan 2023 11:39:51 +0700 Subject: [PATCH 09/17] Shorten name for ApplicationPasswordAuthenticator --- .../Networking.xcodeproj/project.pbxproj | 8 ++++---- ... => ApplicationPasswordAuthenticator.swift} | 12 ++++++------ .../ApplicationPasswordRequestProcessor.swift | 6 +++--- .../Networking/Network/AlamofireNetwork.swift | 2 +- .../RequestProcessorTests.swift | 16 ++++++++-------- .../DefaultRequestAuthenticatorTests.swift | 18 +++++++++--------- 6 files changed, 31 insertions(+), 31 deletions(-) rename Networking/Networking/ApplicationPassword/{ApplicationPasswordRequestAuthenticator.swift => ApplicationPasswordAuthenticator.swift} (89%) diff --git a/Networking/Networking.xcodeproj/project.pbxproj b/Networking/Networking.xcodeproj/project.pbxproj index bf38b8b75bd..c2c4466d51a 100644 --- a/Networking/Networking.xcodeproj/project.pbxproj +++ b/Networking/Networking.xcodeproj/project.pbxproj @@ -786,7 +786,7 @@ EE80A25029556FBD003591E4 /* coupon-reports-without-data.json in Resources */ = {isa = PBXBuildFile; fileRef = EE80A24F29556FBD003591E4 /* coupon-reports-without-data.json */; }; EE8A86F1286C5226003E8AA4 /* media-update-product-id-in-wordpress-site.json in Resources */ = {isa = PBXBuildFile; fileRef = EE8A86F0286C5226003E8AA4 /* media-update-product-id-in-wordpress-site.json */; }; EE8DE432294B17CD005054E7 /* DefaultApplicationPasswordUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE8DE431294B17CD005054E7 /* DefaultApplicationPasswordUseCaseTests.swift */; }; - EE99814E295AA7430074AE68 /* ApplicationPasswordRequestAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE99814D295AA7430074AE68 /* ApplicationPasswordRequestAuthenticator.swift */; }; + EE99814E295AA7430074AE68 /* ApplicationPasswordAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE99814D295AA7430074AE68 /* ApplicationPasswordAuthenticator.swift */; }; EE998150295AACE10074AE68 /* RequestConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE99814F295AACE10074AE68 /* RequestConverter.swift */; }; EEA6583E2966B41E00112DF0 /* products-load-all-without-data.json in Resources */ = {isa = PBXBuildFile; fileRef = EEA6583D2966B41E00112DF0 /* products-load-all-without-data.json */; }; EEA658402966C05D00112DF0 /* product-search-sku-without-data.json in Resources */ = {isa = PBXBuildFile; fileRef = EEA6583F2966C05D00112DF0 /* product-search-sku-without-data.json */; }; @@ -1616,7 +1616,7 @@ EE80A24F29556FBD003591E4 /* coupon-reports-without-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "coupon-reports-without-data.json"; sourceTree = ""; }; EE8A86F0286C5226003E8AA4 /* media-update-product-id-in-wordpress-site.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "media-update-product-id-in-wordpress-site.json"; sourceTree = ""; }; EE8DE431294B17CD005054E7 /* DefaultApplicationPasswordUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultApplicationPasswordUseCaseTests.swift; sourceTree = ""; }; - EE99814D295AA7430074AE68 /* ApplicationPasswordRequestAuthenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationPasswordRequestAuthenticator.swift; sourceTree = ""; }; + EE99814D295AA7430074AE68 /* ApplicationPasswordAuthenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationPasswordAuthenticator.swift; sourceTree = ""; }; EE99814F295AACE10074AE68 /* RequestConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestConverter.swift; sourceTree = ""; }; EEA6583D2966B41E00112DF0 /* products-load-all-without-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "products-load-all-without-data.json"; sourceTree = ""; }; EEA6583F2966C05D00112DF0 /* product-search-sku-without-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "product-search-sku-without-data.json"; sourceTree = ""; }; @@ -2794,7 +2794,7 @@ DEFBA7532949CE6600C35BA9 /* ApplicationPasswordRequestProcessor.swift */, EE54C89E2947782E00A9BF61 /* ApplicationPasswordUseCase.swift */, EE71CC3C2951A8EA0074D908 /* ApplicationPasswordStorage.swift */, - EE99814D295AA7430074AE68 /* ApplicationPasswordRequestAuthenticator.swift */, + EE99814D295AA7430074AE68 /* ApplicationPasswordAuthenticator.swift */, EE99814F295AACE10074AE68 /* RequestConverter.swift */, ); path = ApplicationPassword; @@ -3341,7 +3341,7 @@ 457A574025D1817E000797AD /* ShippingLabelAddressVerification.swift in Sources */, 74ABA1D1213F22CA00FFAD30 /* TopEarnersStatsRemote.swift in Sources */, DEC51AF127699E7A009F3DF4 /* SystemStatus+Page.swift in Sources */, - EE99814E295AA7430074AE68 /* ApplicationPasswordRequestAuthenticator.swift in Sources */, + EE99814E295AA7430074AE68 /* ApplicationPasswordAuthenticator.swift in Sources */, 025CA2C0238EB8CB00B05C81 /* ProductShippingClass.swift in Sources */, 02C1CEF424C6A02B00703EBA /* ProductVariationMapper.swift in Sources */, 3105470C262E27F000C5C02B /* WCPayPaymentIntentStatusEnum.swift in Sources */, diff --git a/Networking/Networking/ApplicationPassword/ApplicationPasswordRequestAuthenticator.swift b/Networking/Networking/ApplicationPassword/ApplicationPasswordAuthenticator.swift similarity index 89% rename from Networking/Networking/ApplicationPassword/ApplicationPasswordRequestAuthenticator.swift rename to Networking/Networking/ApplicationPassword/ApplicationPasswordAuthenticator.swift index ba5edb733e1..8395e68a512 100644 --- a/Networking/Networking/ApplicationPassword/ApplicationPasswordRequestAuthenticator.swift +++ b/Networking/Networking/ApplicationPassword/ApplicationPasswordAuthenticator.swift @@ -1,9 +1,9 @@ -enum ApplicationPasswordRequestAuthenticatorError: Error { +enum ApplicationPasswordAuthenticatorError: Error { case applicationPasswordUseCaseNotAvailable case applicationPasswordNotAvailable } -protocol ApplicationPasswordRequestAuthenticator { +protocol ApplicationPasswordAuthenticator { /// Credentials to authenticate the URLRequest /// var credentials: Credentials? { get } @@ -26,7 +26,7 @@ protocol ApplicationPasswordRequestAuthenticator { /// Authenticates request /// -public struct DefaultApplicationPasswordRequestAuthenticator: ApplicationPasswordRequestAuthenticator { +public struct DefaultApplicationPasswordAuthenticator: ApplicationPasswordAuthenticator { /// Credentials to authenticate the URLRequest /// let credentials: Credentials? @@ -71,7 +71,7 @@ public struct DefaultApplicationPasswordRequestAuthenticator: ApplicationPasswor /// func generateApplicationPassword() async throws { guard let applicationPasswordUseCase else { - throw ApplicationPasswordRequestAuthenticatorError.applicationPasswordUseCaseNotAvailable + throw ApplicationPasswordAuthenticatorError.applicationPasswordUseCaseNotAvailable } let _ = try await applicationPasswordUseCase.generateNewPassword() return @@ -84,7 +84,7 @@ public struct DefaultApplicationPasswordRequestAuthenticator: ApplicationPasswor } } -private extension DefaultApplicationPasswordRequestAuthenticator { +private extension DefaultApplicationPasswordAuthenticator { /// To check whether the given URLRequest is a REST API request /// func isRestAPIRequest(_ urlRequest: URLRequest) -> Bool { @@ -110,7 +110,7 @@ private extension DefaultApplicationPasswordRequestAuthenticator { /// func authenticateUsingApplicationPasswordIfPossible(_ urlRequest: URLRequest) throws -> URLRequest { guard let applicationPassword = applicationPasswordUseCase?.applicationPassword else { - throw ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable + throw ApplicationPasswordAuthenticatorError.applicationPasswordNotAvailable } return AuthenticatedRESTRequest(applicationPassword: applicationPassword, request: urlRequest).asURLRequest() diff --git a/Networking/Networking/ApplicationPassword/ApplicationPasswordRequestProcessor.swift b/Networking/Networking/ApplicationPassword/ApplicationPasswordRequestProcessor.swift index 9029db226d0..df0175dec4d 100644 --- a/Networking/Networking/ApplicationPassword/ApplicationPasswordRequestProcessor.swift +++ b/Networking/Networking/ApplicationPassword/ApplicationPasswordRequestProcessor.swift @@ -8,9 +8,9 @@ final class ApplicationPasswordRequestProcessor { private var isAuthenticating = false - private let requestAuthenticator: ApplicationPasswordRequestAuthenticator + private let requestAuthenticator: ApplicationPasswordAuthenticator - init(requestAuthenticator: ApplicationPasswordRequestAuthenticator) { + init(requestAuthenticator: ApplicationPasswordAuthenticator) { self.requestAuthenticator = requestAuthenticator } } @@ -66,7 +66,7 @@ private extension ApplicationPasswordRequestProcessor { func shouldRetry(_ error: Error) -> Bool { // Need to generate application password - if .applicationPasswordNotAvailable == error as? ApplicationPasswordRequestAuthenticatorError { + if .applicationPasswordNotAvailable == error as? ApplicationPasswordAuthenticatorError { return true } diff --git a/Networking/Networking/Network/AlamofireNetwork.swift b/Networking/Networking/Network/AlamofireNetwork.swift index 121c9e10129..27dfdc15b6b 100644 --- a/Networking/Networking/Network/AlamofireNetwork.swift +++ b/Networking/Networking/Network/AlamofireNetwork.swift @@ -36,7 +36,7 @@ public class AlamofireNetwork: Network { /// public required init(credentials: Credentials?) { self.requestConverter = RequestConverter(credentials: credentials) - self.requestAuthenticator = ApplicationPasswordRequestProcessor(requestAuthenticator: DefaultApplicationPasswordRequestAuthenticator(credentials: credentials)) + self.requestAuthenticator = ApplicationPasswordRequestProcessor(requestAuthenticator: DefaultApplicationPasswordAuthenticator(credentials: credentials)) } /// Executes the specified Network Request. Upon completion, the payload will be sent back to the caller as a Data instance. diff --git a/Networking/NetworkingTests/ApplicationPassword/RequestProcessorTests.swift b/Networking/NetworkingTests/ApplicationPassword/RequestProcessorTests.swift index 7db65101c18..ed0c50efb8b 100644 --- a/Networking/NetworkingTests/ApplicationPassword/RequestProcessorTests.swift +++ b/Networking/NetworkingTests/ApplicationPassword/RequestProcessorTests.swift @@ -50,7 +50,7 @@ final class RequestProcessorTests: XCTestCase { // When request.retryCount = 0 let shouldRetry = waitFor { promise in - let error = ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable + let error = ApplicationPasswordAuthenticatorError.applicationPasswordNotAvailable self.sut.should(sessionManager, retry: request, with: error) { shouldRetry, timeDelay in promise(shouldRetry) } @@ -68,7 +68,7 @@ final class RequestProcessorTests: XCTestCase { // When request.retryCount = 1 let shouldRetry = waitFor { promise in - let error = ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable + let error = ApplicationPasswordAuthenticatorError.applicationPasswordNotAvailable self.sut.should(sessionManager, retry: request, with: error) { shouldRetry, timeDelay in promise(shouldRetry) } @@ -88,7 +88,7 @@ final class RequestProcessorTests: XCTestCase { // When mockRequestAuthenticator.mockedShouldRetryValue = true let shouldRetry = waitFor { promise in - let error = ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable + let error = ApplicationPasswordAuthenticatorError.applicationPasswordNotAvailable self.sut.should(sessionManager, retry: request, with: error) { shouldRetry, timeDelay in promise(shouldRetry) } @@ -106,7 +106,7 @@ final class RequestProcessorTests: XCTestCase { // When mockRequestAuthenticator.mockedShouldRetryValue = false let shouldRetry = waitFor { promise in - let error = ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable + let error = ApplicationPasswordAuthenticatorError.applicationPasswordNotAvailable self.sut.should(sessionManager, retry: request, with: error) { shouldRetry, timeDelay in promise(shouldRetry) } @@ -124,7 +124,7 @@ final class RequestProcessorTests: XCTestCase { let request = try mockRequest() // When - let error = ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable + let error = ApplicationPasswordAuthenticatorError.applicationPasswordNotAvailable let shouldRetry = waitFor { promise in self.sut.should(sessionManager, retry: request, with: error) { shouldRetry, timeDelay in promise(shouldRetry) @@ -177,7 +177,7 @@ final class RequestProcessorTests: XCTestCase { let request = try mockRequest() // When - let error = ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable + let error = ApplicationPasswordAuthenticatorError.applicationPasswordNotAvailable waitFor { promise in self.sut.should(sessionManager, retry: request, with: error) { shouldRetry, timeDelay in promise(()) @@ -196,7 +196,7 @@ final class RequestProcessorTests: XCTestCase { // When mockRequestAuthenticator.mockedShouldRetryValue = false waitFor { promise in - let error = ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable + let error = ApplicationPasswordAuthenticatorError.applicationPasswordNotAvailable self.sut.should(sessionManager, retry: request, with: error) { shouldRetry, timeDelay in promise(()) } @@ -226,7 +226,7 @@ private class MockTaskConvertible: TaskConvertible { } } -private class MockRequestAuthenticator: ApplicationPasswordRequestAuthenticator { +private class MockRequestAuthenticator: ApplicationPasswordAuthenticator { var mockedShouldRetryValue: Bool? private(set) var authenticateCalled = false diff --git a/Networking/NetworkingTests/Network/DefaultRequestAuthenticatorTests.swift b/Networking/NetworkingTests/Network/DefaultRequestAuthenticatorTests.swift index 904d3dcfc7a..e4b70994d6e 100644 --- a/Networking/NetworkingTests/Network/DefaultRequestAuthenticatorTests.swift +++ b/Networking/NetworkingTests/Network/DefaultRequestAuthenticatorTests.swift @@ -6,7 +6,7 @@ final class DefaultRequestAuthenticatorTests: XCTestCase { func test_authenticateRequest_returns_unauthenticated_request_for_non_REST_request_without_WPCOM_credentials() throws { // Given - let authenticator = DefaultApplicationPasswordRequestAuthenticator(credentials: nil) + let authenticator = DefaultApplicationPasswordAuthenticator(credentials: nil) let jetpackRequest = JetpackRequest(wooApiVersion: .mark1, method: .get, siteID: 123, path: "test", availableAsRESTRequest: false) // When @@ -20,7 +20,7 @@ final class DefaultRequestAuthenticatorTests: XCTestCase { func test_authenticatedRequest_returns_authenticated_request_for_non_REST_request_with_WPCOM_credentials() throws { // Given let credentials = Credentials(authToken: "secret") - let authenticator = DefaultApplicationPasswordRequestAuthenticator(credentials: credentials) + let authenticator = DefaultApplicationPasswordAuthenticator(credentials: credentials) let jetpackRequest = JetpackRequest(wooApiVersion: .mark1, method: .get, siteID: 123, path: "test", availableAsRESTRequest: false) // When @@ -38,7 +38,7 @@ final class DefaultRequestAuthenticatorTests: XCTestCase { let credentials: Credentials = .wporg(username: "admin", password: "supersecret", siteAddress: siteURL) let applicationPassword = ApplicationPassword(wpOrgUsername: credentials.username, password: .init(credentials.secret)) let useCase = MockApplicationPasswordUseCase(mockApplicationPassword: applicationPassword) - let authenticator = DefaultApplicationPasswordRequestAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) + let authenticator = DefaultApplicationPasswordAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) let wooAPIVersion = WooAPIVersion.mark1 let basePath = RESTRequest.Settings.basePath let restRequest = RESTRequest(siteURL: siteURL, wooApiVersion: wooAPIVersion, method: .get, path: "test") @@ -60,7 +60,7 @@ final class DefaultRequestAuthenticatorTests: XCTestCase { let credentials: Credentials = .wporg(username: "admin", password: "supersecret", siteAddress: siteURL) let applicationPassword = ApplicationPassword(wpOrgUsername: credentials.username, password: .init(credentials.secret)) let useCase = MockApplicationPasswordUseCase(mockGeneratedPassword: applicationPassword) - let authenticator = DefaultApplicationPasswordRequestAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) + let authenticator = DefaultApplicationPasswordAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) let wooAPIVersion = WooAPIVersion.mark1 let basePath = RESTRequest.Settings.basePath let restRequest = RESTRequest(siteURL: siteURL, wooApiVersion: wooAPIVersion, method: .get, path: "test") @@ -70,7 +70,7 @@ final class DefaultRequestAuthenticatorTests: XCTestCase { do { let _ = try authenticator.authenticate(request) - } catch ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable { + } catch ApplicationPasswordAuthenticatorError.applicationPasswordNotAvailable { try await authenticator.generateApplicationPassword() } @@ -88,7 +88,7 @@ final class DefaultRequestAuthenticatorTests: XCTestCase { let siteURL = "https://test.com/" let credentials: Credentials = .wporg(username: "admin", password: "supersecret", siteAddress: siteURL) let useCase = MockApplicationPasswordUseCase(mockGenerationError: NetworkError.timeout) - let authenticator = DefaultApplicationPasswordRequestAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) + let authenticator = DefaultApplicationPasswordAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) let wooAPIVersion = WooAPIVersion.mark1 let restRequest = RESTRequest(siteURL: siteURL, wooApiVersion: wooAPIVersion, method: .get, path: "test") @@ -98,7 +98,7 @@ final class DefaultRequestAuthenticatorTests: XCTestCase { let request = try restRequest.asURLRequest() do { let _ = try authenticator.authenticate(request) - } catch ApplicationPasswordRequestAuthenticatorError.applicationPasswordNotAvailable { + } catch ApplicationPasswordAuthenticatorError.applicationPasswordNotAvailable { // Then do { try await authenticator.generateApplicationPassword() @@ -115,7 +115,7 @@ final class DefaultRequestAuthenticatorTests: XCTestCase { let siteURL = "https://test.com/" let credentials: Credentials = .wporg(username: "admin", password: "supersecret", siteAddress: siteURL) let useCase = MockApplicationPasswordUseCase(mockGenerationError: NetworkError.timeout) - let authenticator = DefaultApplicationPasswordRequestAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) + let authenticator = DefaultApplicationPasswordAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) let wooAPIVersion = WooAPIVersion.mark1 let restRequest = RESTRequest(siteURL: siteURL, wooApiVersion: wooAPIVersion, method: .get, path: "test") @@ -131,7 +131,7 @@ final class DefaultRequestAuthenticatorTests: XCTestCase { let siteURL = "https://test.com/" let credentials: Credentials = .wporg(username: "admin", password: "supersecret", siteAddress: siteURL) let useCase = MockApplicationPasswordUseCase(mockGenerationError: NetworkError.timeout) - let authenticator = DefaultApplicationPasswordRequestAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) + let authenticator = DefaultApplicationPasswordAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) let jetpackRequest = JetpackRequest(wooApiVersion: .mark1, method: .get, siteID: 123, path: "test", availableAsRESTRequest: false) // When From c203662bd6ff154d3a710efbac1253d65aa37c23 Mon Sep 17 00:00:00 2001 From: Huong Do Date: Tue, 17 Jan 2023 11:42:21 +0700 Subject: [PATCH 10/17] Update tests for WordPressOrgCredentialsAuthenticator --- ...WordPressOrgCredentialsAuthenticatorTests.swift | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/WooCommerce/WooCommerceTests/Extensions/WordPressOrgCredentialsAuthenticatorTests.swift b/WooCommerce/WooCommerceTests/Extensions/WordPressOrgCredentialsAuthenticatorTests.swift index 2be6ebb5e0e..3a0bae62598 100644 --- a/WooCommerce/WooCommerceTests/Extensions/WordPressOrgCredentialsAuthenticatorTests.swift +++ b/WooCommerce/WooCommerceTests/Extensions/WordPressOrgCredentialsAuthenticatorTests.swift @@ -29,23 +29,15 @@ final class WordPressOrgCredentialsAuthenticatorTests: XCTestCase { assertEqual(credentials.adminURL, "https://test.com/wp-admin") } - func test_version_is_correct() { - // Given - let credentials = WordPressOrgCredentials(username: username, password: password, xmlrpc: xmlrpc, options: options) - - // Then - assertEqual(credentials.version, "5.3.1") - } - - func test_authenticator_is_created_correctly() { + func test_configuration_is_created_correctly() { // Given let credentials = WordPressOrgCredentials(username: username, password: password, xmlrpc: xmlrpc, options: options) // When - let authenticator = credentials.makeCookieNonceAuthenticator() + let configuration = credentials.makeCookieNonceAuthenticatorConfig() // Then - XCTAssertNotNil(authenticator) + XCTAssertNotNil(configuration) } } From af89ac01e43672cb7a9886f5b9a59da4910f58e1 Mon Sep 17 00:00:00 2001 From: Huong Do Date: Tue, 17 Jan 2023 11:56:05 +0700 Subject: [PATCH 11/17] Remove redundant self --- .../Networking/CookieNonce/CookieNonceAuthenticator.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift b/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift index 7d9de2eb8a0..19affaa9c08 100644 --- a/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift +++ b/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift @@ -76,9 +76,9 @@ private extension CookieNonceAuthenticator { throw CookieNonceAuthenticator.Error.missingNonce } self.nonce = nonce - self.successfulLoginSequence() + successfulLoginSequence() } catch let error as CookieNonceAuthenticator.Error { - self.invalidateLoginSequence(error: error) + invalidateLoginSequence(error: error) } catch { DDLogError("⛔️ Cookie nonce authenticator failed with uncaught error: \(error)") } From 3be18f8f92b43e5437635742cad8235cf27310c9 Mon Sep 17 00:00:00 2001 From: Huong Do Date: Wed, 18 Jan 2023 09:35:53 +0700 Subject: [PATCH 12/17] Add comment explaining CookieNonceAuthenticator --- .../Networking/CookieNonce/CookieNonceAuthenticator.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift b/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift index 19affaa9c08..9883fe21f40 100644 --- a/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift +++ b/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift @@ -1,6 +1,10 @@ import Alamofire import Foundation +/// An authenticator to handle cookie-nonce authentication. +/// This differs from WordPressKit's version by handling the nonce retrieval as a separate request +/// instead of a redirect from the login request - to fix issues with Pressable sites. +/// final class CookieNonceAuthenticator: RequestRetrier & RequestAdapter { private let username: String private let password: String From 7a7b28e8f15a5143808bb9a0eabb9a510c7877ec Mon Sep 17 00:00:00 2001 From: Huong Do Date: Wed, 18 Jan 2023 09:40:53 +0700 Subject: [PATCH 13/17] Remove misleading comment for canRetry --- .../Networking/CookieNonce/CookieNonceAuthenticator.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift b/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift index 9883fe21f40..eb44239307c 100644 --- a/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift +++ b/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift @@ -11,8 +11,7 @@ final class CookieNonceAuthenticator: RequestRetrier & RequestAdapter { private let loginURL: URL private let adminURL: URL private var nonce: String? - // If we can't get this to work once, don't retry for the same site - // It is likely that there is something preventing us from extracting a nonce + private var canRetry = true private var isAuthenticating = false private var requestsToRetry = [RequestRetryCompletion]() From 3b6b19207314530911e3438f95c055a9533a0538 Mon Sep 17 00:00:00 2001 From: Huong Do Date: Wed, 18 Jan 2023 09:41:43 +0700 Subject: [PATCH 14/17] Use modern guard let syntax --- .../Networking/CookieNonce/CookieNonceAuthenticator.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift b/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift index eb44239307c..37b42376959 100644 --- a/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift +++ b/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift @@ -26,7 +26,7 @@ final class CookieNonceAuthenticator: RequestRetrier & RequestAdapter { // MARK: Request Adapter func adapt(_ urlRequest: URLRequest) throws -> URLRequest { - guard let nonce = nonce else { + guard let nonce else { return urlRequest } var adaptedRequest = urlRequest From 67ec443605c24df0cbbc6f48f315265421df8bd1 Mon Sep 17 00:00:00 2001 From: Huong Do Date: Wed, 18 Jan 2023 09:46:43 +0700 Subject: [PATCH 15/17] Add comment regarding nonce retrieval method --- .../Networking/CookieNonce/CookieNonceAuthenticator.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift b/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift index 37b42376959..4bb5c958ef6 100644 --- a/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift +++ b/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift @@ -5,6 +5,9 @@ import Foundation /// This differs from WordPressKit's version by handling the nonce retrieval as a separate request /// instead of a redirect from the login request - to fix issues with Pressable sites. /// +/// This authenticator uses Ajax nonce retrieval method by default +/// since we are not supporting sites with WP versions earlier than 5.6.0. +/// final class CookieNonceAuthenticator: RequestRetrier & RequestAdapter { private let username: String private let password: String From 22c6ef07bbba2657cf09bc253cfc9379aada6c2c Mon Sep 17 00:00:00 2001 From: Huong Do Date: Wed, 18 Jan 2023 09:47:53 +0700 Subject: [PATCH 16/17] Simplify nonce check --- .../Networking/CookieNonce/CookieNonceAuthenticator.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift b/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift index 4bb5c958ef6..3cab1cc55d0 100644 --- a/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift +++ b/Networking/Networking/CookieNonce/CookieNonceAuthenticator.swift @@ -163,10 +163,7 @@ private extension CookieNonceAuthenticator { } func readNonceFromAjaxAction(html: String) -> String? { - guard !html.isEmpty else { - return nil - } - return html + html.isEmpty ? nil : html } func buildNonceRequestURL(base: URL) -> URL? { From d82041032b1c18c38f7488a93a44e214239495e5 Mon Sep 17 00:00:00 2001 From: Huong Do Date: Wed, 18 Jan 2023 09:49:22 +0700 Subject: [PATCH 17/17] Rename RequestProcessorTests with the ApplicationPassword prefix --- Networking/Networking.xcodeproj/project.pbxproj | 8 ++++---- ...ift => ApplicationPasswordRequestProcessorTests.swift} | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) rename Networking/NetworkingTests/ApplicationPassword/{RequestProcessorTests.swift => ApplicationPasswordRequestProcessorTests.swift} (98%) diff --git a/Networking/Networking.xcodeproj/project.pbxproj b/Networking/Networking.xcodeproj/project.pbxproj index c2c4466d51a..13fe130ab64 100644 --- a/Networking/Networking.xcodeproj/project.pbxproj +++ b/Networking/Networking.xcodeproj/project.pbxproj @@ -780,7 +780,7 @@ EE6FDCFC2966A70400E1CECF /* product-without-data.json in Resources */ = {isa = PBXBuildFile; fileRef = EE6FDCFB2966A70400E1CECF /* product-without-data.json */; }; EE71CC3D2951A8EA0074D908 /* ApplicationPasswordStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE71CC3C2951A8EA0074D908 /* ApplicationPasswordStorage.swift */; }; EE71CC412951CE700074D908 /* generate-application-password-using-wporg-creds-success.json in Resources */ = {isa = PBXBuildFile; fileRef = EE71CC402951CE700074D908 /* generate-application-password-using-wporg-creds-success.json */; }; - EE76762F2962B85E000066FA /* RequestProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE76762E2962B85E000066FA /* RequestProcessorTests.swift */; }; + EE76762F2962B85E000066FA /* ApplicationPasswordRequestProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE76762E2962B85E000066FA /* ApplicationPasswordRequestProcessorTests.swift */; }; EE80A24729547F8B003591E4 /* coupons-all-without-data.json in Resources */ = {isa = PBXBuildFile; fileRef = EE80A24529547F8B003591E4 /* coupons-all-without-data.json */; }; EE80A24829547F8B003591E4 /* coupon-without-data.json in Resources */ = {isa = PBXBuildFile; fileRef = EE80A24629547F8B003591E4 /* coupon-without-data.json */; }; EE80A25029556FBD003591E4 /* coupon-reports-without-data.json in Resources */ = {isa = PBXBuildFile; fileRef = EE80A24F29556FBD003591E4 /* coupon-reports-without-data.json */; }; @@ -1610,7 +1610,7 @@ EE6FDCFB2966A70400E1CECF /* product-without-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "product-without-data.json"; sourceTree = ""; }; EE71CC3C2951A8EA0074D908 /* ApplicationPasswordStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationPasswordStorage.swift; sourceTree = ""; }; EE71CC402951CE700074D908 /* generate-application-password-using-wporg-creds-success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "generate-application-password-using-wporg-creds-success.json"; sourceTree = ""; }; - EE76762E2962B85E000066FA /* RequestProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestProcessorTests.swift; sourceTree = ""; }; + EE76762E2962B85E000066FA /* ApplicationPasswordRequestProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationPasswordRequestProcessorTests.swift; sourceTree = ""; }; EE80A24529547F8B003591E4 /* coupons-all-without-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "coupons-all-without-data.json"; sourceTree = ""; }; EE80A24629547F8B003591E4 /* coupon-without-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "coupon-without-data.json"; sourceTree = ""; }; EE80A24F29556FBD003591E4 /* coupon-reports-without-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "coupon-reports-without-data.json"; sourceTree = ""; }; @@ -2841,7 +2841,7 @@ isa = PBXGroup; children = ( EE8DE431294B17CD005054E7 /* DefaultApplicationPasswordUseCaseTests.swift */, - EE76762E2962B85E000066FA /* RequestProcessorTests.swift */, + EE76762E2962B85E000066FA /* ApplicationPasswordRequestProcessorTests.swift */, ); path = ApplicationPassword; sourceTree = ""; @@ -3765,7 +3765,7 @@ D800DA0A25EFEB9C001E13CE /* WCPayRemoteTests.swift in Sources */, E13BAD5328F8625600217769 /* InAppPurchasesRemoteTests.swift in Sources */, CC851D1425E52AB500249E9C /* Decimal+ExtensionsTests.swift in Sources */, - EE76762F2962B85E000066FA /* RequestProcessorTests.swift in Sources */, + EE76762F2962B85E000066FA /* ApplicationPasswordRequestProcessorTests.swift in Sources */, B554FA8B2180B1D500C54DFF /* NotificationsRemoteTests.swift in Sources */, B518662A20A09C6F00037A38 /* OrdersRemoteTests.swift in Sources */, 02EF166E292F0C5800D90AD6 /* PaymentRemoteTests.swift in Sources */, diff --git a/Networking/NetworkingTests/ApplicationPassword/RequestProcessorTests.swift b/Networking/NetworkingTests/ApplicationPassword/ApplicationPasswordRequestProcessorTests.swift similarity index 98% rename from Networking/NetworkingTests/ApplicationPassword/RequestProcessorTests.swift rename to Networking/NetworkingTests/ApplicationPassword/ApplicationPasswordRequestProcessorTests.swift index ed0c50efb8b..4c0ff3bc6fb 100644 --- a/Networking/NetworkingTests/ApplicationPassword/RequestProcessorTests.swift +++ b/Networking/NetworkingTests/ApplicationPassword/ApplicationPasswordRequestProcessorTests.swift @@ -4,7 +4,7 @@ import XCTest /// RequestProcessor Unit Tests /// -final class RequestProcessorTests: XCTestCase { +final class ApplicationPasswordRequestProcessorTests: XCTestCase { private var mockRequestAuthenticator: MockRequestAuthenticator! private var sut: ApplicationPasswordRequestProcessor! private var sessionManager: Alamofire.SessionManager! @@ -209,7 +209,7 @@ final class RequestProcessorTests: XCTestCase { // MARK: Helpers // -private extension RequestProcessorTests { +private extension ApplicationPasswordRequestProcessorTests { func mockRequest() throws -> Alamofire.Request { let originalTask = MockTaskConvertible() let task = try originalTask.task(session: sessionManager.session, adapter: nil, queue: .main)