Skip to content

Commit 8d7eafe

Browse files
authored
Merge pull request #7961 from woocommerce/issue/7911-webview-authentication
2 parents 804298c + 879f817 commit 8d7eafe

File tree

8 files changed

+1048
-0
lines changed

8 files changed

+1048
-0
lines changed
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
import AutomatticTracks
2+
import Foundation
3+
4+
/// Full copy of the same file from WP-iOS
5+
/// Couple of crash logging calls replaced with WC-iOS counterparts
6+
/// Consider moving it to WordPressAuthenticator lib
7+
/// https://github.com/wordpress-mobile/WordPress-iOS/blob/9b1e03b/WordPress/Classes/Services/AuthenticationService.swift
8+
9+
class AuthenticationService {
10+
11+
static let wpComLoginEndpoint = "https://wordpress.com/wp-login.php"
12+
13+
enum RequestAuthCookieError: Error, LocalizedError {
14+
case wpcomCookieNotReturned
15+
16+
public var errorDescription: String? {
17+
switch self {
18+
case .wpcomCookieNotReturned:
19+
return "Response to request for auth cookie for WP.com site failed to return cookie."
20+
}
21+
}
22+
}
23+
24+
// MARK: - Self Hosted
25+
26+
func loadAuthCookiesForSelfHosted(
27+
into cookieJar: CookieJar,
28+
loginURL: URL,
29+
username: String,
30+
password: String,
31+
success: @escaping () -> Void,
32+
failure: @escaping (Error) -> Void) {
33+
34+
cookieJar.hasWordPressSelfHostedAuthCookie(for: loginURL, username: username) { hasCookie in
35+
guard !hasCookie else {
36+
success()
37+
return
38+
}
39+
40+
self.getAuthCookiesForSelfHosted(loginURL: loginURL, username: username, password: password, success: { cookies in
41+
cookieJar.setCookies(cookies) {
42+
success()
43+
}
44+
45+
cookieJar.hasWordPressSelfHostedAuthCookie(for: loginURL, username: username) { hasCookie in
46+
print("Has cookie: \(hasCookie)")
47+
}
48+
}) { error in
49+
// Make sure this error scenario isn't silently ignored.
50+
ServiceLocator.crashLogging.logError(error)
51+
52+
// Even if getting the auth cookies fail, we'll still try to load the URL
53+
// so that the user sees a reasonable error situation on screen.
54+
// We could opt to create a special screen but for now I'd rather users report
55+
// the issue when it happens.
56+
failure(error)
57+
}
58+
}
59+
}
60+
61+
func getAuthCookiesForSelfHosted(
62+
loginURL: URL,
63+
username: String,
64+
password: String,
65+
success: @escaping (_ cookies: [HTTPCookie]) -> Void,
66+
failure: @escaping (Error) -> Void) {
67+
68+
let headers = [String: String]()
69+
let parameters = [
70+
"log": username,
71+
"pwd": password,
72+
"rememberme": "true"
73+
]
74+
75+
requestAuthCookies(
76+
from: loginURL,
77+
headers: headers,
78+
parameters: parameters,
79+
success: success,
80+
failure: failure)
81+
}
82+
83+
// MARK: - WP.com
84+
85+
func loadAuthCookiesForWPCom(
86+
into cookieJar: CookieJar,
87+
username: String,
88+
authToken: String,
89+
success: @escaping () -> Void,
90+
failure: @escaping (Error) -> Void) {
91+
92+
cookieJar.hasWordPressComAuthCookie(
93+
username: username,
94+
atomicSite: false) { hasCookie in
95+
96+
guard !hasCookie else {
97+
// The stored cookie can be stale but we'll try to use it and refresh it if the request fails.
98+
success()
99+
return
100+
}
101+
102+
self.getAuthCookiesForWPCom(username: username, authToken: authToken, success: { cookies in
103+
cookieJar.setCookies(cookies) {
104+
105+
cookieJar.hasWordPressComAuthCookie(username: username, atomicSite: false) { hasCookie in
106+
guard hasCookie else {
107+
failure(RequestAuthCookieError.wpcomCookieNotReturned)
108+
return
109+
}
110+
success()
111+
}
112+
113+
}
114+
}) { error in
115+
// Make sure this error scenario isn't silently ignored.
116+
ServiceLocator.crashLogging.logError(error)
117+
118+
// Even if getting the auth cookies fail, we'll still try to load the URL
119+
// so that the user sees a reasonable error situation on screen.
120+
// We could opt to create a special screen but for now I'd rather users report
121+
// the issue when it happens.
122+
failure(error)
123+
}
124+
}
125+
}
126+
127+
func getAuthCookiesForWPCom(
128+
username: String,
129+
authToken: String,
130+
success: @escaping (_ cookies: [HTTPCookie]) -> Void,
131+
failure: @escaping (Error) -> Void) {
132+
133+
let loginURL = URL(string: AuthenticationService.wpComLoginEndpoint)!
134+
let headers = [
135+
"Authorization": "Bearer \(authToken)"
136+
]
137+
let parameters = [
138+
"log": username,
139+
"rememberme": "true"
140+
]
141+
142+
requestAuthCookies(
143+
from: loginURL,
144+
headers: headers,
145+
parameters: parameters,
146+
success: success,
147+
failure: failure)
148+
}
149+
150+
// MARK: - Request Construction
151+
152+
private func requestAuthCookies(
153+
from url: URL,
154+
headers: [String: String],
155+
parameters: [String: String],
156+
success: @escaping (_ cookies: [HTTPCookie]) -> Void,
157+
failure: @escaping (Error) -> Void) {
158+
159+
// We don't want these cookies persisted in other sessions
160+
let session = URLSession(configuration: .ephemeral)
161+
var request = URLRequest(url: url)
162+
163+
request.httpMethod = "POST"
164+
request.httpBody = body(withParameters: parameters)
165+
166+
headers.forEach { (key, value) in
167+
request.setValue(value, forHTTPHeaderField: key)
168+
}
169+
// request.setValue(WPUserAgent.wordPress(), forHTTPHeaderField: "User-Agent")
170+
171+
let task = session.dataTask(with: request) { data, response, error in
172+
if let error = error {
173+
DispatchQueue.main.async {
174+
failure(error)
175+
}
176+
return
177+
}
178+
179+
// The following code is a bit complicated to read, apologies.
180+
// We're retrieving all cookies from the "Set-Cookie" header manually, and combining
181+
// those cookies with the ones from the current session. The reason behind this is that
182+
// iOS's URLSession processes the cookies from such header before this callback is executed,
183+
// whereas OHTTPStubs.framework doesn't (the cookies are left in the header fields of
184+
// the response). The only way to combine both is to just add them together here manually.
185+
//
186+
// To know if you can remove this, you'll have to test this code live and in our unit tests
187+
// and compare the session cookies.
188+
let responseCookies = self.cookies(from: response, loginURL: url)
189+
let cookies = (session.configuration.httpCookieStorage?.cookies ?? [HTTPCookie]()) + responseCookies
190+
DispatchQueue.main.async {
191+
success(cookies)
192+
}
193+
}
194+
195+
task.resume()
196+
}
197+
198+
private func body(withParameters parameters: [String: String]) -> Data? {
199+
var queryItems = [URLQueryItem]()
200+
201+
for parameter in parameters {
202+
let queryItem = URLQueryItem(name: parameter.key, value: parameter.value)
203+
queryItems.append(queryItem)
204+
}
205+
206+
var components = URLComponents()
207+
components.queryItems = queryItems
208+
209+
return components.percentEncodedQuery?.data(using: .utf8)
210+
}
211+
212+
// MARK: - Response Parsing
213+
214+
private func cookies(from response: URLResponse?, loginURL: URL) -> [HTTPCookie] {
215+
guard let httpResponse = response as? HTTPURLResponse,
216+
let headers = httpResponse.allHeaderFields as? [String: String] else {
217+
return []
218+
}
219+
220+
return HTTPCookie.cookies(withResponseHeaderFields: headers, for: loginURL)
221+
}
222+
}

0 commit comments

Comments
 (0)