Skip to content
This repository was archived by the owner on Feb 5, 2025. It is now read-only.

Commit b51b4b2

Browse files
authored
Confirm that site is indeed not a WordPress site after XML-RPC validation fails (#796)
2 parents cd0ed3f + fb783d4 commit b51b4b2

File tree

6 files changed

+310
-68
lines changed

6 files changed

+310
-68
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ _None._
4848

4949
_None._
5050

51+
## 8.0.1
52+
53+
### Bug Fixes
54+
55+
- Fix an issue where self-hosted sites are incorrectly flagged as non WordPress sites. [#796]
56+
5157
## 8.0.0
5258

5359
### Breaking Changes
@@ -57,6 +63,7 @@ _None._
5763
### Internal Changes
5864

5965
* Depend on WordPressKit 9.0.0 and make necessary code changes to adopt the new API. [808]
66+
>>>>>>> trunk
6067
6168
## 7.3.1
6269

WordPressAuthenticator.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
01720C132AEB5013006713DF /* SiteAddressViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01720C122AEB5013006713DF /* SiteAddressViewModelTests.swift */; };
11+
01720C152AEB5101006713DF /* SiteAddressViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01720C142AEB5101006713DF /* SiteAddressViewModel.swift */; };
1012
0193F7752A615521004D7C16 /* MemoryManagementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0193F7742A615521004D7C16 /* MemoryManagementTests.swift */; };
1113
020BE74A23B0BD2E007FE54C /* WordPressAuthenticatorDisplayImages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020BE74923B0BD2E007FE54C /* WordPressAuthenticatorDisplayImages.swift */; };
1214
020DEF6428AA091100C85D51 /* MagicLinkRequester.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020DEF6328AA091100C85D51 /* MagicLinkRequester.swift */; };
@@ -263,6 +265,8 @@
263265
/* End PBXCopyFilesBuildPhase section */
264266

265267
/* Begin PBXFileReference section */
268+
01720C122AEB5013006713DF /* SiteAddressViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteAddressViewModelTests.swift; sourceTree = "<group>"; };
269+
01720C142AEB5101006713DF /* SiteAddressViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteAddressViewModel.swift; sourceTree = "<group>"; };
266270
0193F7742A615521004D7C16 /* MemoryManagementTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryManagementTests.swift; sourceTree = "<group>"; };
267271
020BE74923B0BD2E007FE54C /* WordPressAuthenticatorDisplayImages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordPressAuthenticatorDisplayImages.swift; sourceTree = "<group>"; };
268272
020DEF6328AA091100C85D51 /* MagicLinkRequester.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MagicLinkRequester.swift; sourceTree = "<group>"; };
@@ -565,6 +569,7 @@
565569
3F86A83F29D280DC005D20C0 /* SingIn */ = {
566570
isa = PBXGroup;
567571
children = (
572+
01720C122AEB5013006713DF /* SiteAddressViewModelTests.swift */,
568573
3F86A83D29D280D7005D20C0 /* AppleAuthenticatorTests.swift */,
569574
3F86A84929D2A982005D20C0 /* LoginViewControllerTests.swift */,
570575
);
@@ -1079,6 +1084,7 @@
10791084
CEC77C6524854F2E00FB9050 /* SiteAddressViewController.swift */,
10801085
CEC77C6724854F3E00FB9050 /* SiteAddress.storyboard */,
10811086
CE73475524B77A3800A22660 /* SiteCredentialsViewController.swift */,
1087+
01720C142AEB5101006713DF /* SiteAddressViewModel.swift */,
10821088
);
10831089
path = "Site Address";
10841090
sourceTree = "<group>";
@@ -1532,6 +1538,7 @@
15321538
3F86A84229D28473005D20C0 /* SocialUserCreating.swift in Sources */,
15331539
3F9439BE27D6F9B60067183A /* LoginPrologueViewController.swift in Sources */,
15341540
B560913B208A563800399AE4 /* LoginSelfHostedViewController.swift in Sources */,
1541+
01720C152AEB5101006713DF /* SiteAddressViewModel.swift in Sources */,
15351542
B5609136208A563800399AE4 /* Login2FAViewController.swift in Sources */,
15361543
B56090E1208A4F9D00399AE4 /* WPWalkthroughTextField.m in Sources */,
15371544
B56090EF208A527000399AE4 /* WPStyleGuide+Login.swift in Sources */,
@@ -1560,6 +1567,7 @@
15601567
BA53D64824DFDF97001F1ABF /* WordPressSourceTagTests.swift in Sources */,
15611568
3F30A6BA299F12E30004452F /* Character+URLSafeTests.swift in Sources */,
15621569
4A1DEF4B29341B1F00322608 /* LoggingTests.swift in Sources */,
1570+
01720C132AEB5013006713DF /* SiteAddressViewModelTests.swift in Sources */,
15631571
3F86A83E29D280D7005D20C0 /* AppleAuthenticatorTests.swift in Sources */,
15641572
3F3694022991E244006E923E /* JSONWebToken+Fixtures.swift in Sources */,
15651573
3F879FDF293A501D005C2B48 /* URLRequest+GoogleSignInTests.swift in Sources */,

