-
Notifications
You must be signed in to change notification settings - Fork 116
/
Copy pathAuthenticationManager.swift
319 lines (274 loc) · 15.6 KB
/
AuthenticationManager.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
import Foundation
import KeychainAccess
import WordPressAuthenticator
import Yosemite
import class Networking.UserAgent
import struct Networking.Settings
/// Encapsulates all of the interactions with the WordPress Authenticator
///
class AuthenticationManager: Authentication {
/// Store Picker Coordinator
///
private var storePickerCoordinator: StorePickerCoordinator?
/// Keychain access for SIWA auth token
///
private lazy var keychain = Keychain(service: WooConstants.keychainServiceName)
/// Apple ID is temporarily stored in memory until we can save it to Keychain when the authentication is complete.
///
private var appleUserID: String?
/// Initializes the WordPress Authenticator.
///
func initialize() {
let isSignInWithAppleEnabled: Bool
if #available(iOS 13.0, *) {
isSignInWithAppleEnabled = true
} else {
isSignInWithAppleEnabled = false
}
let configuration = WordPressAuthenticatorConfiguration(wpcomClientId: ApiCredentials.dotcomAppId,
wpcomSecret: ApiCredentials.dotcomSecret,
wpcomScheme: ApiCredentials.dotcomAuthScheme,
wpcomTermsOfServiceURL: WooConstants.URLs.termsOfService.rawValue,
wpcomAPIBaseURL: Settings.wordpressApiBaseURL,
googleLoginClientId: ApiCredentials.googleClientId,
googleLoginServerClientId: ApiCredentials.googleServerId,
googleLoginScheme: ApiCredentials.googleAuthScheme,
userAgent: UserAgent.defaultUserAgent,
showLoginOptions: true,
enableSignUp: false,
enableSignInWithApple: isSignInWithAppleEnabled)
let systemGray3LightModeColor = UIColor(red: 199/255.0, green: 199/255.0, blue: 204/255.0, alpha: 1)
let systemLabelLightModeColor = UIColor(red: 0, green: 0, blue: 0, alpha: 1)
let style = WordPressAuthenticatorStyle(primaryNormalBackgroundColor: .primaryButtonBackground,
primaryNormalBorderColor: .primaryButtonDownBackground,
primaryHighlightBackgroundColor: .primaryButtonDownBackground,
primaryHighlightBorderColor: .primaryButtonDownBorder,
secondaryNormalBackgroundColor: .white,
secondaryNormalBorderColor: systemGray3LightModeColor,
secondaryHighlightBackgroundColor: systemGray3LightModeColor,
secondaryHighlightBorderColor: systemGray3LightModeColor,
disabledBackgroundColor: .buttonDisabledBackground,
disabledBorderColor: .gray(.shade30),
primaryTitleColor: .primaryButtonTitle,
secondaryTitleColor: systemLabelLightModeColor,
disabledTitleColor: .textSubtle,
textButtonColor: .accent,
textButtonHighlightColor: .accentDark,
instructionColor: .textSubtle,
subheadlineColor: .gray(.shade30),
placeholderColor: .placeholderImage,
viewControllerBackgroundColor: .listBackground,
textFieldBackgroundColor: .listForeground,
buttonViewBackgroundColor: .authPrologueBottomBackgroundColor,
buttonViewTopShadowImage: nil,
navBarImage: StyleManager.navBarImage,
navBarBadgeColor: .primary,
navBarBackgroundColor: .appBar,
prologueTopContainerChildViewController: LoginPrologueViewController(),
statusBarStyle: .default)
let displayStrings = WordPressAuthenticatorDisplayStrings(emailLoginInstructions: AuthenticationConstants.emailInstructions,
jetpackLoginInstructions: AuthenticationConstants.jetpackInstructions,
siteLoginInstructions: AuthenticationConstants.siteInstructions)
let unifiedStyle = WordPressAuthenticatorUnifiedStyle(borderColor: .divider,
errorColor: .error,
textColor: .text,
textSubtleColor: .textSubtle,
textButtonColor: .brand,
textButtonHighlightColor: .brand,
viewControllerBackgroundColor: .basicBackground,
navBarBackgroundColor: .basicBackground,
navButtonTextColor: .brand,
navTitleTextColor: .text)
let displayImages = WordPressAuthenticatorDisplayImages(
magicLink: .loginMagicLinkImage,
siteAddressModalPlaceholder: .loginSiteAddressInfoImage
)
WordPressAuthenticator.initialize(configuration: configuration,
style: style,
unifiedStyle: unifiedStyle,
displayImages: displayImages,
displayStrings: displayStrings)
WordPressAuthenticator.shared.delegate = self
}
/// Displays the Login Flow using the specified UIViewController as presenter.
///
func displayAuthentication(from presenter: UIViewController, animated: Bool, onCompletion: @escaping () -> Void) {
WordPressAuthenticator.showLogin(from: presenter, animated: animated, onLoginButtonTapped: { [weak self] in
guard let self = self else { return }
// Resets Apple ID at the beginning of the authentication.
self.appleUserID = nil
ServiceLocator.analytics.track(.loginPrologueContinueTapped)
}, onCompletion: onCompletion)
}
/// Handles an Authentication URL Callback. Returns *true* on success.
///
func handleAuthenticationUrl(_ url: URL, options: [UIApplication.OpenURLOptionsKey: Any], rootViewController: UIViewController) -> Bool {
let source = options[.sourceApplication] as? String
let annotation = options[.annotation]
if WordPressAuthenticator.shared.isGoogleAuthUrl(url) {
return WordPressAuthenticator.shared.handleGoogleAuthUrl(url, sourceApplication: source, annotation: annotation)
}
if WordPressAuthenticator.shared.isWordPressAuthUrl(url) {
return WordPressAuthenticator.shared.handleWordPressAuthUrl(url, allowWordPressComAuth: true, rootViewController: rootViewController)
}
return false
}
}
// MARK: - WordPressAuthenticator Delegate
//
extension AuthenticationManager: WordPressAuthenticatorDelegate {
func userAuthenticatedWithAppleUserID(_ appleUserID: String) {
self.appleUserID = appleUserID
}
var allowWPComLogin: Bool {
return true
}
/// Indicates if the active Authenticator can be dismissed or not.
///
var dismissActionEnabled: Bool {
// TODO: Return *true* only if there is no default account already set.
return false
}
/// Indicates whether if the Support Action should be enabled, or not.
///
var supportActionEnabled: Bool {
return true
}
/// Indicates if Support is Enabled.
///
var supportEnabled: Bool {
return ZendeskManager.shared.zendeskEnabled
}
/// Indicates if the Support notification indicator should be displayed.
///
var showSupportNotificationIndicator: Bool {
// TODO: Wire Zendesk
return false
}
/// Executed whenever a new WordPress.com account has been created.
/// Note: As of now, this is a NO-OP, we're not supporting any signup flows.
///
func createdWordPressComAccount(username: String, authToken: String) { }
/// Validates that the self-hosted site contains the correct information
/// and can proceed to the self-hosted username and password view controller.
///
func shouldPresentUsernamePasswordController(for siteInfo: WordPressComSiteInfo?, onCompletion: @escaping (Error?, Bool) -> Void) {
let isSelfHosted = false
guard let site = siteInfo, site.hasJetpack == true else {
let errorInfo = NSLocalizedString(
"Looks like your site isn't set up to use this app. Make sure your site has Jetpack installed to continue.",
comment: "Error message that displays on the 'Log in by entering your site address.' screen. " +
"Jetpack is required for logging into the WooCommerce mobile apps.")
let error = NSError(domain: "WooCommerceAuthenticationErrorDomain",
code: 555,
userInfo: [NSLocalizedDescriptionKey: errorInfo])
onCompletion(error, isSelfHosted)
return
}
onCompletion(nil, isSelfHosted)
}
/// Presents the Login Epilogue, in the specified NavigationController.
///
func presentLoginEpilogue(in navigationController: UINavigationController, for credentials: AuthenticatorCredentials, onDismiss: @escaping () -> Void) {
storePickerCoordinator = StorePickerCoordinator(navigationController, config: .login)
storePickerCoordinator?.onDismiss = onDismiss
storePickerCoordinator?.start()
}
/// Presents the Signup Epilogue, in the specified NavigationController.
///
func presentSignupEpilogue(in navigationController: UINavigationController, for credentials: AuthenticatorCredentials, service: SocialService?) {
// NO-OP: The current WC version does not support Signup. Let SIWA through.
guard case .apple = service else {
return
}
// For SIWA, signups are treating like signing in for now.
// Signup code in Authenticator normally synchronizes the auth credentials but
// since we're hacking in SIWA, that's never called in the pod. Call here so the
// person's name and user ID show up on the picker screen.
//
// This is effectively a useless screen for them other than telling them to install Jetpack.
sync(credentials: credentials) { [weak self] in
self?.storePickerCoordinator = StorePickerCoordinator(navigationController, config: .login)
self?.storePickerCoordinator?.start()
}
}
/// Presents the Support Interface from a given ViewController, with a specified SourceTag.
///
func presentSupport(from sourceViewController: UIViewController, sourceTag: WordPressSupportSourceTag) {
let identifier = HelpAndSupportViewController.classNameWithoutNamespaces
guard let supportViewController = UIStoryboard.dashboard.instantiateViewController(withIdentifier: identifier) as? HelpAndSupportViewController else {
return
}
supportViewController.displaysDismissAction = true
let navController = UINavigationController(rootViewController: supportViewController)
navController.modalPresentationStyle = .formSheet
sourceViewController.present(navController, animated: true, completion: nil)
}
/// Presents the Support new request, from a given ViewController, with a specified SourceTag.
///
func presentSupportRequest(from sourceViewController: UIViewController, sourceTag: WordPressSupportSourceTag) {
ZendeskManager.shared.showNewRequestIfPossible(from: sourceViewController, with: sourceTag.name)
}
/// Indicates if the Login Epilogue should be presented.
///
func shouldPresentLoginEpilogue(isJetpackLogin: Bool) -> Bool {
return true
}
/// Indicates if the Signup Epilogue should be displayed.
/// Note: As of now, this is a NO-OP, we're not supporting any signup flows.
///
func shouldPresentSignupEpilogue() -> Bool {
return false
}
/// Synchronizes the specified WordPress Account.
///
func sync(credentials: AuthenticatorCredentials, onCompletion: @escaping () -> Void) {
guard let wpcom = credentials.wpcom else {
fatalError("Self Hosted sites are not supported. Please review the Authenticator settings!")
}
// If Apple ID is previously set, saves it to Keychain now that authentication is complete.
if let appleUserID = appleUserID {
keychain.wooAppleID = appleUserID
}
appleUserID = nil
ServiceLocator.stores.authenticate(credentials: .init(authToken: wpcom.authToken))
let action = AccountAction.synchronizeAccount { (account, error) in
if let account = account {
let credentials = Credentials(username: account.username, authToken: wpcom.authToken, siteAddress: wpcom.siteURL)
ServiceLocator.stores
.authenticate(credentials: credentials)
.synchronizeEntities(onCompletion: onCompletion)
} else {
ServiceLocator.stores.synchronizeEntities(onCompletion: onCompletion)
}
}
ServiceLocator.stores.dispatch(action)
}
/// Tracks a given Analytics Event.
///
func track(event: WPAnalyticsStat) {
guard let wooEvent = WooAnalyticsStat.valueOf(stat: event) else {
DDLogWarn("⚠️ Could not convert WPAnalyticsStat with value: \(event.rawValue)")
return
}
ServiceLocator.analytics.track(wooEvent)
}
/// Tracks a given Analytics Event, with the specified properties.
///
func track(event: WPAnalyticsStat, properties: [AnyHashable: Any]) {
guard let wooEvent = WooAnalyticsStat.valueOf(stat: event) else {
DDLogWarn("⚠️ Could not convert WPAnalyticsStat with value: \(event.rawValue)")
return
}
ServiceLocator.analytics.track(wooEvent, withProperties: properties)
}
/// Tracks a given Analytics Event, with the specified error.
///
func track(event: WPAnalyticsStat, error: Error) {
guard let wooEvent = WooAnalyticsStat.valueOf(stat: event) else {
DDLogWarn("⚠️ Could not convert WPAnalyticsStat with value: \(event.rawValue)")
return
}
ServiceLocator.analytics.track(wooEvent, withError: error)
}
}