11import Combine
22import UIKit
33import WebKit
4+ import Yosemite
45import class Networking. UserAgent
56import struct WordPressAuthenticator. WordPressOrgCredentials
6- import enum Yosemite. Credentials
77
88/// A web view which is authenticated for WordPress.com, when possible.
99///
1010final class AuthenticatedWebViewController : UIViewController {
1111
12+ private let currentSite : Site ?
1213 private let viewModel : AuthenticatedWebViewModel
14+ private let authenticationFlow : WebViewAuthenticationFlow
1315
1416 private lazy var activityIndicator : UIActivityIndicatorView = {
1517 let activityIndicator = UIActivityIndicatorView ( style: . medium)
@@ -45,12 +47,15 @@ final class AuthenticatedWebViewController: UIViewController {
4547
4648 private let wpcomCredentials : Credentials ?
4749
50+ private var isFirstNavigation = true
4851
49- init ( viewModel: AuthenticatedWebViewModel , extraCredentials: Credentials ? = nil ) {
52+ init ( stores: StoresManager = ServiceLocator . stores,
53+ viewModel: AuthenticatedWebViewModel ,
54+ extraCredentials: Credentials ? = nil ) {
5055 self . viewModel = viewModel
51- let currentCredentials = ServiceLocator . stores. sessionManager. defaultCredentials
56+ let currentCredentials = stores. sessionManager. defaultCredentials
5257
53- self . siteCredentials = {
58+ let siteCredentials : WordPressOrgCredentials ? = {
5459 if case let . wporg( username, password, siteAddress) = extraCredentials {
5560 return WordPressOrgCredentials ( username: username,
5661 password: password,
@@ -65,14 +70,29 @@ final class AuthenticatedWebViewController: UIViewController {
6570 return nil
6671 } ( )
6772
68- self . wpcomCredentials = {
73+ let wpcomCredentials : Credentials ? = {
6974 if case . wpcom = extraCredentials {
7075 return extraCredentials
7176 } else if case . wpcom = currentCredentials {
7277 return currentCredentials
7378 }
7479 return nil
7580 } ( )
81+
82+ let currentSite = stores. sessionManager. defaultSite
83+
84+ self . authenticationFlow = {
85+ guard let currentSite else {
86+ return WebViewAuthenticationFlow . none
87+ }
88+ return viewModel. authenticationFlow ( currentSite: currentSite,
89+ wpcomCredentialsAvailable: wpcomCredentials != nil ,
90+ wporgCredentialsAvailable: siteCredentials != nil )
91+ } ( )
92+ self . currentSite = currentSite
93+ self . wpcomCredentials = wpcomCredentials
94+ self . siteCredentials = siteCredentials
95+
7696 super. init ( nibName: nil , bundle: nil )
7797
7898 if let initialURL = viewModel. initialURL,
@@ -93,6 +113,7 @@ final class AuthenticatedWebViewController: UIViewController {
93113 configureWebView ( )
94114 configureActivityIndicator ( )
95115 configureProgressBar ( )
116+ observeWebView ( )
96117 startLoading ( )
97118 }
98119
@@ -144,7 +165,7 @@ private extension AuthenticatedWebViewController {
144165 ] )
145166 }
146167
147- func startLoading ( ) {
168+ func observeWebView ( ) {
148169 webView. publisher ( for: \. estimatedProgress)
149170 . sink { [ weak self] progress in
150171 if progress == 1 {
@@ -157,42 +178,98 @@ private extension AuthenticatedWebViewController {
157178
158179 webView. publisher ( for: \. url)
159180 . sink { [ weak self] url in
160- guard let self else { return }
161- let initialURL = self . viewModel. initialURL
162- // avoids infinite loop if the initial url happens to be the nonce retrieval path.
163- if url? . absoluteString. contains ( WKWebView . wporgNoncePath) == true ,
164- initialURL? . absoluteString. contains ( WKWebView . wporgNoncePath) != true {
165- self . loadContent ( )
166- } else {
167- self . viewModel. handleRedirect ( for: url)
168- }
181+ guard let url else { return }
182+ self ? . handleRedirect ( for: url)
169183 }
170184 . store ( in: & subscriptions)
185+ }
171186
172- if let siteCredentials, let request = try ? webView. authenticateForWPOrg ( with: siteCredentials) {
173- webView. load ( request)
174- } else {
175- loadContent ( )
187+ /// Authentication logic differs depending on the destination URL and the current site.
188+ /// More information: pe5sF9-3Si-p2
189+ ///
190+ func startLoading( ) {
191+ guard let url = viewModel. initialURL else {
192+ return
193+ }
194+
195+ switch authenticationFlow {
196+ case . wpcom:
197+ authenticateWPComAndLoadContent ( url: url)
198+ case . jetpackSSO:
199+ authenticateSSOAndLoadContent ( url: url)
200+ case . siteCredentials:
201+ authenticateUsingSiteCredentialsAndLoadContent ( url: url)
202+ case . none:
203+ loadContent ( url: url)
176204 }
177205 }
178206}
179207
180208// MARK: - Helper methods
181209private extension AuthenticatedWebViewController {
182- func loadContent( ) {
183- guard let url = viewModel. initialURL else {
210+ func authenticateWPComAndLoadContent( url: URL ) {
211+ guard let wpcomCredentials, case . wpcom = wpcomCredentials else {
212+ return loadContent ( url: url)
213+ }
214+ do {
215+ try webView. authenticateForWPComAndRedirect ( to: url, credentials: wpcomCredentials)
216+ } catch {
217+ loadContent ( url: url)
218+ }
219+ }
220+
221+ func authenticateSSOAndLoadContent( url: URL ) {
222+ let tempURL = WooConstants . URLs. wpcomTempRedirectURL. asURL ( )
223+ authenticateWPComAndLoadContent ( url: tempURL)
224+ }
225+
226+ func authenticateUsingSiteCredentialsAndLoadContent( url: URL ) {
227+ guard let siteCredentials, let request = try ? webView. authenticateForWPOrg ( with: siteCredentials) else {
228+ return loadContent ( url: url)
229+ }
230+ webView. load ( request)
231+ }
232+
233+ func loadContent( url: URL ) {
234+ let request = URLRequest ( url: url)
235+ webView. load ( request)
236+ }
237+
238+ func handleRedirect( for url: URL ) {
239+ guard let initialURL = viewModel. initialURL else {
184240 return
185241 }
186242
187- /// Authenticate for WP.com automatically if credentials are available.
188- ///
189- if let wpcomCredentials, case . wpcom = wpcomCredentials {
190- webView. authenticateForWPComAndRedirect ( to: url, credentials: wpcomCredentials)
191- } else {
192- let request = URLRequest ( url: url)
193- webView. load ( request)
243+ switch url. absoluteString {
244+ case WooConstants . URLs. wpcomTempRedirectURL. rawValue:
245+ guard let currentSite, let host = URL ( string: currentSite. url) ? . host else {
246+ return loadContent ( url: initialURL)
247+ }
248+ let cookie = HTTPCookie ( properties: [
249+ . domain: host,
250+ . path: " / " ,
251+ . name: Constants . ssoRedirectCookieName,
252+ . value: initialURL. absoluteString,
253+ ] )
254+
255+ let queryItem = URLQueryItem ( name: Constants . actionParam, value: Constants . jetpackSSOAction)
256+ guard let cookie, let loginURL = URL ( string: currentSite. loginURL) ? . appending ( queryItems: [ queryItem] ) else {
257+ return loadContent ( url: initialURL)
258+ }
259+ webView. configuration. websiteDataStore. httpCookieStore. setCookie ( cookie)
260+ loadContent ( url: loginURL)
261+
262+ default :
263+ if url. absoluteString. contains ( WKWebView . wporgNoncePath) == true ,
264+ initialURL. absoluteString. contains ( WKWebView . wporgNoncePath) != true {
265+ // Site credentials login completes, now proceed to load the initial URL.
266+ loadContent ( url: initialURL)
267+ } else {
268+ viewModel. handleRedirect ( for: url)
269+ }
194270 }
195271 }
272+
196273}
197274
198275extension AuthenticatedWebViewController : WKNavigationDelegate {
@@ -204,7 +281,19 @@ extension AuthenticatedWebViewController: WKNavigationDelegate {
204281 }
205282
206283 func webView( _ webView: WKWebView , decidePolicyFor navigationResponse: WKNavigationResponse ) async -> WKNavigationResponsePolicy {
284+ defer {
285+ isFirstNavigation = false
286+ }
207287 let response = navigationResponse. response
288+ if let initialURL = viewModel. initialURL,
289+ viewModel. isAuthenticationFailure ( response: response,
290+ currentSite: currentSite,
291+ authenticationFlow: authenticationFlow,
292+ isFirstNavigation: isFirstNavigation) {
293+ /// When automatic authentication fails, cancel the navigation and redirect to the original URL instead.
294+ loadContent ( url: initialURL)
295+ return . cancel
296+ }
208297 return await viewModel. decidePolicy ( for: response)
209298 }
210299
@@ -244,3 +333,11 @@ extension AuthenticatedWebViewController: WKUIDelegate {
244333 return nil
245334 }
246335}
336+
337+ private extension AuthenticatedWebViewController {
338+ enum Constants {
339+ static let actionParam = " action "
340+ static let jetpackSSOAction = " jetpack-sso "
341+ static let ssoRedirectCookieName = " jetpack_sso_redirect_to "
342+ }
343+ }
0 commit comments