blob: 7f256988dcf8477f422b891152daf0909cddf6c2 [file] [log] [blame]
nicholssc372b3352017-05-09 17:17:331// 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
yuweihc5bfa5e22017-06-22 01:14:449#import "remoting/ios/facade/remoting_oauth_authentication.h"
nicholssc372b3352017-05-09 17:17:3310
11#import <Foundation/Foundation.h>
12#import <Security/Security.h>
13
Sylvain Defresne0d298d12018-06-06 09:23:3314#import "base/bind.h"
Scott Nichols9e6de2f2017-07-13 00:55:1015#import "ios/third_party/material_components_ios/src/components/Snackbar/src/MaterialSnackbar.h"
nicholssed158072017-05-10 16:39:1416#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 Huang15b7efb2018-02-22 00:23:5220#import "remoting/ios/persistence/remoting_keychain.h"
Scott Nichols68022ff2017-07-18 00:12:2721#import "remoting/ios/persistence/remoting_preferences.h"
nicholssc372b3352017-05-09 17:17:3322
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 Orlovich8db7d0d62018-08-16 19:22:2728#include "services/network/public/cpp/shared_url_loader_factory.h"
nicholssc372b3352017-05-09 17:17:3329
Yuwei Huang15b7efb2018-02-22 00:23:5230static const char kOauthRedirectUrl[] =
nicholssc372b3352017-05-09 17:17:3331 "https://chromoting-oauth.talkgadget."
32 "google.com/talkgadget/oauth/chrome-remote-desktop/dev";
33
Yuwei Huang15b7efb2018-02-22 00:23:5234// 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.
39static const auto kRefreshTokenAccount =
40 remoting::Keychain::kUnspecifiedAccount;
41
nicholssc372b3352017-05-09 17:17:3342std::unique_ptr<remoting::OAuthTokenGetter>
43CreateOAuthTokenGetterWithAuthorizationCode(
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 Orlovich8db7d0d62018-08-16 19:22:2756 RemotingService.instance.runtime->url_loader_factory(),
nicholssc372b3352017-05-09 17:17:3357 /*auto_refresh=*/true));
58 return oauth_tokenGetter;
59}
60
61std::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 Orlovich8db7d0d62018-08-16 19:22:2772 RemotingService.instance.runtime->url_loader_factory(),
nicholssc372b3352017-05-09 17:17:3373 /*auto_refresh=*/true));
74 return oauth_tokenGetter;
75}
76
yuweihc5bfa5e22017-06-22 01:14:4477RemotingAuthenticationStatus 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 () {
nicholssc372b3352017-05-09 17:17:3390 std::unique_ptr<remoting::OAuthTokenGetter> _tokenGetter;
nicholssc372b3352017-05-09 17:17:3391 BOOL _firstLoadUserAttempt;
92}
93@end
94
yuweihc5bfa5e22017-06-22 01:14:4495@implementation RemotingOAuthAuthentication
nicholssc372b3352017-05-09 17:17:3396
97@synthesize user = _user;
98@synthesize delegate = _delegate;
99
100- (instancetype)init {
101 self = [super init];
102 if (self) {
nicholssc372b3352017-05-09 17:17:33103 _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 {
yuweihc5bfa5e22017-06-22 01:14:44128 __weak RemotingOAuthAuthentication* weakSelf = self;
nicholssc372b3352017-05-09 17:17:33129 _tokenGetter = CreateOAuthTokenGetterWithAuthorizationCode(
130 std::string(base::SysNSStringToUTF8(authorizationCode)),
Sylvain Defresne0d298d12018-06-06 09:23:33131 base::BindRepeating(
nicholssc372b3352017-05-09 17:17:33132 ^(const std::string& user_email, const std::string& refresh_token) {
nicholssc372b3352017-05-09 17:17:33133 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.
yuweihc5bfa5e22017-06-22 01:14:44142 [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 Nichols9e6de2f2017-07-13 00:55:10150 [MDCSnackbarManager
151 showMessage:
152 [MDCSnackbarMessage
153 messageWithText:@"Authentication Failed. Please try again."]];
yuweihc5bfa5e22017-06-22 01:14:44154 }
155 }];
nicholssc372b3352017-05-09 17:17:33156}
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
yuweihc5bfa5e22017-06-22 01:14:44169- (void)callbackWithAccessToken:(AccessTokenCallback)onAccessToken {
Yuwei Huang202cd8ea2017-11-29 00:16:16170 // Be careful here since a failure to reset onAccessToken will end up with
171 // retain cycle and memory leakage.
nicholssc372b3352017-05-09 17:17:33172 if (_tokenGetter) {
Sylvain Defresne0d298d12018-06-06 09:23:33173 _tokenGetter->CallWithToken(base::BindRepeating(
yuweihc5bfa5e22017-06-22 01:14:44174 ^(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 }));
nicholssc372b3352017-05-09 17:17:33180 }
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 {
nicholssc372b3352017-05-09 17:17:33191 if (user) {
Scott Nichols68022ff2017-07-18 00:12:27192 [RemotingPreferences instance].activeUserKey = user.userEmail;
Yuwei Huang15b7efb2018-02-22 00:23:52193 std::string refreshToken = base::SysNSStringToUTF8(user.refreshToken);
194 remoting::RemotingKeychain::GetInstance()->SetData(
195 remoting::Keychain::Key::REFRESH_TOKEN, kRefreshTokenAccount,
196 refreshToken);
nicholssc372b3352017-05-09 17:17:33197 } else {
Scott Nichols68022ff2017-07-18 00:12:27198 [RemotingPreferences instance].activeUserKey = nil;
Yuwei Huang15b7efb2018-02-22 00:23:52199 remoting::RemotingKeychain::GetInstance()->RemoveData(
200 remoting::Keychain::Key::REFRESH_TOKEN, kRefreshTokenAccount);
nicholssc372b3352017-05-09 17:17:33201 }
nicholssc372b3352017-05-09 17:17:33202}
203
204- (UserInfo*)loadUserInfo {
205 UserInfo* user = [[UserInfo alloc] init];
Scott Nichols68022ff2017-07-18 00:12:27206 user.userEmail = [RemotingPreferences instance].activeUserKey;
Yuwei Huang15b7efb2018-02-22 00:23:52207 std::string refreshTokenString =
208 remoting::RemotingKeychain::GetInstance()->GetData(
209 remoting::Keychain::Key::REFRESH_TOKEN, kRefreshTokenAccount);
210 user.refreshToken = base::SysUTF8ToNSString(refreshTokenString);
nicholssc372b3352017-05-09 17:17:33211
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