diff --git a/.circleci/config.yml b/.circleci/config.yml index 014161baf..921e82d44 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2.1 orbs: # Using 1.0 of our Orbs means it will use the latest 1.0.x version from https://github.com/wordpress-mobile/circleci-orbs - ios: wordpress-mobile/ios@1.0 + ios: wordpress-mobile/ios@1.0 workflows: test_and_validate: diff --git a/Gemfile b/Gemfile index 09c4d6ef1..ce0be1912 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,3 @@ source 'https://rubygems.org' do - gem 'cocoapods', '~> 1.8.0' + gem 'cocoapods', '~> 1.8.4' end diff --git a/Gemfile.lock b/Gemfile.lock index 56987e885..dfcd07a02 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -12,10 +12,10 @@ GEM json (>= 1.5.1) atomos (0.1.3) claide (1.0.3) - cocoapods (1.8.3) + cocoapods (1.8.4) activesupport (>= 4.0.2, < 5) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.8.3) + cocoapods-core (= 1.8.4) cocoapods-deintegrate (>= 1.0.3, < 2.0) cocoapods-downloader (>= 1.2.2, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) @@ -31,14 +31,14 @@ GEM nap (~> 1.0) ruby-macho (~> 1.4) xcodeproj (>= 1.11.1, < 2.0) - cocoapods-core (1.8.3) + cocoapods-core (1.8.4) activesupport (>= 4.0.2, < 6) algoliasearch (~> 1.0) concurrent-ruby (~> 1.1) fuzzy_match (~> 2.0.4) nap (~> 1.0) cocoapods-deintegrate (1.0.4) - cocoapods-downloader (1.2.2) + cocoapods-downloader (1.3.0) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.0) @@ -57,7 +57,7 @@ GEM i18n (0.9.5) concurrent-ruby (~> 1.0) json (2.2.0) - minitest (5.12.2) + minitest (5.13.0) molinillo (0.6.6) nanaimo (0.2.6) nap (1.1.0) @@ -66,7 +66,7 @@ GEM thread_safe (0.3.6) tzinfo (1.2.5) thread_safe (~> 0.1) - xcodeproj (1.12.0) + xcodeproj (1.13.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) @@ -77,7 +77,7 @@ PLATFORMS ruby DEPENDENCIES - cocoapods (~> 1.8.0)! + cocoapods (~> 1.8.4)! BUNDLED WITH 2.0.2 diff --git a/Podfile b/Podfile index 1a3aef173..11d59e25d 100644 --- a/Podfile +++ b/Podfile @@ -11,7 +11,8 @@ def wordpress_authenticator_pods ## pod 'Gridicons', '~> 0.15' pod 'WordPressUI', '~> 1.4-beta.1' - pod 'WordPressKit', '~> 4.5.1' + pod 'WordPressKit', '~> 4.5.4' + # pod 'WordPressKit', :git => 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', :branch => 'issue/apple_2fa_auth' pod 'WordPressShared', '~> 1.8' ## Third party libraries diff --git a/Podfile.lock b/Podfile.lock index 65eb9b9ec..bfc1f8236 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -43,12 +43,12 @@ PODS: - OHHTTPStubs/Default - Specta (1.0.7) - SVProgressHUD (2.2.5) - - UIDeviceIdentifier (1.1.4) - - WordPressKit (4.5.1): + - UIDeviceIdentifier (1.4.0) + - WordPressKit (4.5.4): - Alamofire (~> 4.7.3) - CocoaLumberjack (~> 3.4) - NSObject-SafeExpectations (= 0.0.3) - - UIDeviceIdentifier (~> 1.1.4) + - UIDeviceIdentifier (~> 1) - WordPressShared (~> 1.8.0) - wpxmlrpc (= 0.8.4) - WordPressShared (1.8.7): @@ -71,7 +71,7 @@ DEPENDENCIES: - OHHTTPStubs/Swift (= 8.0.0) - Specta (= 1.0.7) - SVProgressHUD (= 2.2.5) - - WordPressKit (~> 4.5.1) + - WordPressKit (~> 4.5.4) - WordPressShared (~> 1.8) - WordPressUI (~> 1.4-beta.1) @@ -116,12 +116,12 @@ SPEC CHECKSUMS: OHHTTPStubs: 9cbce6364bec557cc3439aa6bb7514670d780881 Specta: 3e1bd89c3517421982dc4d1c992503e48bd5fe66 SVProgressHUD: 1428aafac632c1f86f62aa4243ec12008d7a51d6 - UIDeviceIdentifier: 8f8a24b257a4d978c8d40ad1e7355b944ffbfa8c - WordPressKit: c35230114bbd380d63250b6d9a43337c29266c9b + UIDeviceIdentifier: 44f805037d21b94394821828f4fcaba34b38c2d0 + WordPressKit: 1d365775fac17903a76ad5723bc146ab1739ec82 WordPressShared: 09cf184caa614835f5811e8609227165201e6d3e WordPressUI: 35b144885c8e5817ba6874b68accc200bda61ee1 wpxmlrpc: 6ba55c773cfa27083ae4a2173e69b19f46da98e2 -PODFILE CHECKSUM: 831117be8f1a447aaa2277d2b421a8e64fe6f02c +PODFILE CHECKSUM: 6f930e58860031b74be490800e49e91a4f3cd75f -COCOAPODS: 1.8.3 +COCOAPODS: 1.8.4 diff --git a/WordPressAuthenticator.podspec b/WordPressAuthenticator.podspec index f8be1677c..2777d5b21 100644 --- a/WordPressAuthenticator.podspec +++ b/WordPressAuthenticator.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "WordPressAuthenticator" - s.version = "1.10.3" + s.version = "1.10.4" s.summary = "WordPressAuthenticator implements an easy and elegant way to authenticate your WordPress Apps." s.description = <<-DESC @@ -39,6 +39,6 @@ Pod::Spec.new do |s| s.dependency 'Gridicons', '~> 0.15' s.dependency 'GoogleSignIn', '~> 4.4' s.dependency 'WordPressUI', '~> 1.4-beta.1' - s.dependency 'WordPressKit', '~> 4.5.1' + s.dependency 'WordPressKit', '~> 4.5.4' s.dependency 'WordPressShared', '~> 1.8' end diff --git a/WordPressAuthenticator/Services/LoginFacade.h b/WordPressAuthenticator/Services/LoginFacade.h index f3705c7b7..f484255b4 100644 --- a/WordPressAuthenticator/Services/LoginFacade.h +++ b/WordPressAuthenticator/Services/LoginFacade.h @@ -58,11 +58,12 @@ NS_ASSUME_NONNULL_BEGIN - (void)requestSocial2FACodeWithLoginFields:(LoginFields *)loginFields; /** - * Social login via google. + * Social login. * - * @param googleIDToken A Google id_token. + * @param token Social id token. */ -- (void)loginToWordPressDotComWithGoogleIDToken:(NSString *)googleIDToken; +- (void)loginToWordPressDotComWithSocialIDToken:(NSString *)token + service:(NSString *)service; /** * Social login via a social account with 2FA using a nonce. diff --git a/WordPressAuthenticator/Services/LoginFacade.m b/WordPressAuthenticator/Services/LoginFacade.m index 3c806cdfc..8a0e3d0dc 100644 --- a/WordPressAuthenticator/Services/LoginFacade.m +++ b/WordPressAuthenticator/Services/LoginFacade.m @@ -63,21 +63,26 @@ - (void)requestSocial2FACodeWithLoginFields:(LoginFields *)loginFields }]; } -- (void)loginToWordPressDotComWithGoogleIDToken:(NSString *)googleIDToken +- (void)loginToWordPressDotComWithSocialIDToken:(NSString *)token + service:(NSString *)service { if ([self.delegate respondsToSelector:@selector(displayLoginMessage:)]) { [self.delegate displayLoginMessage:NSLocalizedString(@"Connecting to WordPress.com", nil)]; } - [self.wordpressComOAuthClientFacade authenticateWithGoogleIDToken:googleIDToken success:^(NSString *authToken) { - if ([self.delegate respondsToSelector:@selector(finishedLoginWithGoogleIDToken:authToken:)]) { - [self.delegate finishedLoginWithGoogleIDToken:googleIDToken authToken:authToken]; + [self.wordpressComOAuthClientFacade authenticateWithSocialIDToken:token + service:service + success:^(NSString *authToken) { + if ([service isEqualToString:@"google"] && [self.delegate respondsToSelector:@selector(finishedLoginWithGoogleIDToken:authToken:)]) { + // Apple is handled in AppleAuthenticator + [self.delegate finishedLoginWithGoogleIDToken:token authToken:authToken]; } } needsMultiFactor:^(NSInteger userID, SocialLogin2FANonceInfo *nonceInfo){ if ([self.delegate respondsToSelector:@selector(needsMultifactorCodeForUserID:andNonceInfo:)]) { [self.delegate needsMultifactorCodeForUserID:userID andNonceInfo:nonceInfo]; } } existingUserNeedsConnection: ^(NSString *email) { + // Apple is handled in AppleAuthenticator if ([self.delegate respondsToSelector:@selector(existingUserNeedsConnection:)]) { [self.delegate existingUserNeedsConnection: email]; } diff --git a/WordPressAuthenticator/Services/SignupService.swift b/WordPressAuthenticator/Services/SignupService.swift index 3fd5c391c..9662cb22b 100644 --- a/WordPressAuthenticator/Services/SignupService.swift +++ b/WordPressAuthenticator/Services/SignupService.swift @@ -51,7 +51,11 @@ class SignupService { func createWPComUserWithApple(token: String, email: String, fullName: String?, - success: @escaping (_ newAccount: Bool, _ existingNonSocialAccount: Bool, _ username: String, _ wpcomToken: String) -> Void, + success: @escaping (_ newAccount: Bool, + _ existingNonSocialAccount: Bool, + _ existing2faAccount: Bool, + _ username: String, + _ wpcomToken: String) -> Void, failure: @escaping (_ error: Error) -> Void) { let remote = WordPressComServiceRemote(wordPressComRestApi: anonymousAPI) @@ -68,22 +72,27 @@ class SignupService { } let createdAccount = (response?[ResponseKeys.createdAccount] as? Int ?? 0) == 1 - success(createdAccount, false, username, bearer_token) + success(createdAccount, false, false, username, bearer_token) }, failure: { error in if let error = (error as NSError?) { - // If an account already exists, the account email should be returned in the Error response. - // Extract it and return it. - var existingEmail = "" - if let errorData = error.userInfo[WordPressComRestApi.ErrorKeyErrorData] as? [String: String] { - let emailDict = errorData.first { $0.key == WordPressComRestApi.ErrorKeyErrorDataEmail } - let email = emailDict?.value ?? "" - existingEmail = email + if (error.userInfo[ErrorKeys.errorCode] as? String ?? "") == ErrorKeys.twoFactorEnabled { + success(false, true, true, "", "") + return } - let existingNonSocialAccount = (error.userInfo[ErrorKeys.errorCode] as? String ?? "") == ErrorKeys.existingNonSocialUser - if existingNonSocialAccount { - success(false, true, existingEmail, "") + if (error.userInfo[ErrorKeys.errorCode] as? String ?? "") == ErrorKeys.existingNonSocialUser { + + // If an account already exists, the account email should be returned in the Error response. + // Extract it and return it. + var existingEmail = "" + if let errorData = error.userInfo[WordPressComRestApi.ErrorKeyErrorData] as? [String: String] { + let emailDict = errorData.first { $0.key == WordPressComRestApi.ErrorKeyErrorDataEmail } + let email = emailDict?.value ?? "" + existingEmail = email + } + + success(false, true, false, existingEmail, "") return } } @@ -118,6 +127,7 @@ private extension SignupService { struct ErrorKeys { static let errorCode = "WordPressComRestApiErrorCodeKey" static let existingNonSocialUser = "user_exists" + static let twoFactorEnabled = "2FA_enabled" } } diff --git a/WordPressAuthenticator/Services/WordPressComOAuthClientFacade.h b/WordPressAuthenticator/Services/WordPressComOAuthClientFacade.h index 70f14137d..cf1ef89d9 100644 --- a/WordPressAuthenticator/Services/WordPressComOAuthClientFacade.h +++ b/WordPressAuthenticator/Services/WordPressComOAuthClientFacade.h @@ -22,7 +22,8 @@ success:(void (^)(NSString *newNonce))success failure:(void (^)(NSError *error, NSString *newNonce))failure; -- (void)authenticateWithGoogleIDToken:(NSString *)token +- (void)authenticateWithSocialIDToken:(NSString *)token + service:(NSString *)service success:(void (^)(NSString *authToken))success needsMultiFactor:(void (^)(NSInteger userID, SocialLogin2FANonceInfo *nonceInfo))needsMultifactor existingUserNeedsConnection:(void (^)(NSString *email))existingUserNeedsConnection diff --git a/WordPressAuthenticator/Services/WordPressComOAuthClientFacade.m b/WordPressAuthenticator/Services/WordPressComOAuthClientFacade.m index fffb702f7..adb83beab 100644 --- a/WordPressAuthenticator/Services/WordPressComOAuthClientFacade.m +++ b/WordPressAuthenticator/Services/WordPressComOAuthClientFacade.m @@ -68,13 +68,19 @@ - (void)requestSocial2FACodeWithUserID:(NSInteger)userID [self.client requestSocial2FACodeWithUserID:userID nonce:nonce success:success failure:failure]; } -- (void)authenticateWithGoogleIDToken:(NSString *)token +- (void)authenticateWithSocialIDToken:(NSString *)token + service:(NSString *)service success:(void (^)(NSString *authToken))success needsMultiFactor:(void (^)(NSInteger userID, SocialLogin2FANonceInfo *nonceInfo))needsMultifactor existingUserNeedsConnection:(void (^)(NSString *email))existingUserNeedsConnection failure:(void (^)(NSError *error))failure { - [self.client authenticateWithIDToken:token success:success needsMultifactor:needsMultifactor existingUserNeedsConnection:existingUserNeedsConnection failure:failure]; + [self.client authenticateWithIDToken:token + service:service + success:success + needsMultifactor:needsMultifactor + existingUserNeedsConnection:existingUserNeedsConnection + failure:failure]; } - (void)authenticateSocialLoginUser:(NSInteger)userID diff --git a/WordPressAuthenticator/Signin/AppleAuthenticator.swift b/WordPressAuthenticator/Signin/AppleAuthenticator.swift index 26ed4d8b4..28b78b4e4 100644 --- a/WordPressAuthenticator/Signin/AppleAuthenticator.swift +++ b/WordPressAuthenticator/Signin/AppleAuthenticator.swift @@ -5,6 +5,7 @@ import SVProgressHUD @objc protocol AppleAuthenticatorDelegate { func showWPComLogin(loginFields: LoginFields) + func showApple2FA(loginFields: LoginFields) func authFailedWithError(message: String) } @@ -75,13 +76,23 @@ private extension AppleAuthenticator { let service = SignupService() service.createWPComUserWithApple(token: token, email: email, fullName: name, - success: { [weak self] accountCreated, existingNonSocialAccount, wpcomUsername, wpcomToken in + success: { [weak self] accountCreated, + existingNonSocialAccount, + existing2faAccount, + wpcomUsername, + wpcomToken in SVProgressHUD.dismiss() // Notify host app of successful Apple authentication self?.authenticationDelegate.userAuthenticatedWithAppleUserID(appleCredentials.user) guard !existingNonSocialAccount else { + + if existing2faAccount { + self?.show2FA() + return + } + self?.updateLoginEmail(wpcomUsername) self?.logInInstead() return @@ -154,6 +165,11 @@ private extension AppleAuthenticator { delegate?.showWPComLogin(loginFields: loginFields) } + func show2FA() { + WordPressAuthenticator.track(.signupSocialToLogin, properties: ["source": "apple"]) + delegate?.showApple2FA(loginFields: loginFields) + } + // MARK: - Helpers func fullName(from components: PersonNameComponents?) -> String { diff --git a/WordPressAuthenticator/Signin/Login2FAViewController.swift b/WordPressAuthenticator/Signin/Login2FAViewController.swift index e83911db9..9f0a840b8 100644 --- a/WordPressAuthenticator/Signin/Login2FAViewController.swift +++ b/WordPressAuthenticator/Signin/Login2FAViewController.swift @@ -180,7 +180,13 @@ class Login2FAViewController: LoginViewController, NUXKeyboardResponder, UITextF GIDSignIn.sharedInstance().disconnect() WordPressAuthenticator.track(.signedIn) - WordPressAuthenticator.track(.loginSocialSuccess) + + var properties = [AnyHashable:Any]() + if let service = loginFields.meta.socialService?.rawValue { + properties["source"] = service + } + + WordPressAuthenticator.track(.loginSocialSuccess, properties: properties) } /// Only allow digits in the 2FA text field diff --git a/WordPressAuthenticator/Signin/LoginEmailViewController.swift b/WordPressAuthenticator/Signin/LoginEmailViewController.swift index 99ebceb63..5cf2ef07a 100644 --- a/WordPressAuthenticator/Signin/LoginEmailViewController.swift +++ b/WordPressAuthenticator/Signin/LoginEmailViewController.swift @@ -394,13 +394,10 @@ open class LoginEmailViewController: LoginViewController, NUXKeyboardResponder { if let vc = segue.destination as? LoginPrologueSignupMethodViewController { vc.transitioningDelegate = self vc.emailTapped = { [weak self] in - self?.performSegue(withIdentifier: NUXViewController.SegueIdentifier.showSigninV2.rawValue, sender: self) + self?.performSegue(withIdentifier: .showSigninV2, sender: self) } vc.googleTapped = { [weak self] in - self?.performSegue(withIdentifier: NUXViewController.SegueIdentifier.showGoogle.rawValue, sender: self) - } - vc.appleTapped = { [weak self] in - self?.appleTapped() + self?.performSegue(withIdentifier: .showGoogle, sender: self) } vc.modalPresentationStyle = .custom } @@ -493,25 +490,6 @@ open class LoginEmailViewController: LoginViewController, NUXKeyboardResponder { } } - private func appleTapped() { - AppleAuthenticator.sharedInstance.delegate = self - AppleAuthenticator.sharedInstance.showFrom(viewController: self) - } -} - -// MARK: - AppleAuthenticatorDelegate - -extension LoginEmailViewController: AppleAuthenticatorDelegate { - - func showWPComLogin(loginFields: LoginFields) { - self.loginFields = loginFields - performSegue(withIdentifier: .showWPComLogin, sender: self) - } - - func authFailedWithError(message: String) { - displayErrorAlert(message, sourceTag: .wpComSignupApple) - } - } // MARK: - Google Sign In @@ -529,7 +507,7 @@ extension LoginEmailViewController { func needsMultifactorCode(forUserID userID: Int, andNonceInfo nonceInfo: SocialLogin2FANonceInfo) { configureViewLoading(false) - googleNeedsMultifactorCode(forUserID: userID, andNonceInfo: nonceInfo) + socialNeedsMultifactorCode(forUserID: userID, andNonceInfo: nonceInfo) } } diff --git a/WordPressAuthenticator/Signin/LoginPrologueViewController.swift b/WordPressAuthenticator/Signin/LoginPrologueViewController.swift index 9cfa987d6..9c65d6cf7 100644 --- a/WordPressAuthenticator/Signin/LoginPrologueViewController.swift +++ b/WordPressAuthenticator/Signin/LoginPrologueViewController.swift @@ -135,15 +135,19 @@ extension LoginPrologueViewController: AppleAuthenticatorDelegate { performSegue(withIdentifier: .showWPComLogin, sender: self) } + func showApple2FA(loginFields: LoginFields) { + self.loginFields = loginFields + signInAppleAccount() + } + func authFailedWithError(message: String) { displayErrorAlert(message, sourceTag: .loginApple) } } -// MARK: - Google Sign In +// MARK: - Social LoginFacadeDelegate Methods -// LoginFacadeDelegate methods for Google Google Sign In extension LoginPrologueViewController { override open func displayRemoteError(_ error: Error) { @@ -162,11 +166,13 @@ extension LoginPrologueViewController { func needsMultifactorCode(forUserID userID: Int, andNonceInfo nonceInfo: SocialLogin2FANonceInfo) { configureViewLoading(false) - googleNeedsMultifactorCode(forUserID: userID, andNonceInfo: nonceInfo) + socialNeedsMultifactorCode(forUserID: userID, andNonceInfo: nonceInfo) } } +// MARK: - GIDSignInDelegate + extension LoginPrologueViewController: GIDSignInDelegate { open func sign(_ signIn: GIDSignIn?, didSignInFor user: GIDGoogleUser?, withError error: Error?) { signInGoogleAccount(signIn, didSignInFor: user, withError: error) diff --git a/WordPressAuthenticator/Signin/LoginSiteAddressViewController.swift b/WordPressAuthenticator/Signin/LoginSiteAddressViewController.swift index 4f3c85f87..b878083c7 100644 --- a/WordPressAuthenticator/Signin/LoginSiteAddressViewController.swift +++ b/WordPressAuthenticator/Signin/LoginSiteAddressViewController.swift @@ -372,6 +372,11 @@ extension LoginSiteAddressViewController: AppleAuthenticatorDelegate { self.loginFields = loginFields performSegue(withIdentifier: .showWPComLogin, sender: self) } + + func showApple2FA(loginFields: LoginFields) { + self.loginFields = loginFields + signInAppleAccount() + } func authFailedWithError(message: String) { displayErrorAlert(message, sourceTag: .loginApple) diff --git a/WordPressAuthenticator/Signin/LoginViewController.swift b/WordPressAuthenticator/Signin/LoginViewController.swift index 1e9ec1bf0..a41528fda 100644 --- a/WordPressAuthenticator/Signin/LoginViewController.swift +++ b/WordPressAuthenticator/Signin/LoginViewController.swift @@ -416,12 +416,28 @@ extension LoginViewController { configureViewLoading(false) } - func googleNeedsMultifactorCode(forUserID userID: Int, andNonceInfo nonceInfo: SocialLogin2FANonceInfo) { + func socialNeedsMultifactorCode(forUserID userID: Int, andNonceInfo nonceInfo: SocialLogin2FANonceInfo) { loginFields.nonceInfo = nonceInfo loginFields.nonceUserID = userID performSegue(withIdentifier: .show2FA, sender: self) - WordPressAuthenticator.track(.loginSocial2faNeeded, properties: ["source": "google"]) + + var properties = [AnyHashable:Any]() + if let service = loginFields.meta.socialService?.rawValue { + properties["source"] = service + } + + WordPressAuthenticator.track(.loginSocial2faNeeded, properties: properties) + } + + func signInAppleAccount() { + guard let token = loginFields.meta.socialServiceIDToken else { + WordPressAuthenticator.track(.loginSocialButtonFailure, properties: ["source": SocialServiceName.apple.rawValue]) + configureViewLoading(false) + return + } + + loginFacade.loginToWordPressDotCom(withSocialIDToken: token, service: SocialServiceName.apple.rawValue) } func signInGoogleAccount(_ signIn: GIDSignIn?, didSignInFor user: GIDGoogleUser?, withError error: Error?) { @@ -429,13 +445,17 @@ extension LoginViewController { let token = user.authentication.idToken, let email = user.profile.email else { // The Google SignIn for may have been canceled. - WordPressAuthenticator.track(.loginSocialButtonFailure, error: error) + + let properties = ["error": error?.localizedDescription, + "source": SocialServiceName.google.rawValue] + + WordPressAuthenticator.track(.loginSocialButtonFailure, properties: properties as [AnyHashable : Any]) configureViewLoading(false) return } updateLoginFields(googleUser: user, googleToken: token, googleEmail: email) - loginFacade.loginToWordPressDotCom(withGoogleIDToken: token) + loginFacade.loginToWordPressDotCom(withSocialIDToken: token, service: SocialServiceName.google.rawValue) } /// Updates the LoginFields structure, with the specified Google User + Token + Email.