Skip to content

Commit 914d62c

Browse files
authored
Merge branch 'trunk' into issue/8589-ipp-banner-dismiss
2 parents 9884f22 + a8f3e9c commit 914d62c

File tree

120 files changed

+3427
-432
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

120 files changed

+3427
-432
lines changed

Experiments/Experiments/DefaultFeatureFlagService.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,6 @@ public struct DefaultFeatureFlagService: FeatureFlagService {
6060
case .applicationPasswordAuthenticationForSiteCredentialLogin:
6161
// Enable this to test application password authentication (WIP)
6262
return false
63-
case .productsBulkEditing:
64-
return buildConfig == .localDeveloper || buildConfig == .alpha
6563
case .domainSettings:
6664
return buildConfig == .localDeveloper || buildConfig == .alpha
6765
default:

Experiments/Experiments/FeatureFlag.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,6 @@ public enum FeatureFlag: Int {
142142
///
143143
case applicationPasswordAuthenticationForSiteCredentialLogin
144144

145-
/// Bulk editing of status and price in products list
146-
///
147-
case productsBulkEditing
148-
149145
/// Whether to enable domain updates from the settings for a WPCOM site.
150146
///
151147
case domainSettings

Networking/Networking.xcodeproj/project.pbxproj

Lines changed: 101 additions & 13 deletions
Large diffs are not rendered by default.

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
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
enum ApplicationPasswordAuthenticatorError: Error {
1+
enum RequestAuthenticatorError: Error {
22
case applicationPasswordUseCaseNotAvailable
33
case applicationPasswordNotAvailable
44
}
55

6-
protocol ApplicationPasswordAuthenticator {
6+
protocol RequestAuthenticator {
77
/// Credentials to authenticate the URLRequest
88
///
99
var credentials: Credentials? { get }
@@ -26,7 +26,7 @@ protocol ApplicationPasswordAuthenticator {
2626

2727
/// Authenticates request
2828
///
29-
public struct DefaultApplicationPasswordAuthenticator: ApplicationPasswordAuthenticator {
29+
public struct DefaultRequestAuthenticator: RequestAuthenticator {
3030
/// Credentials to authenticate the URLRequest
3131
///
3232
let credentials: Credentials?
@@ -71,7 +71,7 @@ public struct DefaultApplicationPasswordAuthenticator: ApplicationPasswordAuthen
7171
///
7272
func generateApplicationPassword() async throws {
7373
guard let applicationPasswordUseCase else {
74-
throw ApplicationPasswordAuthenticatorError.applicationPasswordUseCaseNotAvailable
74+
throw RequestAuthenticatorError.applicationPasswordUseCaseNotAvailable
7575
}
7676
let _ = try await applicationPasswordUseCase.generateNewPassword()
7777
return
@@ -84,7 +84,7 @@ public struct DefaultApplicationPasswordAuthenticator: ApplicationPasswordAuthen
8484
}
8585
}
8686

87-
private extension DefaultApplicationPasswordAuthenticator {
87+
private extension DefaultRequestAuthenticator {
8888
/// To check whether the given URLRequest is a REST API request
8989
///
9090
func isRestAPIRequest(_ urlRequest: URLRequest) -> Bool {
@@ -110,7 +110,7 @@ private extension DefaultApplicationPasswordAuthenticator {
110110
///
111111
func authenticateUsingApplicationPasswordIfPossible(_ urlRequest: URLRequest) throws -> URLRequest {
112112
guard let applicationPassword = applicationPasswordUseCase?.applicationPassword else {
113-
throw ApplicationPasswordAuthenticatorError.applicationPasswordNotAvailable
113+
throw RequestAuthenticatorError.applicationPasswordNotAvailable
114114
}
115115

116116
return AuthenticatedRESTRequest(applicationPassword: applicationPassword, request: urlRequest).asURLRequest()

Networking/Networking/ApplicationPassword/RequestConverter.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ struct RequestConverter {
66
let credentials: Credentials?
77

88
func convert(_ request: URLRequestConvertible) -> URLRequestConvertible {
9-
guard let jetpackRequest = request as? JetpackRequest,
9+
guard let convertibleRequest = request as? RESTRequestConvertible,
1010
case let .wporg(_, _, siteAddress) = credentials,
11-
let restRequest = jetpackRequest.asRESTRequest(with: siteAddress) else {
11+
let restRequest = convertibleRequest.asRESTRequest(with: siteAddress) else {
1212
return request
1313
}
1414

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,29 @@ import Foundation
33

44
/// Authenticates and retries requests
55
///
6-
final class ApplicationPasswordRequestProcessor {
6+
final class RequestProcessor {
77
private var requestsToRetry = [RequestRetryCompletion]()
88

99
private var isAuthenticating = false
1010

11-
private let requestAuthenticator: ApplicationPasswordAuthenticator
11+
private let requestAuthenticator: RequestAuthenticator
1212

13-
init(requestAuthenticator: ApplicationPasswordAuthenticator) {
13+
init(requestAuthenticator: RequestAuthenticator) {
1414
self.requestAuthenticator = requestAuthenticator
1515
}
1616
}
1717

1818
// MARK: Request Authentication
1919
//
20-
extension ApplicationPasswordRequestProcessor: RequestAdapter {
20+
extension RequestProcessor: RequestAdapter {
2121
func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
2222
return try requestAuthenticator.authenticate(urlRequest)
2323
}
2424
}
2525

2626
// MARK: Retrying Request
2727
//
28-
extension ApplicationPasswordRequestProcessor: RequestRetrier {
28+
extension RequestProcessor: RequestRetrier {
2929
func should(_ manager: Alamofire.SessionManager,
3030
retry request: Alamofire.Request,
3131
with error: Error,
@@ -48,7 +48,7 @@ extension ApplicationPasswordRequestProcessor: RequestRetrier {
4848

4949
// MARK: Helpers
5050
//
51-
private extension ApplicationPasswordRequestProcessor {
51+
private extension RequestProcessor {
5252
func generateApplicationPassword() {
5353
Task(priority: .medium) {
5454
isAuthenticating = true
@@ -66,7 +66,7 @@ private extension ApplicationPasswordRequestProcessor {
6666

6767
func shouldRetry(_ error: Error) -> Bool {
6868
// Need to generate application password
69-
if .applicationPasswordNotAvailable == error as? ApplicationPasswordAuthenticatorError {
69+
if .applicationPasswordNotAvailable == error as? RequestAuthenticatorError {
7070
return true
7171
}
7272

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
}

Networking/Networking/Mapper/AddOnGroupMapper.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ struct AddOnGroupMapper: Mapper {
1111
func map(response: Data) throws -> [AddOnGroup] {
1212
let decoder = JSONDecoder()
1313
decoder.userInfo = [.siteID: siteID]
14-
return try decoder.decode(AddOnGroupEnvelope.self, from: response).data
14+
do {
15+
return try decoder.decode(AddOnGroupEnvelope.self, from: response).data
16+
} catch {
17+
return try decoder.decode([AddOnGroup].self, from: response)
18+
}
1519
}
1620
}
1721

0 commit comments

Comments
 (0)