Skip to content

Commit 5a0b846

Browse files
authored
Merge pull request #8525 from woocommerce/feat/8453-woo-check
REST API: Check for Woo after authentication
2 parents 1c75cb4 + 4d2afb8 commit 5a0b846

File tree

9 files changed

+483
-173
lines changed

9 files changed

+483
-173
lines changed

Networking/Networking/Model/WordPressSite.swift

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,23 @@ public struct WordPressSite: Decodable, Equatable {
2424
///
2525
public let gmtOffset: String
2626

27-
public init(name: String, description: String, url: String, timezone: String, gmtOffset: String) {
27+
/// Namespaces supported by the site.
28+
///
29+
public let namespaces: [String]
30+
31+
/// Whether WooCommerce is one of the active plugins in the site.
32+
///
33+
public var isWooCommerceActive: Bool {
34+
namespaces.contains { $0.hasPrefix(Constants.wooNameSpace) }
35+
}
36+
37+
public init(name: String, description: String, url: String, timezone: String, gmtOffset: String, namespaces: [String]) {
2838
self.name = name
2939
self.description = description
3040
self.url = url
3141
self.timezone = timezone
3242
self.gmtOffset = gmtOffset
43+
self.namespaces = namespaces
3344
}
3445
}
3546

@@ -47,7 +58,7 @@ public extension WordPressSite {
4758
plan: "",
4859
isJetpackThePluginInstalled: false,
4960
isJetpackConnected: false,
50-
isWooCommerceActive: true, // we expect to only call this after checking Woo is active
61+
isWooCommerceActive: isWooCommerceActive,
5162
isWordPressComStore: false,
5263
jetpackConnectionActivePlugins: [],
5364
timezone: timezone,
@@ -64,10 +75,12 @@ private extension WordPressSite {
6475
case url
6576
case timezone = "timezone_string"
6677
case gmtOffset = "gmt_offset"
78+
case namespaces
6779
}
6880

6981
enum Constants {
7082
static let adminPath = "/wp-admin"
7183
static let loginPath = "/wp-login.php"
84+
static let wooNameSpace = "wc/"
7285
}
7386
}

Networking/NetworkingTests/Mapper/WordPressSiteMapperTests.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ final class WordPressSiteMapperTests: XCTestCase {
1313
XCTAssertEqual(site.url, "https://test.com")
1414
XCTAssertEqual(site.gmtOffset, "0")
1515
XCTAssertEqual(site.timezone, "")
16+
XCTAssertFalse(site.namespaces.isEmpty)
17+
XCTAssertFalse(site.isWooCommerceActive)
1618
}
1719
}
1820

Networking/NetworkingTests/Responses/wordpress-site-info.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,10 @@
66
"gmt_offset": "0",
77
"timezone_string": "",
88
"authentication": [],
9+
"namespaces": [
10+
"oembed/1.0",
11+
"wp/v2",
12+
"wp-site-health/v1",
13+
"wp-block-editor/v1"
14+
]
915
}

WooCommerce/Classes/Authentication/AuthenticationManager.swift

Lines changed: 11 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,12 @@ import KeychainAccess
33
import WordPressAuthenticator
44
import WordPressKit
55
import Yosemite
6-
import WordPressUI
76
import class Networking.UserAgent
87
import enum Experiments.ABTest
98
import struct Networking.Settings
109
import protocol Experiments.FeatureFlagService
1110
import protocol Storage.StorageManagerType
12-
import protocol Networking.ApplicationPasswordUseCase
1311
import class Networking.DefaultApplicationPasswordUseCase
14-
import enum Networking.ApplicationPasswordUseCaseError
1512

