1 // Copyright 2015 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 #include "chrome/browser/chromeos/login/signin/token_handle_util.h"
6 
7 #include "base/memory/weak_ptr.h"
8 #include "base/metrics/histogram_macros.h"
9 #include "base/util/values/values_util.h"
10 #include "base/values.h"
11 #include "chrome/browser/chromeos/profiles/profile_helper.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "components/user_manager/known_user.h"
14 #include "google_apis/gaia/gaia_oauth_client.h"
15 #include "services/network/public/cpp/shared_url_loader_factory.h"
16 
17 namespace {
18 
19 const char kTokenHandlePref[] = "PasswordTokenHandle";
20 const char kTokenHandleStatusPref[] = "TokenHandleStatus";
21 const char kTokenHandleLastCheckedPref[] = "TokenHandleLastChecked";
22 
23 const char kHandleStatusValid[] = "valid";
24 const char kHandleStatusInvalid[] = "invalid";
25 const char* const kDefaultHandleStatus = kHandleStatusValid;
26 
27 constexpr int kMaxRetries = 3;
28 
29 constexpr base::TimeDelta kCacheStatusTime = base::TimeDelta::FromHours(1);
30 
31 const char* g_invalid_token_for_testing = nullptr;
32 
MaybeReturnCachedStatus(const AccountId & account_id,const TokenHandleUtil::TokenValidationCallback & callback)33 bool MaybeReturnCachedStatus(
34     const AccountId& account_id,
35     const TokenHandleUtil::TokenValidationCallback& callback) {
36   std::string saved_status;
37   if (!user_manager::known_user::GetStringPref(
38           account_id, kTokenHandleStatusPref, &saved_status)) {
39     return false;
40   }
41 
42   if (saved_status == kHandleStatusValid) {
43     callback.Run(account_id, TokenHandleUtil::VALID);
44     return true;
45   }
46 
47   if (saved_status == kHandleStatusInvalid) {
48     callback.Run(account_id, TokenHandleUtil::INVALID);
49     return true;
50   }
51 
52   NOTREACHED();
53   return false;
54 }
55 
OnStatusChecked(const TokenHandleUtil::TokenValidationCallback & callback,const AccountId & account_id,TokenHandleUtil::TokenHandleStatus status)56 void OnStatusChecked(const TokenHandleUtil::TokenValidationCallback& callback,
57                      const AccountId& account_id,
58                      TokenHandleUtil::TokenHandleStatus status) {
59   if (status != TokenHandleUtil::UNKNOWN) {
60     // Update last checked timestamp.
61     user_manager::known_user::SetPref(account_id, kTokenHandleLastCheckedPref,
62                                       util::TimeToValue(base::Time::Now()));
63   }
64 
65   if (status == TokenHandleUtil::INVALID) {
66     user_manager::known_user::SetStringPref(account_id, kTokenHandleStatusPref,
67                                             kHandleStatusInvalid);
68   }
69   callback.Run(account_id, status);
70 }
71 
72 }  // namespace
73 
TokenHandleUtil()74 TokenHandleUtil::TokenHandleUtil() {}
75 
~TokenHandleUtil()76 TokenHandleUtil::~TokenHandleUtil() {}
77 
78 // static
HasToken(const AccountId & account_id)79 bool TokenHandleUtil::HasToken(const AccountId& account_id) {
80   const base::DictionaryValue* dict = nullptr;
81   std::string token;
82   if (!user_manager::known_user::FindPrefs(account_id, &dict))
83     return false;
84   if (!dict->GetString(kTokenHandlePref, &token))
85     return false;
86   return !token.empty();
87 }
88 
89 // static
IsRecentlyChecked(const AccountId & account_id)90 bool TokenHandleUtil::IsRecentlyChecked(const AccountId& account_id) {
91   const base::Value* value;
92   if (!user_manager::known_user::GetPref(account_id,
93                                          kTokenHandleLastCheckedPref, &value)) {
94     return false;
95   }
96 
97   base::Optional<base::Time> last_checked = util::ValueToTime(value);
98   if (!last_checked.has_value()) {
99     return false;
100   }
101 
102   return base::Time::Now() - last_checked.value() < kCacheStatusTime;
103 }
104 
105 // static
ShouldObtainHandle(const AccountId & account_id)106 bool TokenHandleUtil::ShouldObtainHandle(const AccountId& account_id) {
107   const base::DictionaryValue* dict = nullptr;
108   std::string token;
109   if (!user_manager::known_user::FindPrefs(account_id, &dict))
110     return true;
111   if (!dict->GetString(kTokenHandlePref, &token))
112     return true;
113   if (token.empty())
114     return true;
115   std::string status(kDefaultHandleStatus);
116   dict->GetString(kTokenHandleStatusPref, &status);
117   return kHandleStatusInvalid == status;
118 }
119 
120 // static
CheckToken(const AccountId & account_id,scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,const TokenValidationCallback & callback)121 void TokenHandleUtil::CheckToken(
122     const AccountId& account_id,
123     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
124     const TokenValidationCallback& callback) {
125   const base::DictionaryValue* dict = nullptr;
126   std::string token;
127   if (!user_manager::known_user::FindPrefs(account_id, &dict)) {
128     callback.Run(account_id, UNKNOWN);
129     return;
130   }
131   if (!dict->GetString(kTokenHandlePref, &token)) {
132     callback.Run(account_id, UNKNOWN);
133     return;
134   }
135 
136   if (g_invalid_token_for_testing && g_invalid_token_for_testing == token) {
137     callback.Run(account_id, INVALID);
138     return;
139   }
140 
141   if (IsRecentlyChecked(account_id) &&
142       MaybeReturnCachedStatus(account_id, callback)) {
143     return;
144   }
145 
146   // Constructor starts validation.
147   validation_delegates_[token] = std::make_unique<TokenDelegate>(
148       weak_factory_.GetWeakPtr(), account_id, token,
149       std::move(url_loader_factory), base::Bind(&OnStatusChecked, callback));
150 }
151 
152 // static
StoreTokenHandle(const AccountId & account_id,const std::string & handle)153 void TokenHandleUtil::StoreTokenHandle(const AccountId& account_id,
154                                        const std::string& handle) {
155   user_manager::known_user::SetStringPref(account_id, kTokenHandlePref, handle);
156   user_manager::known_user::SetStringPref(account_id, kTokenHandleStatusPref,
157                                           kHandleStatusValid);
158   user_manager::known_user::SetPref(account_id, kTokenHandleLastCheckedPref,
159                                     util::TimeToValue(base::Time::Now()));
160 }
161 
162 // static
SetInvalidTokenForTesting(const char * token)163 void TokenHandleUtil::SetInvalidTokenForTesting(const char* token) {
164   g_invalid_token_for_testing = token;
165 }
166 
167 // static
SetLastCheckedPrefForTesting(const AccountId & account_id,base::Time time)168 void TokenHandleUtil::SetLastCheckedPrefForTesting(const AccountId& account_id,
169                                                    base::Time time) {
170   user_manager::known_user::SetPref(account_id, kTokenHandleLastCheckedPref,
171                                     util::TimeToValue(time));
172 }
173 
OnValidationComplete(const std::string & token)174 void TokenHandleUtil::OnValidationComplete(const std::string& token) {
175   validation_delegates_.erase(token);
176 }
177 
TokenDelegate(const base::WeakPtr<TokenHandleUtil> & owner,const AccountId & account_id,const std::string & token,scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,const TokenValidationCallback & callback)178 TokenHandleUtil::TokenDelegate::TokenDelegate(
179     const base::WeakPtr<TokenHandleUtil>& owner,
180     const AccountId& account_id,
181     const std::string& token,
182     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
183     const TokenValidationCallback& callback)
184     : owner_(owner),
185       account_id_(account_id),
186       token_(token),
187       tokeninfo_response_start_time_(base::TimeTicks::Now()),
188       callback_(callback),
189       gaia_client_(std::move(url_loader_factory)) {
190   gaia_client_.GetTokenHandleInfo(token_, kMaxRetries, this);
191 }
192 
~TokenDelegate()193 TokenHandleUtil::TokenDelegate::~TokenDelegate() {}
194 
OnOAuthError()195 void TokenHandleUtil::TokenDelegate::OnOAuthError() {
196   callback_.Run(account_id_, INVALID);
197   NotifyDone();
198 }
199 
200 // Warning: NotifyDone() deletes `this`
NotifyDone()201 void TokenHandleUtil::TokenDelegate::NotifyDone() {
202   if (owner_)
203     owner_->OnValidationComplete(token_);
204 }
205 
OnNetworkError(int response_code)206 void TokenHandleUtil::TokenDelegate::OnNetworkError(int response_code) {
207   callback_.Run(account_id_, UNKNOWN);
208   NotifyDone();
209 }
210 
OnGetTokenInfoResponse(std::unique_ptr<base::DictionaryValue> token_info)211 void TokenHandleUtil::TokenDelegate::OnGetTokenInfoResponse(
212     std::unique_ptr<base::DictionaryValue> token_info) {
213   TokenHandleStatus outcome = UNKNOWN;
214   if (!token_info->HasKey("error")) {
215     int expires_in = 0;
216     if (token_info->GetInteger("expires_in", &expires_in))
217       outcome = (expires_in < 0) ? INVALID : VALID;
218   }
219 
220   const base::TimeDelta duration =
221       base::TimeTicks::Now() - tokeninfo_response_start_time_;
222   UMA_HISTOGRAM_TIMES("Login.TokenCheckResponseTime", duration);
223   callback_.Run(account_id_, outcome);
224   NotifyDone();
225 }
226