WordPressAuthenticator/Unified Auth/View Related/Site Address/SiteAddressViewController.swift

Lines changed: 29 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ final class SiteAddressViewController: LoginViewController {
3232
///
3333
private let isSiteDiscovery: Bool
3434
private let configuration = WordPressAuthenticator.shared.configuration
35+
private lazy var viewModel: SiteAddressViewModel = {
36+
return SiteAddressViewModel(
37+
isSiteDiscovery: isSiteDiscovery,
38+
xmlrpcFacade: WordPressXMLRPCAPIFacade(),
39+
authenticationDelegate: authenticationDelegate,
40+
blogService: WordPressComBlogService(),
41+
loginFields: loginFields
42+
)
43+
}()
3544

3645
init?(isSiteDiscovery: Bool, coder: NSCoder) {
3746
self.isSiteDiscovery = isSiteDiscovery
@@ -482,75 +491,29 @@ private extension SiteAddressViewController {
482491
}
483492

484493
func guessXMLRPCURL(for siteAddress: String) {
485-
let facade = WordPressXMLRPCAPIFacade()
486-
facade.guessXMLRPCURL(forSite: siteAddress, success: { [weak self] (url) in
487-
// Success! We now know that we have a valid XML-RPC endpoint.
488-
// At this point, we do NOT know if this is a WP.com site or a self-hosted site.
489-
if let url = url {
490-
self?.loginFields.meta.xmlrpcURL = url as NSURL
491-
}
492-
// Let's try to grab site info in preparation for the next screen.
493-
self?.fetchSiteInfo()
494-
495-
}, failure: { [weak self] (error) in
496-
guard let error = error, let self = self else {
497-
return
498-
}
499-
// Intentionally log the attempted address on failures.
500-
// It's not guaranteed to be included in the error object depending on the error.
501-
WPAuthenticatorLogInfo("Error attempting to connect to site address: \(self.loginFields.siteAddress)")
502-
WPAuthenticatorLogError(error.localizedDescription)
503-
504-
self.tracker.track(failure: .loginFailedToGuessXMLRPC)
505-
506-
self.configureViewLoading(false)
507-
508-
guard self.isSiteDiscovery == false else {
509-
WordPressAuthenticator.shared.delegate?.troubleshootSite(nil, in: self.navigationController)
510-
return
511-
}
512-
513-
let err = self.originalErrorOrError(error: error as NSError)
514-
515-
let errorMessage: String? = {
516-
if let xmlrpcValidatorError = err as? WordPressOrgXMLRPCValidatorError {
517-
return xmlrpcValidatorError.localizedDescription
518-
} else if (err.domain == NSURLErrorDomain && err.code == NSURLErrorCannotFindHost) ||
519-
(err.domain == NSURLErrorDomain && err.code == NSURLErrorNetworkConnectionLost) {
520-
// NSURLErrorNetworkConnectionLost can be returned when an invalid URL is entered.
521-
let msg = NSLocalizedString(
522-
"The site at this address is not a WordPress site. For us to connect to it, the site must use WordPress.",
523-
comment: "Error message shown a URL does not point to an existing site.")
524-
return msg
525-
} else {
526-
return nil
527-
}
528-
}()
529-
530-
/// Check if the host app wants to provide custom UI to handle the error.
531-
/// If it does, insert the custom UI provided by the host app and exit early
532-
if self.authenticationDelegate.shouldHandleError(err) {
533-
534-
// Send the error to the host app
535-
self.authenticationDelegate.handleError(err) { customUI in
536-
self.pushCustomUI(customUI)
537-
}
538-
539-
// Track error message as failure
494+
viewModel.guessXMLRPCURL(
495+
for: siteAddress,
496+
loading: { [weak self] isLoading in
497+
self?.configureViewLoading(isLoading)
498+
},
499+
completion: { [weak self] result -> Void in
500+
guard let self else { return }
501+
switch result {
502+
case .success:
503+
// Let's try to grab site info in preparation for the next screen.
504+
self.fetchSiteInfo()
505+
case .error(let error, let errorMessage):
540506
if let message = errorMessage {
541-
self.tracker.track(failure: message)
507+
self.displayError(message: message, moveVoiceOverFocus: true)
508+
} else {
509+
self.displayError(error, sourceTag: self.sourceTag)
542510
}
543-
544-
// Return as the error has been handled by the host app.
545-
return
546-
}
547-
548-
if let message = errorMessage {
549-
self.displayError(message: message, moveVoiceOverFocus: true)
550-
} else {
551-
self.displayError(error, sourceTag: self.sourceTag)
511+
case .troubleshootSite:
512+
WordPressAuthenticator.shared.delegate?.troubleshootSite(nil, in: self.navigationController)
513+
case .customUI(let viewController):
514+
self.pushCustomUI(viewController)
552515
}
553-
})
516+
})
554517
}
555518

556519
func fetchSiteInfo() {
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import Foundation
2+
import WordPressKit
3+
4+
struct SiteAddressViewModel {
5+
private let isSiteDiscovery: Bool
6+
private let xmlrpcFacade: WordPressXMLRPCAPIFacade
7+
private unowned let authenticationDelegate: WordPressAuthenticatorDelegate
8+
private let blogService: WordPressComBlogService
9+
private var loginFields: LoginFields
10+
11+
private let tracker = AuthenticatorAnalyticsTracker.shared
12+
13+
init(isSiteDiscovery: Bool,
14+
xmlrpcFacade: WordPressXMLRPCAPIFacade,
15+
authenticationDelegate: WordPressAuthenticatorDelegate,
16+
blogService: WordPressComBlogService,
17+
loginFields: LoginFields
18+
) {
19+
self.isSiteDiscovery = isSiteDiscovery
20+
self.xmlrpcFacade = xmlrpcFacade
21+
self.authenticationDelegate = authenticationDelegate
22+
self.blogService = blogService
23+
self.loginFields = loginFields
24+
}
25+
26+
enum GuessXMLRPCURLResult: Equatable {
27+
case success
28+
case error(NSError, String?)
29+
case troubleshootSite
30+
case customUI(UIViewController)
31+
}
32+
33+
func guessXMLRPCURL(
34+
for siteAddress: String,
35+
loading: @escaping ((Bool) -> ()),
36+
completion: @escaping (GuessXMLRPCURLResult) -> ()
37+
) {
38+
xmlrpcFacade.guessXMLRPCURL(forSite: siteAddress, success: { url in
39+
// Success! We now know that we have a valid XML-RPC endpoint.
40+
// At this point, we do NOT know if this is a WP.com site or a self-hosted site.
41+
if let url = url {
42+
self.loginFields.meta.xmlrpcURL = url as NSURL
43+
}
44+
45+
completion(.success)
46+
47+
}, failure: { error in
48+
guard let error = error else {
49+
return
50+
}
51+
// Intentionally log the attempted address on failures.
52+
// It's not guaranteed to be included in the error object depending on the error.
53+
WPAuthenticatorLogInfo("Error attempting to connect to site address: \(self.loginFields.siteAddress)")
54+
WPAuthenticatorLogError(error.localizedDescription)
55+
56+
self.tracker.track(failure: .loginFailedToGuessXMLRPC)
57+
58+
loading(false)
59+
60+
guard self.isSiteDiscovery == false else {
61+
completion(.troubleshootSite)
62+
return
63+
}
64+
65+
let err = self.originalErrorOrError(error: error as NSError)
66+
self.handleGuessXMLRPCURLError(error: err, loading: loading, completion: completion)
67+
})
68+
}
69+
70+
private func handleGuessXMLRPCURLError(
71+
error: NSError,
72+
loading: @escaping ((Bool) -> ()),
73+
completion: @escaping (GuessXMLRPCURLResult) -> ()
74+
) {
75+
let completion: (NSError, String?) -> Void = { error, errorMessage in
76+
if self.authenticationDelegate.shouldHandleError(error) {
77+
self.authenticationDelegate.handleError(error) { customUI in
78+
completion(.customUI(customUI))
79+
}
80+
if let message = errorMessage {
81+
self.tracker.track(failure: message)
82+
}
83+
return
84+
}
85+
86+
completion(.error(error, errorMessage))
87+
}
88+
89+
/// Confirm the site is not a WordPress site before describing it as an invalid WP site
90+
if let xmlrpcValidatorError = error as? WordPressOrgXMLRPCValidatorError, xmlrpcValidatorError == .invalid {
91+
loading(true)
92+
isWPSite { isWP in
93+
loading(false)
94+
if isWP {
95+
let error = WordPressOrgXMLRPCValidatorError.xmlrpc_missing
96+
completion(error as NSError, error.localizedDescription)
97+
} else {
98+
completion(error, Strings.notWPSiteErrorMessage)
99+
}
100+
}
101+
} else if (error.domain == NSURLErrorDomain && error.code == NSURLErrorCannotFindHost) ||
102+
(error.domain == NSURLErrorDomain && error.code == NSURLErrorNetworkConnectionLost) {
103+
completion(error, Strings.notWPSiteErrorMessage)
104+
} else {
105+
completion(error, (error as? WordPressOrgXMLRPCValidatorError)?.localizedDescription)
106+
}
107+
}
108+
109+
private func originalErrorOrError(error: NSError) -> NSError {
110+
guard let err = error.userInfo[XMLRPCOriginalErrorKey] as? NSError else {
111+
return error
112+
}
113+
114+
return err
115+
}
116+
}
117+
118+
extension SiteAddressViewModel {
119+
private func isWPSite(_ completion: @escaping (Bool) -> ()) {
120+
let baseSiteUrl = WordPressAuthenticator.baseSiteURL(string: loginFields.siteAddress)
121+
blogService.fetchUnauthenticatedSiteInfoForAddress(
122+
for: baseSiteUrl,
123+
success: { siteInfo in
124+
completion(siteInfo.isWP)
125+
},
126+
failure: { _ in
127+
completion(false)
128+
})
129+
}
130+
}
131+
132+
private extension SiteAddressViewModel {
133+
struct Strings {
134+
static let notWPSiteErrorMessage = NSLocalizedString("The site at this address is not a WordPress site. For us to connect to it, the site must use WordPress.", comment: "Error message shown when a URL does not point to an existing site.")
135+
}
136+
}

WordPressAuthenticatorTests/Mocks/WordPressAuthenticatorDelegateSpy.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class WordPressAuthenticatorDelegateSpy: WordPressAuthenticatorDelegate {
88
var showSupportNotificationIndicator: Bool = true
99
var supportEnabled: Bool = true
1010
var allowWPComLogin: Bool = true
11+
var shouldHandleError: Bool = false
1112

1213
private(set) var presentSignupEpilogueCalled = false
1314
private(set) var socialUser: SocialUser?
@@ -50,11 +51,13 @@ class WordPressAuthenticatorDelegateSpy: WordPressAuthenticatorDelegate {
5051
}
5152

5253
func shouldHandleError(_ error: Error) -> Bool {
53-
true
54+
shouldHandleError
5455
}
5556

5657
func handleError(_ error: Error, onCompletion: @escaping (UIViewController) -> Void) {
57-
// no-op
58+
if shouldHandleError {
59+
onCompletion(UIViewController())
60+
}
5861
}
5962

6063
func shouldPresentSignupEpilogue() -> Bool {

0 commit comments

Comments
 (0)