Skip to content

Commit 7a59c61

Browse files
authored
Tracking request authentication type for order list loading (#16112)
2 parents 801d7c9 + 5ff1e80 commit 7a59c61

File tree

7 files changed

+198
-1
lines changed

7 files changed

+198
-1
lines changed

Modules/Sources/NetworkingCore/Network/AlamofireNetwork.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ public struct JetpackSite: Equatable {
1515
}
1616
}
1717

18+
public enum RequestAuthenticationMode: String {
19+
case appPasswords = "app_passwords"
20+
case appPasswordsWithJetpack = "app_passwords_with_jetpack" // switching to app password for Jetpack sites
21+
case jetpackTunnel = "jetpack_tunnel"
22+
}
23+
1824
extension Alamofire.MultipartFormData: MultipartFormData {
1925
public func append(_ data: Data, withName name: String) {
2026
self.append(data, withName: name, fileName: nil, mimeType: nil)
@@ -24,6 +30,10 @@ extension Alamofire.MultipartFormData: MultipartFormData {
2430
/// AlamofireWrapper: Encapsulates all of the Alamofire OP's
2531
///
2632
public class AlamofireNetwork: Network {
33+
34+
/// authentication mode for requests
35+
public private(set) var authenticationMode: RequestAuthenticationMode?
36+
2737
/// Lazy-initialized session manager. Use ensuresSessionManagerIsInitialized=true to avoid race conditions with concurrent requests.
2838
private lazy var alamofireSession: Alamofire.Session = {
2939
let sessionConfiguration = URLSessionConfiguration.default
@@ -88,6 +98,18 @@ public class AlamofireNetwork: Network {
8898
} else if ensuresSessionManagerIsInitialized {
8999
self.alamofireSession = makeSession(configuration: URLSessionConfiguration.default)
90100
}
101+
102+
let authenticationMode: RequestAuthenticationMode? = {
103+
switch credentials {
104+
case .wporg, .applicationPassword:
105+
return .appPasswords
106+
case .wpcom:
107+
return .jetpackTunnel
108+
case .none:
109+
return nil
110+
}
111+
}()
112+
updateAuthenticationMode(authenticationMode)
91113
}
92114

93115
public func updateAppPasswordSwitching(enabled: Bool) {
@@ -98,6 +120,7 @@ public class AlamofireNetwork: Network {
98120
requestConverter = RequestConverter(siteAddress: nil)
99121
requestAuthenticator.updateAuthenticator(DefaultRequestAuthenticator(credentials: credentials))
100122
requestAuthenticator.delegate = nil
123+
updateAuthenticationMode(.jetpackTunnel)
101124
}
102125
}
103126

@@ -276,6 +299,7 @@ private extension AlamofireNetwork {
276299
requestConverter = RequestConverter(siteAddress: nil)
277300
requestAuthenticator.updateAuthenticator(DefaultRequestAuthenticator(credentials: credentials))
278301
requestAuthenticator.delegate = nil
302+
updateAuthenticationMode(.jetpackTunnel)
279303
return
280304
}
281305
requestConverter = RequestConverter(siteAddress: site.siteAddress)
@@ -286,8 +310,15 @@ private extension AlamofireNetwork {
286310
))
287311
requestAuthenticator.delegate = self
288312
errorHandler.resetFailureCount(for: site.siteID) // reset failure count
313+
updateAuthenticationMode(.appPasswordsWithJetpack)
289314
}
290315
}
316+
317+
func updateAuthenticationMode(_ mode: RequestAuthenticationMode?) {
318+
DispatchQueue.main.async { [weak self] in
319+
self?.authenticationMode = mode
320+
}
321+
}
291322
}
292323

293324
// MARK: Helper methods for error handling

Modules/Sources/Yosemite/Base/StoresManager.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Combine
22
import Foundation
3+
import enum NetworkingCore.RequestAuthenticationMode
34