1613
/// Encapsulates all of the interactions with the WordPress Authenticator
1714
///
@@ -48,13 +45,8 @@ class AuthenticationManager: Authentication {
4845

4946
private let analytics: Analytics
5047

51-
/// Keep strong reference of the use case to check for application password availability if necessary.
52-
private var applicationPasswordUseCase: ApplicationPasswordUseCase?
53-
54-
/// Keep strong reference of the use case to check for role eligibility if necessary.
55-
private lazy var roleEligibilityUseCase: RoleEligibilityUseCase = {
56-
.init(stores: ServiceLocator.stores)
57-
}()
48+
/// Keeps a reference to the checker
49+
private var postSiteCredentialLoginChecker: PostSiteCredentialLoginChecker?
5850

5951
init(storageManager: StorageManagerType = ServiceLocator.storageManager,
6052
featureFlagService: FeatureFlagService = ServiceLocator.featureFlagService,
@@ -391,8 +383,7 @@ extension AuthenticationManager: WordPressAuthenticatorDelegate {
391383
featureFlagService.isFeatureFlagEnabled(.applicationPasswordAuthenticationForSiteCredentialLogin) {
392384
return didAuthenticateUser(to: siteURL,
393385
with: siteCredentials,
394-
in: navigationController,
395-
source: source)
386+
in: navigationController)
396387
}
397388

398389
/// Jetpack is required. Present an error if we don't detect a valid installation for a self-hosted site.
@@ -736,7 +727,7 @@ private extension AuthenticationManager {
736727
/// The error screen to be displayed when the user tries to enter a site without WooCommerce.
737728
///
738729
func noWooUI(for site: Site,
739-
with matcher: ULAccountMatcher,
730+
with matcher: ULAccountMatcher = .init(),
740731
navigationController: UINavigationController,
741732
onStorePickerDismiss: @escaping () -> Void) -> UIViewController {
742733
let viewModel = NoWooErrorViewModel(
@@ -800,139 +791,24 @@ private extension AuthenticationManager {
800791
return accountMismatchUI(for: site.url, siteCredentials: nil, with: matcher, in: navigationController)
801792
}
802793

803-
/// The error screen to be displayed when the user tries to log in with site credentials
804-
/// with application password disabled.
805-
///
806-
func applicationPasswordDisabledUI(for siteURL: String) -> UIViewController {
807-
let viewModel = ApplicationPasswordDisabledViewModel(siteURL: siteURL)
808-
return ULErrorViewController(viewModel: viewModel)
809-
}
810-
811794
/// Checks if the authenticated user is eligible to use the app and navigates to the home screen.
812795
///
813796
func didAuthenticateUser(to siteURL: String,
814797
with siteCredentials: WordPressOrgCredentials,
815-
in navigationController: UINavigationController,
816-
source: SignInSource?) {
817-
// check if application password is enabled
818-
guard let applicationPasswordUseCase = try? DefaultApplicationPasswordUseCase(
798+
in navigationController: UINavigationController) {
799+
guard let useCase = try? DefaultApplicationPasswordUseCase(
819800
username: siteCredentials.username,
820801
password: siteCredentials.password,
821802
siteAddress: siteCredentials.siteURL
822803
) else {
823804
return assertionFailure("⛔️ Error creating application password use case")
824805
}
825-
self.applicationPasswordUseCase = applicationPasswordUseCase
826-
checkApplicationPassword(for: siteURL,
827-
with: applicationPasswordUseCase,
828-
in: navigationController) { [weak self] in
829-
guard let self else { return }
830-
self.checkRoleEligibility(in: navigationController) { [weak self] in
831-
guard let self else { return }
832-
// TODO: check for Woo
833-
// navigates to home screen immediately with a placeholder store ID
834-
self.startStorePicker(with: WooConstants.placeholderStoreID, in: navigationController)
835-
}
806+
let checker = PostSiteCredentialLoginChecker(applicationPasswordUseCase: useCase)
807+
checker.checkEligibility(for: siteURL, from: navigationController) { [weak self] in
808+
// navigates to home screen immediately with a placeholder store ID
809+
self?.startStorePicker(with: WooConstants.placeholderStoreID, in: navigationController)
836810
}
837-
}
838-
839-
func checkApplicationPassword(for siteURL: String,
840-
with useCase: ApplicationPasswordUseCase,
841-
in navigationController: UINavigationController, onSuccess: @escaping () -> Void) {
842-
Task {
843-
do {
844-
let _ = try await useCase.generateNewPassword()
845-
await MainActor.run {
846-
onSuccess()
847-
}
848-
} catch ApplicationPasswordUseCaseError.applicationPasswordsDisabled {
849-
// show application password disabled error
850-
await MainActor.run {
851-
let errorUI = applicationPasswordDisabledUI(for: siteURL)
852-
navigationController.show(errorUI, sender: nil)
853-
}
854-
} catch {
855-
// show generic error
856-
await MainActor.run {
857-
DDLogError("⛔️ Error generating application password: \(error)")
858-
let alert = FancyAlertViewController.makeSiteCredentialLoginAlert(
859-
message: Localization.applicationPasswordError,
860-
retryAction: { [weak self] in
861-
self?.checkApplicationPassword(for: siteURL, with: useCase, in: navigationController, onSuccess: onSuccess)
862-
},
863-
restartLoginAction: {
864-
ServiceLocator.stores.deauthenticate()
865-
navigationController.popToRootViewController(animated: true)
866-
}
867-
)
868-
navigationController.present(alert, animated: true)
869-
}
870-
}
871-
}
872-
}
873-
874-
/// Checks role eligibility for the logged in user with the site address saved in the credentials.
875-
/// Placeholder store ID is used because we are checking for users logging in with site credentials.
876-
///
877-
func checkRoleEligibility(in navigationController: UINavigationController, onSuccess: @escaping () -> Void) {
878-
roleEligibilityUseCase.checkEligibility(for: WooConstants.placeholderStoreID) { [weak self] result in
879-
guard let self else { return }
880-
switch result {
881-
case .success:
882-
onSuccess()
883-
case .failure(let error):
884-
if case let RoleEligibilityError.insufficientRole(errorInfo) = error {
885-
self.showRoleErrorScreen(for: WooConstants.placeholderStoreID,
886-
errorInfo: errorInfo,
887-
in: navigationController,
888-
onSuccess: onSuccess)
889-
} else {
890-
// show generic error
891-
DDLogError("⛔️ Error checking role eligibility: \(error)")
892-
let alert = FancyAlertViewController.makeSiteCredentialLoginAlert(
893-
message: Localization.roleEligibilityCheckError,
894-
retryAction: { [weak self] in
895-
self?.checkRoleEligibility(in: navigationController, onSuccess: onSuccess)
896-
},
897-
restartLoginAction: {
898-
ServiceLocator.stores.deauthenticate()
899-
navigationController.popToRootViewController(animated: true)
900-
}
901-
)
902-
navigationController.present(alert, animated: true)
903-
}
904-
}
905-
}
906-
}
907-
908-
/// Shows a Role Error page using the provided error information.
909-
///
910-
func showRoleErrorScreen(for siteID: Int64,
911-
errorInfo: StorageEligibilityErrorInfo,
912-
in navigationController: UINavigationController,
913-
onSuccess: @escaping () -> Void) {
914-
let errorViewModel = RoleErrorViewModel(siteID: siteID, title: errorInfo.name, subtitle: errorInfo.humanizedRoles, useCase: self.roleEligibilityUseCase)
915-
let errorViewController = RoleErrorViewController(viewModel: errorViewModel)
916-
917-
errorViewModel.onSuccess = onSuccess
918-
errorViewModel.onDeauthenticationRequest = {
919-
ServiceLocator.stores.deauthenticate()
920-
navigationController.popToRootViewController(animated: true)
921-
}
922-
navigationController.show(errorViewController, sender: self)
923-
}
924-
}
925-
926-
private extension AuthenticationManager {
927-
enum Localization {
928-
static let applicationPasswordError = NSLocalizedString(
929-
"Error fetching application password for your site.",
930-
comment: "Error message displayed when application password cannot be fetched after authentication."
931-
)
932-
static let roleEligibilityCheckError = NSLocalizedString(
933-
"Error fetching user information.",
934-
comment: "Error message displayed when user information cannot be fetched after authentication."
935-
)
811+
self.postSiteCredentialLoginChecker = checker
936812
}
937813
}
938814

0 commit comments

Comments
 (0)