Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions Networking/Networking.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -724,7 +724,7 @@
DEC51AFB2769C66B009F3DF4 /* SystemStatusMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC51AFA2769C66B009F3DF4 /* SystemStatusMapperTests.swift */; };
DEC51B02276AFB35009F3DF4 /* SystemStatus+DropinMustUsePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC51B01276AFB34009F3DF4 /* SystemStatus+DropinMustUsePlugin.swift */; };
DEFBA74E29485A7600C35BA9 /* RESTRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFBA74D29485A7600C35BA9 /* RESTRequest.swift */; };
DEFBA7542949CE6600C35BA9 /* RequestAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFBA7532949CE6600C35BA9 /* RequestAuthenticator.swift */; };
DEFBA7542949CE6600C35BA9 /* RequestProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFBA7532949CE6600C35BA9 /* RequestProcessor.swift */; };
DEFBA7562949D17400C35BA9 /* RequestAuthenticatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFBA7552949D17300C35BA9 /* RequestAuthenticatorTests.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 */; };
Expand All @@ -745,13 +745,18 @@
EE338A0E294AF9BD00183934 /* ApplicationPasswordMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE338A0D294AF9BD00183934 /* ApplicationPasswordMapperTests.swift */; };
EE54C89F2947782E00A9BF61 /* ApplicationPasswordUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE54C89E2947782E00A9BF61 /* ApplicationPasswordUseCase.swift */; };
EE54C8A729486B6800A9BF61 /* ApplicationPasswordMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE54C8A629486B6800A9BF61 /* ApplicationPasswordMapper.swift */; };
EE62EE61295ACF8D009C965B /* RequestConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE62EE60295ACF8D009C965B /* RequestConverterTests.swift */; };
EE62EE63295AD45E009C965B /* String+URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE62EE62295AD45E009C965B /* String+URL.swift */; };
EE62EE65295AD46D009C965B /* String+URLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE62EE64295AD46D009C965B /* String+URLTests.swift */; };
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 */; };
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 */; };
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 */; };
EE998150295AACE10074AE68 /* RequestConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE99814F295AACE10074AE68 /* RequestConverter.swift */; };
EECB7EE8286555180028C888 /* media-update-product-id.json in Resources */ = {isa = PBXBuildFile; fileRef = EECB7EE7286555180028C888 /* media-update-product-id.json */; };
FE28F6E226840DED004465C7 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE28F6E126840DED004465C7 /* User.swift */; };
FE28F6E426842848004465C7 /* UserMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE28F6E326842848004465C7 /* UserMapper.swift */; };
Expand Down Expand Up @@ -1507,7 +1512,7 @@
DEC51AFA2769C66B009F3DF4 /* SystemStatusMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemStatusMapperTests.swift; sourceTree = "<group>"; };
DEC51B01276AFB34009F3DF4 /* SystemStatus+DropinMustUsePlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SystemStatus+DropinMustUsePlugin.swift"; sourceTree = "<group>"; };
DEFBA74D29485A7600C35BA9 /* RESTRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTRequest.swift; sourceTree = "<group>"; };
DEFBA7532949CE6600C35BA9 /* RequestAuthenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestAuthenticator.swift; sourceTree = "<group>"; };
DEFBA7532949CE6600C35BA9 /* RequestProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestProcessor.swift; sourceTree = "<group>"; };
DEFBA7552949D17300C35BA9 /* RequestAuthenticatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestAuthenticatorTests.swift; sourceTree = "<group>"; };
E12552C426385B05001CEE70 /* ShippingLabelAddressValidationSuccess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShippingLabelAddressValidationSuccess.swift; sourceTree = "<group>"; };
E137619829151C7400FD098F /* error-wp-rest-forbidden.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "error-wp-rest-forbidden.json"; sourceTree = "<group>"; };
Expand All @@ -1528,13 +1533,18 @@
EE338A0D294AF9BD00183934 /* ApplicationPasswordMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationPasswordMapperTests.swift; sourceTree = "<group>"; };
EE54C89E2947782E00A9BF61 /* ApplicationPasswordUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationPasswordUseCase.swift; sourceTree = "<group>"; };
EE54C8A629486B6800A9BF61 /* ApplicationPasswordMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationPasswordMapper.swift; sourceTree = "<group>"; };
EE62EE60295ACF8D009C965B /* RequestConverterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestConverterTests.swift; sourceTree = "<group>"; };
EE62EE62295AD45E009C965B /* String+URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+URL.swift"; sourceTree = "<group>"; };
EE62EE64295AD46D009C965B /* String+URLTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+URLTests.swift"; sourceTree = "<group>"; };
EE71CC3C2951A8EA0074D908 /* ApplicationPasswordStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationPasswordStorage.swift; sourceTree = "<group>"; };
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 = "<group>"; };
EE80A24529547F8B003591E4 /* coupons-all-without-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "coupons-all-without-data.json"; sourceTree = "<group>"; };
EE80A24629547F8B003591E4 /* coupon-without-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "coupon-without-data.json"; sourceTree = "<group>"; };
EE80A24F29556FBD003591E4 /* coupon-reports-without-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "coupon-reports-without-data.json"; sourceTree = "<group>"; };
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 = "<group>"; };
EE8DE431294B17CD005054E7 /* DefaultApplicationPasswordUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultApplicationPasswordUseCaseTests.swift; sourceTree = "<group>"; };
EE99814D295AA7430074AE68 /* RequestAuthenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestAuthenticator.swift; sourceTree = "<group>"; };
EE99814F295AACE10074AE68 /* RequestConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestConverter.swift; sourceTree = "<group>"; };
EECB7EE7286555180028C888 /* media-update-product-id.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "media-update-product-id.json"; sourceTree = "<group>"; };
F3F25DC15EC1D7C631169CB5 /* Pods_Networking.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Networking.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F6CEE1CA2AD376C0C28AE9F6 /* Pods-NetworkingTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NetworkingTests.release.xcconfig"; path = "../Pods/Target Support Files/Pods-NetworkingTests/Pods-NetworkingTests.release.xcconfig"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1746,7 +1756,6 @@
B518662320A099BF00037A38 /* AlamofireNetwork.swift */,
B518662620A09BCC00037A38 /* MockNetwork.swift */,
D87F6150226591E10031A13B /* NullNetwork.swift */,
DEFBA7532949CE6600C35BA9 /* RequestAuthenticator.swift */,
);
path = Network;
sourceTree = "<group>";
Expand Down Expand Up @@ -2438,6 +2447,7 @@
children = (
B57B1E6621C916850046E764 /* NetworkErrorTests.swift */,
DEFBA7552949D17300C35BA9 /* RequestAuthenticatorTests.swift */,
EE62EE60295ACF8D009C965B /* RequestConverterTests.swift */,
);
path = Network;
sourceTree = "<group>";
Expand Down Expand Up @@ -2474,6 +2484,7 @@
57E8FED2246616AC0057CD68 /* Result+Extensions.swift */,
265EFBDB285257950033BD33 /* Order+Fallbacks.swift */,
DE2E8EB0295464C5002E4B14 /* URLRequest+Request.swift */,
EE62EE62295AD45E009C965B /* String+URL.swift */,
);
path = Extensions;
sourceTree = "<group>";
Expand All @@ -2487,6 +2498,7 @@
02BDB83623EA9C4D00BCC63E /* String+HTMLTests.swift */,
0212683424C046CB00F8A892 /* MockNetwork+Path.swift */,
CC851D1325E52AB500249E9C /* Decimal+ExtensionsTests.swift */,
EE62EE64295AD46D009C965B /* String+URLTests.swift */,
);
path = Extensions;
sourceTree = "<group>";
Expand Down Expand Up @@ -2646,8 +2658,11 @@
EE54C899294777D000A9BF61 /* ApplicationPassword */ = {
isa = PBXGroup;
children = (
DEFBA7532949CE6600C35BA9 /* RequestProcessor.swift */,
EE54C89E2947782E00A9BF61 /* ApplicationPasswordUseCase.swift */,
EE71CC3C2951A8EA0074D908 /* ApplicationPasswordStorage.swift */,
EE99814D295AA7430074AE68 /* RequestAuthenticator.swift */,
EE99814F295AACE10074AE68 /* RequestConverter.swift */,
);
path = ApplicationPassword;
sourceTree = "<group>";
Expand Down Expand Up @@ -3160,6 +3175,7 @@
457A574025D1817E000797AD /* ShippingLabelAddressVerification.swift in Sources */,
74ABA1D1213F22CA00FFAD30 /* TopEarnersStatsRemote.swift in Sources */,
DEC51AF127699E7A009F3DF4 /* SystemStatus+Page.swift in Sources */,
EE99814E295AA7430074AE68 /* RequestAuthenticator.swift in Sources */,
025CA2C0238EB8CB00B05C81 /* ProductShippingClass.swift in Sources */,
02C1CEF424C6A02B00703EBA /* ProductVariationMapper.swift in Sources */,
3105470C262E27F000C5C02B /* WCPayPaymentIntentStatusEnum.swift in Sources */,
Expand Down Expand Up @@ -3190,7 +3206,7 @@
020D07B823D852BB00FD9580 /* Media.swift in Sources */,
B5BB1D0C20A2050300112D92 /* DateFormatter+Woo.swift in Sources */,
743E84EE2217244C00FAC9D7 /* ShipmentTrackingListMapper.swift in Sources */,
DEFBA7542949CE6600C35BA9 /* RequestAuthenticator.swift in Sources */,
DEFBA7542949CE6600C35BA9 /* RequestProcessor.swift in Sources */,
451A97E5260B631E0059D135 /* ShippingLabelPredefinedPackage.swift in Sources */,
BAB373722795A1FB00837B4A /* OrderTaxLine.swift in Sources */,
EE54C89F2947782E00A9BF61 /* ApplicationPasswordUseCase.swift in Sources */,
Expand All @@ -3214,6 +3230,7 @@
7452387221124B7700A973CD /* AnyEncodable.swift in Sources */,
740CF89921937A030023ED3A /* CommentRemote.swift in Sources */,
31054702262E04F700C5C02B /* RemotePaymentIntentMapper.swift in Sources */,
EE62EE63295AD45E009C965B /* String+URL.swift in Sources */,
025CA2C2238EBBAA00B05C81 /* ProductShippingClassListMapper.swift in Sources */,
74ABA1CD213F1B6B00FFAD30 /* TopEarnerStats.swift in Sources */,
CCAAD10F2683974000909664 /* ShippingLabelPackagePurchase.swift in Sources */,
Expand Down Expand Up @@ -3364,6 +3381,7 @@
CE583A0E2109154500D73C1C /* OrderNoteMapper.swift in Sources */,
D8FBFF0D22D3AF4A006E3336 /* StatsGranularityV4.swift in Sources */,
261870782540A252006522A1 /* ShippingLineTax.swift in Sources */,
EE998150295AACE10074AE68 /* RequestConverter.swift in Sources */,
74046E1B217A684D007DD7BF /* SiteSettingsRemote.swift in Sources */,
0359EA1D27AADE000048DE2D /* WCPayChargeMapper.swift in Sources */,
B5C6FCCF20A3592900A4F8E4 /* OrderItem.swift in Sources */,
Expand Down Expand Up @@ -3505,6 +3523,8 @@
45150AA026837357006922EA /* CountryListMapperTests.swift in Sources */,
74D5BECE217E0F98007B0348 /* SiteSettingsRemoteTests.swift in Sources */,
D8FBFF1C22D51C34006E3336 /* OrderStatsRemoteV4Tests.swift in Sources */,
EE62EE61295ACF8D009C965B /* RequestConverterTests.swift in Sources */,
EE62EE65295AD46D009C965B /* String+URLTests.swift in Sources */,
CE6D666F2379E82A007835A1 /* ArrayWooTests.swift in Sources */,
DE2E8EAD295418D8002E4B14 /* WordPressSiteRemoteTests.swift in Sources */,
45D685FC23D0C739005F87D0 /* ProductSkuMapperTests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
enum RequestAuthenticatorError: Error {
case applicationPasswordUseCaseNotAvailable
case applicationPasswordNotAvailable
}

/// Authenticates request
///
public struct RequestAuthenticator {
/// Credentials.
///
let credentials: Credentials?

/// The use case to handle authentication with application passwords.
///
private let applicationPasswordUseCase: ApplicationPasswordUseCase?

/// Sets up the authenticator with optional credentials and application password use case.
/// `applicationPasswordUseCase` can be injected for unit tests.
///
init(credentials: Credentials?, applicationPasswordUseCase: ApplicationPasswordUseCase? = nil) {
self.credentials = credentials
let useCase: ApplicationPasswordUseCase? = {
if let applicationPasswordUseCase {
return applicationPasswordUseCase
} else if case let .wporg(username, password, siteAddress) = credentials {
return try? DefaultApplicationPasswordUseCase(username: username,
password: password,
siteAddress: siteAddress)
} else {
return nil
}
}()
self.applicationPasswordUseCase = useCase
}

func authenticate(_ urlRequest: URLRequest) throws -> URLRequest {
guard case let .wporg(_, _, siteAddress) = credentials,
let url = urlRequest.url,
url.absoluteString.hasPrefix(siteAddress.trimSlashes() + "/" + RESTRequest.Settings.basePath) else {
// Handle non-REST requests as before
return try authenticateUsingWPCOMTokenIfPossible(urlRequest)
}

return try authenticateUsingApplicationPasswordIfPossible(urlRequest)
}

func generateApplicationPassword() async throws {
guard let applicationPasswordUseCase = applicationPasswordUseCase else {
throw RequestAuthenticatorError.applicationPasswordUseCaseNotAvailable
}
let _ = try await applicationPasswordUseCase.generateNewPassword()
return
}
}

private extension RequestAuthenticator {
/// Attempts creating a request with WPCOM token if possible.
///
func authenticateUsingWPCOMTokenIfPossible(_ urlRequest: URLRequest) throws -> URLRequest {
if let credentials, case .wpcom = credentials {
return try AuthenticatedRequest(credentials: credentials, request: urlRequest).asURLRequest()
}
return UnauthenticatedRequest(request: urlRequest).asURLRequest()
}

/// Attempts creating a request with application password if possible.
///
func authenticateUsingApplicationPasswordIfPossible(_ urlRequest: URLRequest) throws -> URLRequest {
guard let applicationPassword = applicationPasswordUseCase?.applicationPassword else {
throw RequestAuthenticatorError.applicationPasswordNotAvailable
}

var request = urlRequest
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue(UserAgent.defaultUserAgent, forHTTPHeaderField: "User-Agent")

let username = applicationPassword.wpOrgUsername
let password = applicationPassword.password.secretValue
let loginString = "\(username):\(password)"
guard let loginData = loginString.data(using: .utf8) else {
return request
}
let base64LoginString = loginData.base64EncodedString()
request.setValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")

// Cookies from `CookieNonceAuthenticator` should be skipped
request.httpShouldHandleCookies = false

return request
}
}
17 changes: 17 additions & 0 deletions Networking/Networking/ApplicationPassword/RequestConverter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Alamofire

/// Converter to convert Jetpack tunnel requests into REST API requests if needed
///
struct RequestConverter {
let credentials: Credentials?

func convert(_ request: URLRequestConvertible) -> URLRequestConvertible {
guard let jetpackRequest = request as? JetpackRequest,
case let .wporg(_, _, siteAddress) = credentials,
let restRequest = jetpackRequest.asRESTRequest(with: siteAddress) else {
return request
}

return restRequest
}
}
Loading