45
/// Abstracts the Stores coordination
56
///
@@ -53,6 +54,9 @@ public protocol StoresManager {
5354
///
5455
var isAuthenticatedWithoutWPCom: Bool { get }
5556

57+
/// Authentication mode for network requests
58+
var requestAuthenticationMode: RequestAuthenticationMode? { get }
59+
5660
/// Publishes signal that indicates if the user is currently logged in with credentials.
5761
///
5862
var isLoggedInPublisher: AnyPublisher<Bool, Never> { get }

Modules/Sources/Yosemite/Model/Mocks/MockStoresManager.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Combine
22
import Storage
3+
import enum NetworkingCore.RequestAuthenticationMode
34

45
public class MockStoresManager: StoresManager {
56

@@ -223,6 +224,10 @@ public class MockStoresManager: StoresManager {
223224
false
224225
}
225226

227+
public var requestAuthenticationMode: RequestAuthenticationMode? {
228+
.jetpackTunnel
229+
}
230+
226231
public var needsDefaultStore: Bool {
227232
sessionManager.defaultStoreID == nil
228233
}

Modules/Tests/NetworkingTests/Network/AlamofireNetworkTests.swift

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,142 @@ final class AlamofireNetworkTests: XCTestCase {
668668
let networkError = result.1 as? NetworkError
669669
XCTAssertEqual(networkError?.errorCode, "failed")
670670
}
671+
672+
// MARK: - Authentication Mode Tests
673+
674+
func test_authenticationMode_is_appPasswords_for_wporg_credentials() {
675+
// Given
676+
let wporgCredentials = Credentials.wporg(username: "user", password: "pass", siteAddress: "https://example.com")
677+
678+
// When
679+
let network = AlamofireNetwork(credentials: wporgCredentials, sessionManager: createSessionWithMockURLProtocol())
680+
681+
// Then
682+
let expectation = XCTestExpectation(description: "Authentication mode should be set")
683+
DispatchQueue.main.async {
684+
XCTAssertEqual(network.authenticationMode, .appPasswords)
685+
expectation.fulfill()
686+
}
687+
wait(for: [expectation], timeout: 1.0)
688+
}
689+
690+
func test_authenticationMode_is_appPasswords_for_applicationPassword_credentials() {
691+
// Given
692+
let appPasswordCredentials = Credentials.applicationPassword(username: "user", password: "pass", siteAddress: "https://example.com")
693+
694+
// When
695+
let network = AlamofireNetwork(credentials: appPasswordCredentials, sessionManager: createSessionWithMockURLProtocol())
696+
697+
// Then
698+
let expectation = XCTestExpectation(description: "Authentication mode should be set")
699+
DispatchQueue.main.async {
700+
XCTAssertEqual(network.authenticationMode, .appPasswords)
701+
expectation.fulfill()
702+
}
703+
wait(for: [expectation], timeout: 1.0)
704+
}
705+
706+
func test_authenticationMode_is_jetpackTunnel_for_wpcom_credentials() {
707+
// Given
708+
let wpcomCredentials = createWPComCredentials()
709+
710+
// When
711+
let network = AlamofireNetwork(credentials: wpcomCredentials, sessionManager: createSessionWithMockURLProtocol())
712+
713+
// Then
714+
let expectation = XCTestExpectation(description: "Authentication mode should be set")
715+
DispatchQueue.main.async {
716+
XCTAssertEqual(network.authenticationMode, .jetpackTunnel)
717+
expectation.fulfill()
718+
}
719+
wait(for: [expectation], timeout: 1.0)
720+
}
721+
722+
func test_authenticationMode_is_nil_for_no_credentials() {
723+
// When
724+
let network = AlamofireNetwork(credentials: nil, sessionManager: createSessionWithMockURLProtocol())
725+
726+
// Then
727+
let expectation = XCTestExpectation(description: "Authentication mode should be set")
728+
DispatchQueue.main.async {
729+
XCTAssertNil(network.authenticationMode)
730+
expectation.fulfill()
731+
}
732+
wait(for: [expectation], timeout: 1.0)
733+
}
734+
735+
func test_authenticationMode_changes_to_appPasswordsWithJetpack_when_app_password_switching_enabled() {
736+
// Given
737+
let siteID: Int64 = 123
738+
let wpcomCredentials = createWPComCredentials()
739+
let network = createNetworkWithSelectedSite(siteID: siteID, credentials: wpcomCredentials, userDefaults: userDefaults)
740+
741+
// When - Enable app password switching
742+
network.updateAppPasswordSwitching(enabled: true)
743+
744+
// Then
745+
let expectation = XCTestExpectation(description: "Authentication mode should change")
746+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
747+
XCTAssertEqual(network.authenticationMode, .appPasswordsWithJetpack)
748+
expectation.fulfill()
749+
}
750+
wait(for: [expectation], timeout: 1.0)
751+
}
752+
753+
func test_authenticationMode_reverts_to_jetpackTunnel_when_app_password_switching_disabled() {
754+
// Given
755+
let siteID: Int64 = 456
756+
let wpcomCredentials = createWPComCredentials()
757+
let network = createNetworkWithSelectedSite(siteID: siteID, credentials: wpcomCredentials, userDefaults: userDefaults)
758+
759+
// When - Enable then disable app password switching
760+
network.updateAppPasswordSwitching(enabled: true)
761+
network.updateAppPasswordSwitching(enabled: false)
762+
763+
// Then
764+
let expectation = XCTestExpectation(description: "Authentication mode should revert")
765+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
766+
XCTAssertEqual(network.authenticationMode, .jetpackTunnel)
767+
expectation.fulfill()
768+
}
769+
wait(for: [expectation], timeout: 1.0)
770+
}
771+
772+
func test_authenticationMode_remains_jetpackTunnel_when_site_flagged_as_unsupported() {
773+
// Given
774+
let siteID: Int64 = 789
775+
let wpcomCredentials = createWPComCredentials()
776+
userDefaults.applicationPasswordUnsupportedList = [String(siteID): Date()]
777+
let network = createNetworkWithSelectedSite(siteID: siteID, credentials: wpcomCredentials, userDefaults: userDefaults)
778+
779+
// When - Enable app password switching for an unsupported site
780+
network.updateAppPasswordSwitching(enabled: true)
781+
782+
// Then
783+
let expectation = XCTestExpectation(description: "Authentication mode should remain jetpackTunnel")
784+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
785+
XCTAssertEqual(network.authenticationMode, .jetpackTunnel)
786+
expectation.fulfill()
787+
}
788+
wait(for: [expectation], timeout: 1.0)
789+
}
790+
791+
func test_authenticationMode_does_not_change_for_non_wpcom_credentials() {
792+
// Given
793+
let wporgCredentials = Credentials.wporg(username: "user", password: "pass", siteAddress: "https://example.com")
794+
let network = AlamofireNetwork(credentials: wporgCredentials, sessionManager: createSessionWithMockURLProtocol())
795+
796+
// When - Try to enable app password switching (should have no effect)
797+
network.updateAppPasswordSwitching(enabled: true)
798+
799+
// Then
800+
let expectation = XCTestExpectation(description: "Authentication mode should remain unchanged")
801+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
802+
XCTAssertEqual(network.authenticationMode, .appPasswords)
803+
expectation.fulfill()
804+
}
805+
wait(for: [expectation], timeout: 1.0)
806+
}
671807
}
672808

