Skip to content

Commit ce39acf

Browse files
committed
Add WebKitViewController to handle authenticated sessions in webview
1 parent f9304bf commit ce39acf

File tree

8 files changed

+1572
-0
lines changed

8 files changed

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

0 commit comments

Comments
 (0)