nicholss | c372b335 | 2017-05-09 17:17:33 | [diff] [blame] | 1 | // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #if !defined(__has_feature) || !__has_feature(objc_arc) |
| 6 | #error "This file requires ARC support." |
| 7 | #endif |
| 8 | |
yuweih | c5bfa5e2 | 2017-06-22 01:14:44 | [diff] [blame] | 9 | #import "remoting/ios/facade/remoting_oauth_authentication.h" |
nicholss | c372b335 | 2017-05-09 17:17:33 | [diff] [blame] | 10 | |
| 11 | #import <Foundation/Foundation.h> |
| 12 | #import <Security/Security.h> |
| 13 | |
Sylvain Defresne | 0d298d1 | 2018-06-06 09:23:33 | [diff] [blame] | 14 | #import "base/bind.h" |
Scott Nichols | 9e6de2f | 2017-07-13 00:55:10 | [diff] [blame] | 15 | #import "ios/third_party/material_components_ios/src/components/Snackbar/src/MaterialSnackbar.h" |
nicholss | ed15807 | 2017-05-10 16:39:14 | [diff] [blame] | 16 | #import "remoting/ios/facade/host_info.h" |
| 17 | #import "remoting/ios/facade/host_list_fetcher.h" |
| 18 | #import "remoting/ios/facade/ios_client_runtime_delegate.h" |
| 19 | #import "remoting/ios/facade/remoting_service.h" |
Yuwei Huang | 15b7efb | 2018-02-22 00:23:52 | [diff] [blame] | 20 | #import "remoting/ios/persistence/remoting_keychain.h" |
Scott Nichols | 68022ff | 2017-07-18 00:12:27 | [diff] [blame] | 21 | #import "remoting/ios/persistence/remoting_preferences.h" |
nicholss | c372b335 | 2017-05-09 17:17:33 | [diff] [blame] | 22 | |
| 23 | #include "base/logging.h" |
| 24 | #include "base/strings/sys_string_conversions.h" |
| 25 | #include "net/url_request/url_request_context_getter.h" |
| 26 | #include "remoting/base/oauth_token_getter.h" |
| 27 | #include "remoting/base/oauth_token_getter_impl.h" |
Maks Orlovich | 8db7d0d6 | 2018-08-16 19:22:27 | [diff] [blame] | 28 | #include "services/network/public/cpp/shared_url_loader_factory.h" |
nicholss | c372b335 | 2017-05-09 17:17:33 | [diff] [blame] | 29 | |
Yuwei Huang | 15b7efb | 2018-02-22 00:23:52 | [diff] [blame] | 30 | static const char kOauthRedirectUrl[] = |
nicholss | c372b335 | 2017-05-09 17:17:33 | [diff] [blame] | 31 | "https://chromoting-oauth.talkgadget." |
| 32 | "google.com/talkgadget/oauth/chrome-remote-desktop/dev"; |
| 33 | |
Yuwei Huang | 15b7efb | 2018-02-22 00:23:52 | [diff] [blame] | 34 | // We currently don't support multi-account sign in for OAuth authentication, so |
| 35 | // we store the current refresh token for an unspecified account. If we later |
| 36 | // decide to support multi-account sign in, we may use the user email as the |
| 37 | // account name when storing the refresh token and store the current user email |
| 38 | // in UserDefaults. |
| 39 | static const auto kRefreshTokenAccount = |
| 40 | remoting::Keychain::kUnspecifiedAccount; |
| 41 | |
nicholss | c372b335 | 2017-05-09 17:17:33 | [diff] [blame] | 42 | std::unique_ptr<remoting::OAuthTokenGetter> |
| 43 | CreateOAuthTokenGetterWithAuthorizationCode( |
| 44 | const std::string& auth_code, |
| 45 | const remoting::OAuthTokenGetter::CredentialsUpdatedCallback& |
| 46 | on_credentials_update) { |
| 47 | std::unique_ptr<remoting::OAuthTokenGetter::OAuthIntermediateCredentials> |
| 48 | oauth_credentials( |
| 49 | new remoting::OAuthTokenGetter::OAuthIntermediateCredentials( |
| 50 | auth_code, /*is_service_account=*/false)); |
| 51 | oauth_credentials->oauth_redirect_uri = kOauthRedirectUrl; |
| 52 | |
| 53 | std::unique_ptr<remoting::OAuthTokenGetter> oauth_tokenGetter( |
| 54 | new remoting::OAuthTokenGetterImpl( |
| 55 | std::move(oauth_credentials), on_credentials_update, |
Maks Orlovich | 8db7d0d6 | 2018-08-16 19:22:27 | [diff] [blame] | 56 | RemotingService.instance.runtime->url_loader_factory(), |
nicholss | c372b335 | 2017-05-09 17:17:33 | [diff] [blame] | 57 | /*auto_refresh=*/true)); |
| 58 | return oauth_tokenGetter; |
| 59 | } |
| 60 | |
| 61 | std::unique_ptr<remoting::OAuthTokenGetter> CreateOAuthTokenWithRefreshToken( |
| 62 | const std::string& refresh_token, |
| 63 | const std::string& email) { |
| 64 | std::unique_ptr<remoting::OAuthTokenGetter::OAuthAuthorizationCredentials> |
| 65 | oauth_credentials( |
| 66 | new remoting::OAuthTokenGetter::OAuthAuthorizationCredentials( |
| 67 | email, refresh_token, /*is_service_account=*/false)); |
| 68 | |
| 69 | std::unique_ptr<remoting::OAuthTokenGetter> oauth_tokenGetter( |
| 70 | new remoting::OAuthTokenGetterImpl( |
| 71 | std::move(oauth_credentials), |
Maks Orlovich | 8db7d0d6 | 2018-08-16 19:22:27 | [diff] [blame] | 72 | RemotingService.instance.runtime->url_loader_factory(), |
nicholss | c372b335 | 2017-05-09 17:17:33 | [diff] [blame] | 73 | /*auto_refresh=*/true)); |
| 74 | return oauth_tokenGetter; |
| 75 | } |
| 76 | |
yuweih | c5bfa5e2 | 2017-06-22 01:14:44 | [diff] [blame] | 77 | RemotingAuthenticationStatus oauthStatusToRemotingAuthenticationStatus( |
| 78 | remoting::OAuthTokenGetter::Status status) { |
| 79 | switch (status) { |
| 80 | case remoting::OAuthTokenGetter::Status::AUTH_ERROR: |
| 81 | return RemotingAuthenticationStatusAuthError; |
| 82 | case remoting::OAuthTokenGetter::Status::NETWORK_ERROR: |
| 83 | return RemotingAuthenticationStatusNetworkError; |
| 84 | case remoting::OAuthTokenGetter::Status::SUCCESS: |
| 85 | return RemotingAuthenticationStatusSuccess; |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | @interface RemotingOAuthAuthentication () { |
nicholss | c372b335 | 2017-05-09 17:17:33 | [diff] [blame] | 90 | std::unique_ptr<remoting::OAuthTokenGetter> _tokenGetter; |
nicholss | c372b335 | 2017-05-09 17:17:33 | [diff] [blame] | 91 | BOOL _firstLoadUserAttempt; |
| 92 | } |
| 93 | @end |
| 94 | |
yuweih | c5bfa5e2 | 2017-06-22 01:14:44 | [diff] [blame] | 95 | @implementation RemotingOAuthAuthentication |
nicholss | c372b335 | 2017-05-09 17:17:33 | [diff] [blame] | 96 | |
| 97 | @synthesize user = _user; |
| 98 | @synthesize delegate = _delegate; |
| 99 | |
| 100 | - (instancetype)init { |
| 101 | self = [super init]; |
| 102 | if (self) { |
nicholss | c372b335 | 2017-05-09 17:17:33 | [diff] [blame] | 103 | _user = nil; |
| 104 | _firstLoadUserAttempt = YES; |
| 105 | } |
| 106 | return self; |
| 107 | } |
| 108 | |
| 109 | #pragma mark - Property Overrides |
| 110 | |
| 111 | - (UserInfo*)user { |
| 112 | if (_firstLoadUserAttempt && _user == nil) { |
| 113 | _firstLoadUserAttempt = NO; |
| 114 | [self setUser:[self loadUserInfo]]; |
| 115 | } |
| 116 | return _user; |
| 117 | } |
| 118 | |
| 119 | - (void)setUser:(UserInfo*)user { |
| 120 | _user = user; |
| 121 | [self storeUserInfo:_user]; |
| 122 | [_delegate userDidUpdate:_user]; |
| 123 | } |
| 124 | |
| 125 | #pragma mark - Class Implementation |
| 126 | |
| 127 | - (void)authenticateWithAuthorizationCode:(NSString*)authorizationCode { |
yuweih | c5bfa5e2 | 2017-06-22 01:14:44 | [diff] [blame] | 128 | __weak RemotingOAuthAuthentication* weakSelf = self; |
nicholss | c372b335 | 2017-05-09 17:17:33 | [diff] [blame] | 129 | _tokenGetter = CreateOAuthTokenGetterWithAuthorizationCode( |
| 130 | std::string(base::SysNSStringToUTF8(authorizationCode)), |
Sylvain Defresne | 0d298d1 | 2018-06-06 09:23:33 | [diff] [blame] | 131 | base::BindRepeating( |
nicholss | c372b335 | 2017-05-09 17:17:33 | [diff] [blame] | 132 | ^(const std::string& user_email, const std::string& refresh_token) { |
nicholss | c372b335 | 2017-05-09 17:17:33 | [diff] [blame] | 133 | VLOG(1) << "New Creds: " << user_email << " " << refresh_token; |
| 134 | UserInfo* user = [[UserInfo alloc] init]; |
| 135 | user.userEmail = base::SysUTF8ToNSString(user_email); |
| 136 | user.refreshToken = base::SysUTF8ToNSString(refresh_token); |
| 137 | [weakSelf setUser:user]; |
| 138 | })); |
| 139 | // Stimulate the oAuth Token Getter to fetch and access token, this forces it |
| 140 | // to convert the authorization code into a refresh token, and saving the |
| 141 | // refresh token will happen automaticly in the above block. |
yuweih | c5bfa5e2 | 2017-06-22 01:14:44 | [diff] [blame] | 142 | [self callbackWithAccessToken:^(RemotingAuthenticationStatus status, |
| 143 | NSString* user_email, |
| 144 | NSString* access_token) { |
| 145 | if (status == RemotingAuthenticationStatusSuccess) { |
| 146 | VLOG(1) << "Success fetching access token from authorization code."; |
| 147 | } else { |
| 148 | LOG(ERROR) << "Failed to fetch access token from authorization code. (" |
| 149 | << status << ")"; |
Scott Nichols | 9e6de2f | 2017-07-13 00:55:10 | [diff] [blame] | 150 | [MDCSnackbarManager |
| 151 | showMessage: |
| 152 | [MDCSnackbarMessage |
| 153 | messageWithText:@"Authentication Failed. Please try again."]]; |
yuweih | c5bfa5e2 | 2017-06-22 01:14:44 | [diff] [blame] | 154 | } |
| 155 | }]; |
nicholss | c372b335 | 2017-05-09 17:17:33 | [diff] [blame] | 156 | } |
| 157 | |
| 158 | #pragma mark - Private |
| 159 | |
| 160 | // Provide the |refreshToken| and |email| to authenticate a user as a returning |
| 161 | // user of the application. |
| 162 | - (void)authenticateWithRefreshToken:(NSString*)refreshToken |
| 163 | email:(NSString*)email { |
| 164 | _tokenGetter = CreateOAuthTokenWithRefreshToken( |
| 165 | std::string(base::SysNSStringToUTF8(refreshToken)), |
| 166 | base::SysNSStringToUTF8(email)); |
| 167 | } |
| 168 | |
yuweih | c5bfa5e2 | 2017-06-22 01:14:44 | [diff] [blame] | 169 | - (void)callbackWithAccessToken:(AccessTokenCallback)onAccessToken { |
Yuwei Huang | 202cd8ea | 2017-11-29 00:16:16 | [diff] [blame] | 170 | // Be careful here since a failure to reset onAccessToken will end up with |
| 171 | // retain cycle and memory leakage. |
nicholss | c372b335 | 2017-05-09 17:17:33 | [diff] [blame] | 172 | if (_tokenGetter) { |
Sylvain Defresne | 0d298d1 | 2018-06-06 09:23:33 | [diff] [blame] | 173 | _tokenGetter->CallWithToken(base::BindRepeating( |
yuweih | c5bfa5e2 | 2017-06-22 01:14:44 | [diff] [blame] | 174 | ^(remoting::OAuthTokenGetter::Status status, |
| 175 | const std::string& user_email, const std::string& access_token) { |
| 176 | onAccessToken(oauthStatusToRemotingAuthenticationStatus(status), |
| 177 | base::SysUTF8ToNSString(user_email), |
| 178 | base::SysUTF8ToNSString(access_token)); |
| 179 | })); |
nicholss | c372b335 | 2017-05-09 17:17:33 | [diff] [blame] | 180 | } |
| 181 | } |
| 182 | |
| 183 | - (void)logout { |
| 184 | [self storeUserInfo:nil]; |
| 185 | [self setUser:nil]; |
| 186 | } |
| 187 | |
| 188 | #pragma mark - Persistence |
| 189 | |
| 190 | - (void)storeUserInfo:(UserInfo*)user { |
nicholss | c372b335 | 2017-05-09 17:17:33 | [diff] [blame] | 191 | if (user) { |
Scott Nichols | 68022ff | 2017-07-18 00:12:27 | [diff] [blame] | 192 | [RemotingPreferences instance].activeUserKey = user.userEmail; |
Yuwei Huang | 15b7efb | 2018-02-22 00:23:52 | [diff] [blame] | 193 | std::string refreshToken = base::SysNSStringToUTF8(user.refreshToken); |
| 194 | remoting::RemotingKeychain::GetInstance()->SetData( |
| 195 | remoting::Keychain::Key::REFRESH_TOKEN, kRefreshTokenAccount, |
| 196 | refreshToken); |
nicholss | c372b335 | 2017-05-09 17:17:33 | [diff] [blame] | 197 | } else { |
Scott Nichols | 68022ff | 2017-07-18 00:12:27 | [diff] [blame] | 198 | [RemotingPreferences instance].activeUserKey = nil; |
Yuwei Huang | 15b7efb | 2018-02-22 00:23:52 | [diff] [blame] | 199 | remoting::RemotingKeychain::GetInstance()->RemoveData( |
| 200 | remoting::Keychain::Key::REFRESH_TOKEN, kRefreshTokenAccount); |
nicholss | c372b335 | 2017-05-09 17:17:33 | [diff] [blame] | 201 | } |
nicholss | c372b335 | 2017-05-09 17:17:33 | [diff] [blame] | 202 | } |
| 203 | |
| 204 | - (UserInfo*)loadUserInfo { |
| 205 | UserInfo* user = [[UserInfo alloc] init]; |
Scott Nichols | 68022ff | 2017-07-18 00:12:27 | [diff] [blame] | 206 | user.userEmail = [RemotingPreferences instance].activeUserKey; |
Yuwei Huang | 15b7efb | 2018-02-22 00:23:52 | [diff] [blame] | 207 | std::string refreshTokenString = |
| 208 | remoting::RemotingKeychain::GetInstance()->GetData( |
| 209 | remoting::Keychain::Key::REFRESH_TOKEN, kRefreshTokenAccount); |
| 210 | user.refreshToken = base::SysUTF8ToNSString(refreshTokenString); |
nicholss | c372b335 | 2017-05-09 17:17:33 | [diff] [blame] | 211 | |
| 212 | if (!user || ![user isAuthenticated]) { |
| 213 | user = nil; |
| 214 | } else { |
| 215 | [self authenticateWithRefreshToken:user.refreshToken email:user.userEmail]; |
| 216 | } |
| 217 | return user; |
| 218 | } |
| 219 | |
| 220 | @end |