673809
private extension AlamofireNetworkTests {

WooCommerce/Classes/Analytics/WooAnalyticsEvent.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,19 @@ extension WooAnalyticsEvent {
147147
pageNumber: Int,
148148
filters: FilterOrderListViewModel.Filters?,
149149
totalCompletedOrders: Int?) -> WooAnalyticsEvent {
150+
let requestAuthMode: String = {
151+
guard let authState = ServiceLocator.stores.requestAuthenticationMode else {
152+
return "none"
153+
}
154+
return authState.rawValue
155+
}()
150156
let properties: [String: WooAnalyticsEventPropertyType?] = [
151157
"status": (filters?.orderStatus ?? []).map { $0.rawValue }.joined(separator: ","),
152158
"page_number": Int64(pageNumber),
153159
"total_duration": Double(totalDuration),
154160
"date_range": filters?.dateRange?.analyticsDescription ?? String(),
155-
"total_completed_orders": totalCompletedOrders
161+
"total_completed_orders": totalCompletedOrders,
162+
"request_type": requestAuthMode
156163
]
157164
return WooAnalyticsEvent(statName: .ordersListLoaded, properties: properties.compactMapValues { $0 })
158165
}

WooCommerce/Classes/Yosemite/AuthenticatedState.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@ import Yosemite
33
import Networking
44
import Storage
55
import Combine
6+
import enum NetworkingCore.RequestAuthenticationMode
67

78
// MARK: - AuthenticatedState
89
//
910
class AuthenticatedState: StoresManagerState {
1011

12+
var requestAuthenticationMode: RequestAuthenticationMode? {
13+
network.authenticationMode
14+
}
15+
1116
/// Dispatcher: Glues all of the Stores!
1217
///
1318
private let dispatcher = Dispatcher()

WooCommerce/Classes/Yosemite/DefaultStoresManager.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import KeychainAccess
88
import class WidgetKit.WidgetCenter
99
import Experiments
1010
import WordPressAuthenticator
11+
import enum NetworkingCore.RequestAuthenticationMode
1112

1213
// MARK: - DefaultStoresManager
1314
//
@@ -79,6 +80,14 @@ class DefaultStoresManager: StoresManager {
7980
return state is AuthenticatedState
8081
}
8182

83+
/// Authentication mode for network requests
84+
var requestAuthenticationMode: RequestAuthenticationMode? {
85+
guard let state = state as? AuthenticatedState else {
86+
return nil
87+
}
88+
return state.requestAuthenticationMode
89+
}
90+
8291
/// Indicates if the StoresManager is currently authenticated with site credentials only.
8392
///
8493
var isAuthenticatedWithoutWPCom: Bool {

0 commit comments

Comments
 (0)