Skip to content

Commit feb9031

Browse files
committed
Merge branch 'trunk' into issue/8523-release-bulk-editing
2 parents 8c20c17 + 18ab5c9 commit feb9031

28 files changed

+437
-155
lines changed

Networking/Networking.xcodeproj/project.pbxproj

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,11 @@
775775
EE338A0E294AF9BD00183934 /* ApplicationPasswordMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE338A0D294AF9BD00183934 /* ApplicationPasswordMapperTests.swift */; };
776776
EE54C89F2947782E00A9BF61 /* ApplicationPasswordUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE54C89E2947782E00A9BF61 /* ApplicationPasswordUseCase.swift */; };
777777
EE54C8A729486B6800A9BF61 /* ApplicationPasswordMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE54C8A629486B6800A9BF61 /* ApplicationPasswordMapper.swift */; };
778+
EE57C10E2979277300BC31E7 /* ApplicationPasswordNameAndUUID.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE57C10D2979277300BC31E7 /* ApplicationPasswordNameAndUUID.swift */; };
779+
EE57C111297927C600BC31E7 /* ApplicationPasswordNameAndUUIDMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE57C110297927C600BC31E7 /* ApplicationPasswordNameAndUUIDMapper.swift */; };
780+
EE57C1152979371B00BC31E7 /* ApplicationPassword.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE57C1142979371B00BC31E7 /* ApplicationPassword.swift */; };
781+
EE57C11729794BD500BC31E7 /* ApplicationPasswordNameAndUUIDMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE57C11629794BD500BC31E7 /* ApplicationPasswordNameAndUUIDMapperTests.swift */; };
782+
EE57C11929794DCA00BC31E7 /* get-application-passwords-success.json in Resources */ = {isa = PBXBuildFile; fileRef = EE57C11829794DCA00BC31E7 /* get-application-passwords-success.json */; };
778783
EE62EE61295ACF8D009C965B /* RequestConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE62EE60295ACF8D009C965B /* RequestConverterTests.swift */; };
779784
EE62EE63295AD45E009C965B /* String+URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE62EE62295AD45E009C965B /* String+URL.swift */; };
780785
EE62EE65295AD46D009C965B /* String+URLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE62EE64295AD46D009C965B /* String+URLTests.swift */; };
@@ -1608,6 +1613,11 @@
16081613
EE338A0D294AF9BD00183934 /* ApplicationPasswordMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationPasswordMapperTests.swift; sourceTree = "<group>"; };
16091614
EE54C89E2947782E00A9BF61 /* ApplicationPasswordUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationPasswordUseCase.swift; sourceTree = "<group>"; };
16101615
EE54C8A629486B6800A9BF61 /* ApplicationPasswordMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationPasswordMapper.swift; sourceTree = "<group>"; };
1616+
EE57C10D2979277300BC31E7 /* ApplicationPasswordNameAndUUID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationPasswordNameAndUUID.swift; sourceTree = "<group>"; };
1617+
EE57C110297927C600BC31E7 /* ApplicationPasswordNameAndUUIDMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationPasswordNameAndUUIDMapper.swift; sourceTree = "<group>"; };
1618+
EE57C1142979371B00BC31E7 /* ApplicationPassword.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationPassword.swift; sourceTree = "<group>"; };
1619+
EE57C11629794BD500BC31E7 /* ApplicationPasswordNameAndUUIDMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationPasswordNameAndUUIDMapperTests.swift; sourceTree = "<group>"; };
1620+
EE57C11829794DCA00BC31E7 /* get-application-passwords-success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "get-application-passwords-success.json"; sourceTree = "<group>"; };
16111621
EE62EE60295ACF8D009C965B /* RequestConverterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestConverterTests.swift; sourceTree = "<group>"; };
16121622
EE62EE62295AD45E009C965B /* String+URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+URL.swift"; sourceTree = "<group>"; };
16131623
EE62EE64295AD46D009C965B /* String+URLTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+URLTests.swift"; sourceTree = "<group>"; };
@@ -2085,6 +2095,7 @@
20852095
B557DA1B20979E6D005962F4 /* Model */ = {
20862096
isa = PBXGroup;
20872097
children = (
2098+
EE57C10A297926E800BC31E7 /* ApplicationPassword */,
20882099
5726F72F2460A83D0031CAAC /* Copiable */,
20892100
B50A583A21AD872E00617455 /* Devices */,
20902101
020D07B623D852AB00FD9580 /* Media */,
@@ -2470,6 +2481,7 @@
24702481
B567AF2720A0FA0A00AB6C62 /* Mapper */ = {
24712482
isa = PBXGroup;
24722483
children = (
2484+
EE57C10F297927A000BC31E7 /* ApplicationPassword */,
24732485
B567AF2820A0FA1E00AB6C62 /* Mapper.swift */,
24742486
B505F6CC20BEE37E00BB1B69 /* AccountMapper.swift */,
24752487
93D8BBFC226BBEE800AD2EB3 /* AccountSettingsMapper.swift */,
@@ -2566,7 +2578,6 @@
25662578
DE34051828BDEE6A00CF0D97 /* JetpackConnectionURLMapper.swift */,
25672579
68CB800D28D8901B00E169F8 /* CustomerMapper.swift */,
25682580
68F48B0C28E3B2E80045C15B /* WCAnalyticsCustomerMapper.swift */,
2569-
EE54C8A629486B6800A9BF61 /* ApplicationPasswordMapper.swift */,
25702581
DE2E8E9E295310C5002E4B14 /* WordPressSiteMapper.swift */,
25712582
);
25722583
path = Mapper;
@@ -2707,6 +2718,7 @@
27072718
DE42F9622967C8B900D514C2 /* ReportOrderTotalsMapperTests.swift */,
27082719
EEA658412966C41A00112DF0 /* ProductIDMapperTests.swift */,
27092720
EEA658472966CBAD00112DF0 /* EntityIDMapperTests.swift */,
2721+
EE57C11629794BD500BC31E7 /* ApplicationPasswordNameAndUUIDMapperTests.swift */,
27102722
);
27112723
path = Mapper;
27122724
sourceTree = "<group>";
@@ -2792,6 +2804,7 @@
27922804
EE338A0A294AF92A00183934 /* AppliicationPassword */ = {
27932805
isa = PBXGroup;
27942806
children = (
2807+
EE57C11829794DCA00BC31E7 /* get-application-passwords-success.json */,
27952808
EE71CC402951CE700074D908 /* generate-application-password-using-wporg-creds-success.json */,
27962809
);
27972810
path = AppliicationPassword;
@@ -2809,6 +2822,24 @@
28092822
path = ApplicationPassword;
28102823
sourceTree = "<group>";
28112824
};
2825+
EE57C10A297926E800BC31E7 /* ApplicationPassword */ = {
2826+
isa = PBXGroup;
2827+
children = (
2828+
EE57C10D2979277300BC31E7 /* ApplicationPasswordNameAndUUID.swift */,
2829+
EE57C1142979371B00BC31E7 /* ApplicationPassword.swift */,
2830+
);
2831+
path = ApplicationPassword;
2832+
sourceTree = "<group>";
2833+
};
2834+
EE57C10F297927A000BC31E7 /* ApplicationPassword */ = {
2835+
isa = PBXGroup;
2836+
children = (
2837+
EE54C8A629486B6800A9BF61 /* ApplicationPasswordMapper.swift */,
2838+
EE57C110297927C600BC31E7 /* ApplicationPasswordNameAndUUIDMapper.swift */,
2839+
);
2840+
path = ApplicationPassword;
2841+
sourceTree = "<group>";
2842+
};
28122843
EE80A24B29556F1D003591E4 /* Coupon */ = {
28132844
isa = PBXGroup;
28142845
children = (
@@ -3251,6 +3282,7 @@
32513282
020D07C223D858BB00FD9580 /* media-upload.json in Resources */,
32523283
31A451D127863A2E00FE81AA /* stripe-account-rejected-other.json in Resources */,
32533284
DEF13C5029629EEA0024A02B /* user-complete-without-data.json in Resources */,
3285+
EE57C11929794DCA00BC31E7 /* get-application-passwords-success.json in Resources */,
32543286
74ABA1CA213F19FE00FFAD30 /* top-performers-year.json in Resources */,
32553287
DEF13C5C2965812D0024A02B /* order-stats-v4-year-without-data.json in Resources */,
32563288
31A451CF27863A2E00FE81AA /* stripe-account-rejected-terms-of-service.json in Resources */,
@@ -3359,6 +3391,7 @@
33593391
311D41302783C0E200052F64 /* StripeRemote.swift in Sources */,
33603392
267313312559CC930026F7EF /* PaymentGateway.swift in Sources */,
33613393
B58E5BEA20FFB3D0003C986E /* CodingUserInfoKey+Woo.swift in Sources */,
3394+
EE57C10E2979277300BC31E7 /* ApplicationPasswordNameAndUUID.swift in Sources */,
33623395
B59325D3217E4206000B0E8E /* NoteMedia.swift in Sources */,
33633396
26731337255ACA850026F7EF /* PaymentGatewayListMapper.swift in Sources */,
33643397
09EA564B27C75FCE00407D40 /* ProductVariationsBulkUpdateMapper.swift in Sources */,
@@ -3451,13 +3484,15 @@
34513484
45A4B84E25D2E11300776FB4 /* ShippingLabelAddressValidationSuccessMapper.swift in Sources */,
34523485
CC851D0625E51ADF00249E9C /* Decimal+Extensions.swift in Sources */,
34533486
45D1CF4923BACA6500945A36 /* ProductTaxStatus.swift in Sources */,
3487+
EE57C111297927C600BC31E7 /* ApplicationPasswordNameAndUUIDMapper.swift in Sources */,
34543488
D823D91222377DF300C90817 /* ShipmentTrackingProviderListMapper.swift in Sources */,
34553489
B5C151BB217EC34100C7BDC1 /* KeyedDecodingContainer+Woo.swift in Sources */,
34563490
933A2732222234F800C2143A /* Logging.swift in Sources */,
34573491
CE50346021B5799F007573C6 /* SitePlan.swift in Sources */,
34583492
DE2E8E9F295310C5002E4B14 /* WordPressSiteMapper.swift in Sources */,
34593493
B59325CC217E2B4C000B0E8E /* NoteListMapper.swift in Sources */,
34603494
B505F6CD20BEE37E00BB1B69 /* AccountMapper.swift in Sources */,
3495+
EE57C1152979371B00BC31E7 /* ApplicationPassword.swift in Sources */,
34613496
B554FA8F2180BC7000C54DFF /* NoteHashListMapper.swift in Sources */,
34623497
B556FD69211CE2EC00B5DAE7 /* NetworkError.swift in Sources */,
34633498
45B204B82489095100FE6526 /* ProductCategoryMapper.swift in Sources */,
@@ -3821,6 +3856,7 @@
38213856
03DCB76C262591C400C8953D /* CouponsRemoteTests.swift in Sources */,
38223857
31A451BD2786344B00FE81AA /* StripeRemoteTests.swift in Sources */,
38233858
743E84F822172E1F00FAC9D7 /* ShipmentTrackingListMapperTests.swift in Sources */,
3859+
EE57C11729794BD500BC31E7 /* ApplicationPasswordNameAndUUIDMapperTests.swift in Sources */,
38243860
45ED4F14239E8F2E004F1BE3 /* TaxClassRemoteTests.swift in Sources */,
38253861
D88D5A4D230BD010007B6E01 /* ProductReviewsRemoteTests.swift in Sources */,
38263862
261CF1BC255AEE290090D8D3 /* PaymentsGatewayRemoteTests.swift in Sources */,

Networking/Networking/ApplicationPassword/ApplicationPasswordStorage.swift

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ struct ApplicationPasswordStorage {
1515
///
1616
var applicationPassword: ApplicationPassword? {
1717
guard let password = keychain.password,
18-
let username = keychain.username else {
18+
let username = keychain.username,
19+
let uuid = keychain.uuid else {
1920
return nil
2021
}
21-
return ApplicationPassword(wpOrgUsername: username, password: Secret(password))
22+
return ApplicationPassword(wpOrgUsername: username, password: Secret(password), uuid: uuid)
2223
}
2324

2425
/// Saves application password into keychain
@@ -28,6 +29,7 @@ struct ApplicationPasswordStorage {
2829
func saveApplicationPassword(_ password: ApplicationPassword) {
2930
keychain.username = password.wpOrgUsername
3031
keychain.password = password.password.secretValue
32+
keychain.uuid = password.uuid
3133
}
3234

3335
/// Removes the currently saved password from storage
@@ -36,6 +38,7 @@ struct ApplicationPasswordStorage {
3638
// Delete password from keychain
3739
keychain.username = nil
3840
keychain.password = nil
41+
keychain.uuid = nil
3942
}
4043
}
4144

@@ -44,6 +47,7 @@ struct ApplicationPasswordStorage {
4447
private extension Keychain {
4548
private static let keychainApplicationPassword = "ApplicationPassword"
4649
private static let keychainApplicationPasswordUsername = "ApplicationPasswordUsername"
50+
private static let keychainApplicationPasswordUUID = "ApplicationPasswordUUID"
4751

4852
var password: String? {
4953
get { self[Keychain.keychainApplicationPassword] }
@@ -54,4 +58,9 @@ private extension Keychain {
5458
get { self[Keychain.keychainApplicationPasswordUsername] }
5559
set { self[Keychain.keychainApplicationPasswordUsername] = newValue }
5660
}
61+
62+
var uuid: String? {
63+
get { self[Keychain.keychainApplicationPasswordUUID] }
64+
set { self[Keychain.keychainApplicationPasswordUUID] = newValue }
65+
}
5766
}

Networking/Networking/ApplicationPassword/ApplicationPasswordUseCase.swift

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,7 @@ public enum ApplicationPasswordUseCaseError: Error {
99
case applicationPasswordsDisabled
1010
case failedToConstructLoginOrAdminURLUsingSiteAddress
1111
case unauthorizedRequest
12-
}
13-
14-
public struct ApplicationPassword {
15-
/// WordPress org username that the application password belongs to
16-
///
17-
let wpOrgUsername: String
18-
19-
/// Application password
20-
///
21-
let password: Secret<String>
12+
case unableToFindPasswordUUID
2213
}
2314

2415
public protocol ApplicationPasswordUseCase {
@@ -105,16 +96,20 @@ final public class DefaultApplicationPasswordUseCase: ApplicationPasswordUseCase
10596
/// - Returns: Generated `ApplicationPassword` instance
10697
///
10798
public func generateNewPassword() async throws -> ApplicationPassword {
108-
async let password = try {
99+
let applicationPassword = try await {
109100
do {
110101
return try await createApplicationPassword()
111102
} catch ApplicationPasswordUseCaseError.duplicateName {
112-
try await deletePassword()
103+
do {
104+
try await deletePassword()
105+
} catch ApplicationPasswordUseCaseError.unableToFindPasswordUUID {
106+
// No password found with the `applicationPasswordName`
107+
// We can proceed to the creation step
108+
}
113109
return try await createApplicationPassword()
114110
}
115111
}()
116112

117-
let applicationPassword = try await ApplicationPassword(wpOrgUsername: username, password: Secret(password))
118113
storage.saveApplicationPassword(applicationPassword)
119114
return applicationPassword
120115
}
@@ -124,26 +119,40 @@ final public class DefaultApplicationPasswordUseCase: ApplicationPasswordUseCase
124119
/// Deletes locally and also sends an API request to delete it from the site
125120
///
126121
public func deletePassword() async throws {
127-
try await deleteApplicationPassword()
122+
// Get the uuid before removing the password from storage
123+
let uuidFromLocalPassword = applicationPassword?.uuid
124+
125+
// Remove password from storage
126+
storage.removeApplicationPassword()
127+
128+
let uuidToBeDeleted = try await {
129+
if let uuidFromLocalPassword {
130+
return uuidFromLocalPassword
131+
} else {
132+
return try await self.fetchUUIDForApplicationPassword(await applicationPasswordName)
133+
}
134+
}()
135+
try await deleteApplicationPassword(uuidToBeDeleted)
128136
}
129137
}
130138

131139
private extension DefaultApplicationPasswordUseCase {
132140
/// Creates application password using WordPress.com authentication token
133141
///
134-
/// - Returns: Application password as `String`
142+
/// - Returns: Generated `ApplicationPassword`
135143
///
136-
func createApplicationPassword() async throws -> String {
144+
func createApplicationPassword() async throws -> ApplicationPassword {
137145
let passwordName = await applicationPasswordName
138146

139147
let parameters = [ParameterKey.name: passwordName]
140148
let request = RESTRequest(siteURL: siteAddress, method: .post, path: Path.applicationPasswords, parameters: parameters)
141149
return try await withCheckedThrowingContinuation { continuation in
142-
network.responseData(for: request) { result in
150+
network.responseData(for: request) { [weak self] result in
151+
guard let self else { return }
143152
switch result {
144153
case .success(let data):
145154
do {
146-
let mapper = ApplicationPasswordMapper()
155+
let mapper = ApplicationPasswordMapper(wpOrgUsername: self.username)
147156
let password = try mapper.map(response: data)
148157
continuation.resume(returning: password)
149158
} catch {
@@ -172,15 +181,37 @@ private extension DefaultApplicationPasswordUseCase {
172181
}
173182
}
174183

175-
/// Deletes application password using WordPress.com authentication token
184+
/// Get the UUID of the application password
176185
///
177-
func deleteApplicationPassword() async throws {
178-
// Remove password from storage
179-
storage.removeApplicationPassword()
186+
func fetchUUIDForApplicationPassword(_ passwordName: String) async throws -> String {
187+
let request = RESTRequest(siteURL: siteAddress, method: .get, path: Path.applicationPasswords)
180188

181-
let passwordName = await applicationPasswordName
182-
let parameters = [ParameterKey.name: passwordName]
183-
let request = RESTRequest(siteURL: siteAddress, method: .delete, path: Path.applicationPasswords, parameters: parameters)
189+
return try await withCheckedThrowingContinuation { continuation in
190+
network.responseData(for: request) { result in
191+
switch result {
192+
case .success(let data):
193+
do {
194+
let mapper = ApplicationPasswordNameAndUUIDMapper()
195+
let list = try mapper.map(response: data)
196+
if let item = list.first(where: { $0.name == passwordName }) {
197+
continuation.resume(returning: item.uuid)
198+
} else {
199+
continuation.resume(throwing: ApplicationPasswordUseCaseError.unableToFindPasswordUUID)
200+
}
201+
} catch {
202+
continuation.resume(throwing: error)
203+
}
204+
case .failure(let error):
205+
continuation.resume(throwing: error)
206+
}
207+
}
208+
}
209+
}
210+
211+
/// Deletes application password using UUID
212+
///
213+
func deleteApplicationPassword(_ uuid: String) async throws {
214+
let request = RESTRequest(siteURL: siteAddress, method: .delete, path: Path.applicationPasswords + "/" + uuid)
184215

185216
try await withCheckedThrowingContinuation { continuation in
186217
network.responseData(for: request) { result in

Networking/Networking/Extensions/CodingUserInfoKey+Woo.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,8 @@ extension CodingUserInfoKey {
2828
/// Used to store the Granularity within a Coder/Decoder's userInfo dictionary.
2929
///
3030
public static let granularity = CodingUserInfoKey(rawValue: "granularity")!
31+
32+
/// Used to store the WordPress org username within a Coder/Decoder's userInfo dictionary.
33+
///
34+
public static let wpOrgUsername = CodingUserInfoKey(rawValue: "wpOrgUsername")!
3135
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import Foundation
2+
3+
struct ApplicationPasswordMapper: Mapper {
4+
/// WordPress org username that the application password belongs to
5+
///
6+
/// We're injecting this field via `JSONDecoder.userInfo` because wpOrgUsername is not returned from the endpoint
7+
///
8+
let wpOrgUsername: String
9+
10+
func map(response: Data) throws -> ApplicationPassword {
11+
let decoder = JSONDecoder()
12+
decoder.userInfo = [
13+
.wpOrgUsername: wpOrgUsername
14+
]
15+
let password = try decoder.decode(ApplicationPassword.self, from: response)
16+
return password
17+
}
18+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import Foundation
2+
3+
struct ApplicationPasswordNameAndUUIDMapper: Mapper {
4+
func map(response: Data) throws -> [ApplicationPasswordNameAndUUID] {
5+
let decoder = JSONDecoder()
6+
return try decoder.decode([ApplicationPasswordNameAndUUID].self, from: response)
7+
}
8+
}

Networking/Networking/Mapper/ApplicationPasswordMapper.swift

Lines changed: 0 additions & 12 deletions
This file was deleted.

0 commit comments

Comments
 (0)