Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3bf6772
Enable application password for fetching site plugin details
itsmeichigo Jan 2, 2023
6845228
Check for woo plugin after authentication
itsmeichigo Jan 2, 2023
d8b8e64
Show alert when Woo check fails
itsmeichigo Jan 2, 2023
1047167
Use default alert for site credential login failures
itsmeichigo Jan 2, 2023
cd1fb1c
Revert changes to FancyAlertViewController
itsmeichigo Jan 2, 2023
3b201ca
Remove formatting for no Woo error
itsmeichigo Jan 2, 2023
2295109
Remove alert title for site credential failure
itsmeichigo Jan 2, 2023
736e59b
Move login checker to a new file
itsmeichigo Jan 3, 2023
deecb1c
Revert "Enable application password for fetching site plugin details"
itsmeichigo Jan 3, 2023
a141eec
Add woo check to settings remote and store
itsmeichigo Jan 3, 2023
6c527d6
Check woo with site settings
itsmeichigo Jan 3, 2023
aec51ca
Simplify code with less guard let self
itsmeichigo Jan 3, 2023
5939c7f
Refactor PostSiteCredentialLoginChecker for testability
itsmeichigo Jan 3, 2023
81a4e03
Add tests for PostSiteCredentialLoginCheckerTests's application passw…
itsmeichigo Jan 3, 2023
3754169
Add tests for role eligibility check
itsmeichigo Jan 3, 2023
2697edd
Revert "Add woo check to settings remote and store"
itsmeichigo Jan 3, 2023
e2eebbd
Check for woo in WordPressSite
itsmeichigo Jan 3, 2023
eaf0b7a
Check for Woo by fetching WordPressSite
itsmeichigo Jan 3, 2023
e33605d
Remove redundant line
itsmeichigo Jan 3, 2023
2499757
Add tests for woo check
itsmeichigo Jan 3, 2023
1f78ccd
Add namespaces to the mock response for wordpress site
itsmeichigo Jan 3, 2023
c9a5fa3
Add more checks to WordPressSiteMapperTests
itsmeichigo Jan 3, 2023
4d2afb8
Fix unit test failure for WordPressSiteStoreTests
itsmeichigo Jan 3, 2023
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
17 changes: 15 additions & 2 deletions Networking/Networking/Model/WordPressSite.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,23 @@ public struct WordPressSite: Decodable, Equatable {
///
public let gmtOffset: String

public init(name: String, description: String, url: String, timezone: String, gmtOffset: String) {
/// Namespaces supported by the site.
///
public let namespaces: [String]

/// Whether WooCommerce is one of the active plugins in the site.
///
public var isWooCommerceActive: Bool {
namespaces.contains { $0.hasPrefix(Constants.wooNameSpace) }
}

public init(name: String, description: String, url: String, timezone: String, gmtOffset: String, namespaces: [String]) {
self.name = name
self.description = description
self.url = url
self.timezone = timezone
self.gmtOffset = gmtOffset
self.namespaces = namespaces
}
}

Expand All @@ -47,7 +58,7 @@ public extension WordPressSite {
plan: "",
isJetpackThePluginInstalled: false,
isJetpackConnected: false,
isWooCommerceActive: true, // we expect to only call this after checking Woo is active
isWooCommerceActive: isWooCommerceActive,
isWordPressComStore: false,
jetpackConnectionActivePlugins: [],
timezone: timezone,
Expand All @@ -64,10 +75,12 @@ private extension WordPressSite {
case url
case timezone = "timezone_string"
case gmtOffset = "gmt_offset"
case namespaces
}

enum Constants {
static let adminPath = "/wp-admin"
static let loginPath = "/wp-login.php"
static let wooNameSpace = "wc/"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ final class WordPressSiteMapperTests: XCTestCase {
XCTAssertEqual(site.url, "https://test.com")
XCTAssertEqual(site.gmtOffset, "0")
XCTAssertEqual(site.timezone, "")
XCTAssertFalse(site.namespaces.isEmpty)
XCTAssertFalse(site.isWooCommerceActive)
}
}

Expand Down
6 changes: 6 additions & 0 deletions Networking/NetworkingTests/Responses/wordpress-site-info.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,10 @@
"gmt_offset": "0",
"timezone_string": "",
"authentication": [],
"namespaces": [
"oembed/1.0",
"wp/v2",
"wp-site-health/v1",
"wp-block-editor/v1"
]
}
146 changes: 11 additions & 135 deletions WooCommerce/Classes/Authentication/AuthenticationManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,12 @@ import KeychainAccess
import WordPressAuthenticator
import WordPressKit
import Yosemite
import WordPressUI
import class Networking.UserAgent
import enum Experiments.ABTest
import struct Networking.Settings
import protocol Experiments.FeatureFlagService
import protocol Storage.StorageManagerType
import protocol Networking.ApplicationPasswordUseCase
import class Networking.DefaultApplicationPasswordUseCase
import enum Networking.ApplicationPasswordUseCaseError

/// Encapsulates all of the interactions with the WordPress Authenticator
///
Expand Down Expand Up @@ -48,13 +45,8 @@ class AuthenticationManager: Authentication {

private let analytics: Analytics

/// Keep strong reference of the use case to check for application password availability if necessary.
private var applicationPasswordUseCase: ApplicationPasswordUseCase?

/// Keep strong reference of the use case to check for role eligibility if necessary.
private lazy var roleEligibilityUseCase: RoleEligibilityUseCase = {
.init(stores: ServiceLocator.stores)
}()
/// Keeps a reference to the checker
private var postSiteCredentialLoginChecker: PostSiteCredentialLoginChecker?

init(storageManager: StorageManagerType = ServiceLocator.storageManager,
featureFlagService: FeatureFlagService = ServiceLocator.featureFlagService,
Expand Down Expand Up @@ -398,8 +390,7 @@ extension AuthenticationManager: WordPressAuthenticatorDelegate {
featureFlagService.isFeatureFlagEnabled(.applicationPasswordAuthenticationForSiteCredentialLogin) {
return didAuthenticateUser(to: siteURL,
with: siteCredentials,
in: navigationController,
source: source)
in: navigationController)
}

/// Jetpack is required. Present an error if we don't detect a valid installation for a self-hosted site.
Expand Down Expand Up @@ -743,7 +734,7 @@ private extension AuthenticationManager {
/// The error screen to be displayed when the user tries to enter a site without WooCommerce.
///
func noWooUI(for site: Site,
with matcher: ULAccountMatcher,
with matcher: ULAccountMatcher = .init(),
navigationController: UINavigationController,
onStorePickerDismiss: @escaping () -> Void) -> UIViewController {
let viewModel = NoWooErrorViewModel(
Expand Down Expand Up @@ -807,139 +798,24 @@ private extension AuthenticationManager {
return accountMismatchUI(for: site.url, siteCredentials: nil, with: matcher, in: navigationController)
}

/// The error screen to be displayed when the user tries to log in with site credentials
/// with application password disabled.
///
func applicationPasswordDisabledUI(for siteURL: String) -> UIViewController {
let viewModel = ApplicationPasswordDisabledViewModel(siteURL: siteURL)
return ULErrorViewController(viewModel: viewModel)
}

/// Checks if the authenticated user is eligible to use the app and navigates to the home screen.
///
func didAuthenticateUser(to siteURL: String,
with siteCredentials: WordPressOrgCredentials,
in navigationController: UINavigationController,
source: SignInSource?) {
// check if application password is enabled
guard let applicationPasswordUseCase = try? DefaultApplicationPasswordUseCase(
in navigationController: UINavigationController) {
guard let useCase = try? DefaultApplicationPasswordUseCase(
username: siteCredentials.username,
password: siteCredentials.password,
siteAddress: siteCredentials.siteURL
) else {
return assertionFailure("⛔️ Error creating application password use case")
}
self.applicationPasswordUseCase = applicationPasswordUseCase
checkApplicationPassword(for: siteURL,
with: applicationPasswordUseCase,
in: navigationController) { [weak self] in
guard let self else { return }
self.checkRoleEligibility(in: navigationController) { [weak self] in
guard let self else { return }
// TODO: check for Woo
// navigates to home screen immediately with a placeholder store ID
self.startStorePicker(with: WooConstants.placeholderStoreID, in: navigationController)
}
let checker = PostSiteCredentialLoginChecker(applicationPasswordUseCase: useCase)
checker.checkEligibility(for: siteURL, from: navigationController) { [weak self] in
// navigates to home screen immediately with a placeholder store ID
self?.startStorePicker(with: WooConstants.placeholderStoreID, in: navigationController)
}
}

func checkApplicationPassword(for siteURL: String,
with useCase: ApplicationPasswordUseCase,
in navigationController: UINavigationController, onSuccess: @escaping () -> Void) {
Task {
do {
let _ = try await useCase.generateNewPassword()
await MainActor.run {
onSuccess()
}
} catch ApplicationPasswordUseCaseError.applicationPasswordsDisabled {
// show application password disabled error
await MainActor.run {
let errorUI = applicationPasswordDisabledUI(for: siteURL)
navigationController.show(errorUI, sender: nil)
}
} catch {
// show generic error
await MainActor.run {
DDLogError("⛔️ Error generating application password: \(error)")
let alert = FancyAlertViewController.makeSiteCredentialLoginAlert(
message: Localization.applicationPasswordError,
retryAction: { [weak self] in
self?.checkApplicationPassword(for: siteURL, with: useCase, in: navigationController, onSuccess: onSuccess)
},
restartLoginAction: {
ServiceLocator.stores.deauthenticate()
navigationController.popToRootViewController(animated: true)
}
)
navigationController.present(alert, animated: true)
}
}
}
}

/// Checks role eligibility for the logged in user with the site address saved in the credentials.
/// Placeholder store ID is used because we are checking for users logging in with site credentials.
///
func checkRoleEligibility(in navigationController: UINavigationController, onSuccess: @escaping () -> Void) {
roleEligibilityUseCase.checkEligibility(for: WooConstants.placeholderStoreID) { [weak self] result in
guard let self else { return }
switch result {
case .success:
onSuccess()
case .failure(let error):
if case let RoleEligibilityError.insufficientRole(errorInfo) = error {
self.showRoleErrorScreen(for: WooConstants.placeholderStoreID,
errorInfo: errorInfo,
in: navigationController,
onSuccess: onSuccess)
} else {
// show generic error
DDLogError("⛔️ Error checking role eligibility: \(error)")
let alert = FancyAlertViewController.makeSiteCredentialLoginAlert(
message: Localization.roleEligibilityCheckError,
retryAction: { [weak self] in
self?.checkRoleEligibility(in: navigationController, onSuccess: onSuccess)
},
restartLoginAction: {
ServiceLocator.stores.deauthenticate()
navigationController.popToRootViewController(animated: true)
}
)
navigationController.present(alert, animated: true)
}
}
}
}

/// Shows a Role Error page using the provided error information.
///
func showRoleErrorScreen(for siteID: Int64,
errorInfo: StorageEligibilityErrorInfo,
in navigationController: UINavigationController,
onSuccess: @escaping () -> Void) {
let errorViewModel = RoleErrorViewModel(siteID: siteID, title: errorInfo.name, subtitle: errorInfo.humanizedRoles, useCase: self.roleEligibilityUseCase)
let errorViewController = RoleErrorViewController(viewModel: errorViewModel)

errorViewModel.onSuccess = onSuccess
errorViewModel.onDeauthenticationRequest = {
ServiceLocator.stores.deauthenticate()
navigationController.popToRootViewController(animated: true)
}
navigationController.show(errorViewController, sender: self)
}
}

private extension AuthenticationManager {
enum Localization {
static let applicationPasswordError = NSLocalizedString(
"Error fetching application password for your site.",
comment: "Error message displayed when application password cannot be fetched after authentication."
)
static let roleEligibilityCheckError = NSLocalizedString(
"Error fetching user information.",
comment: "Error message displayed when user information cannot be fetched after authentication."
)
self.postSiteCredentialLoginChecker = checker
}
}

Expand Down
Loading