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