1// Copyright 2013 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/ui/webui/identity_internals_ui.h"
6
7#include <set>
8#include <string>
9
10#include "base/bind.h"
11#include "base/i18n/time_formatting.h"
12#include "base/strings/utf_string_conversions.h"
13#include "base/values.h"
14#include "chrome/browser/extensions/api/identity/identity_api.h"
15#include "chrome/browser/extensions/extension_service.h"
16#include "chrome/browser/profiles/profile.h"
17#include "chrome/common/url_constants.h"
18#include "chrome/grit/generated_resources.h"
19#include "content/public/browser/web_ui.h"
20#include "content/public/browser/web_ui_controller.h"
21#include "content/public/browser/web_ui_data_source.h"
22#include "content/public/browser/web_ui_message_handler.h"
23#include "extensions/browser/extension_system.h"
24#include "google_apis/gaia/gaia_auth_fetcher.h"
25#include "google_apis/gaia/gaia_constants.h"
26#include "grit/browser_resources.h"
27#include "ui/base/l10n/l10n_util.h"
28
29namespace {
30
31// Properties of the Javascript object representing a token.
32const char kExtensionId[] = "extensionId";
33const char kExtensionName[] = "extensionName";
34const char kScopes[] = "scopes";
35const char kStatus[] = "status";
36const char kTokenExpirationTime[] = "expirationTime";
37const char kAccessToken[] = "accessToken";
38
39// RevokeToken message parameter offsets.
40const int kRevokeTokenExtensionOffset = 0;
41const int kRevokeTokenTokenOffset = 1;
42
43class IdentityInternalsTokenRevoker;
44
45// Class acting as a controller of the chrome://identity-internals WebUI.
46class IdentityInternalsUIMessageHandler : public content::WebUIMessageHandler {
47 public:
48  IdentityInternalsUIMessageHandler();
49  virtual ~IdentityInternalsUIMessageHandler();
50
51  // Ensures that a proper clean up happens after a token is revoked. That
52  // includes deleting the |token_revoker|, removing the token from Identity API
53  // cache and updating the UI that the token is gone.
54  void OnTokenRevokerDone(IdentityInternalsTokenRevoker* token_revoker);
55
56  // WebUIMessageHandler implementation.
57  virtual void RegisterMessages() OVERRIDE;
58
59 private:
60  // Gets the name of an extension referred to by |token_cache_key| as a string.
61  const std::string GetExtensionName(
62      const extensions::ExtensionTokenKey& token_cache_key);
63
64  // Gets a list of scopes specified in |token_cache_key| and returns a pointer
65  // to a ListValue containing the scopes. The caller gets ownership of the
66  // returned object.
67  base::ListValue* GetScopes(
68      const extensions::ExtensionTokenKey& token_cache_key);
69
70  // Gets a localized status of the access token in |token_cache_value|.
71  const base::string16 GetStatus(
72      const extensions::IdentityTokenCacheValue& token_cache_value);
73
74  // Gets a string representation of an expiration time of the access token in
75  // |token_cache_value|.
76  const std::string GetExpirationTime(
77      const extensions::IdentityTokenCacheValue& token_cache_value);
78
79  // Converts a pair of |token_cache_key| and |token_cache_value| to a
80  // DictionaryValue object with corresponding information in a localized and
81  // readable form and returns a pointer to created object. Caller gets the
82  // ownership of the returned object.
83  base::DictionaryValue* GetInfoForToken(
84      const extensions::ExtensionTokenKey& token_cache_key,
85      const extensions::IdentityTokenCacheValue& token_cache_value);
86
87  // Gets all of the tokens stored in IdentityAPI token cache and returns them
88  // to the caller using Javascript callback function
89  // |identity_internals.returnTokens()|.
90  void GetInfoForAllTokens(const base::ListValue* args);
91
92  // Initiates revoking of the token, based on the extension ID and token
93  // passed as entries in the |args| list. Updates the caller of completion
94  // using Javascript callback function |identity_internals.tokenRevokeDone()|.
95  void RevokeToken(const base::ListValue* args);
96
97  // A vector of token revokers that are currently revoking tokens.
98  ScopedVector<IdentityInternalsTokenRevoker> token_revokers_;
99};
100
101// Handles the revoking of an access token and helps performing the clean up
102// after it is revoked by holding information about the access token and related
103// extension ID.
104class IdentityInternalsTokenRevoker : public GaiaAuthConsumer {
105 public:
106  // Revokes |access_token| from extension with |extension_id|.
107  // |profile| is required for its request context. |consumer| will be
108  // notified when revocation succeeds via |OnTokenRevokerDone()|.
109  IdentityInternalsTokenRevoker(const std::string& extension_id,
110                                const std::string& access_token,
111                                Profile* profile,
112                                IdentityInternalsUIMessageHandler* consumer);
113  virtual ~IdentityInternalsTokenRevoker();
114
115  // Returns the access token being revoked.
116  const std::string& access_token() const { return access_token_; }
117
118  // Returns the ID of the extension the access token is related to.
119  const std::string& extension_id() const { return extension_id_; }
120
121  // GaiaAuthConsumer implementation.
122  virtual void OnOAuth2RevokeTokenCompleted() OVERRIDE;
123
124 private:
125  // An object used to start a token revoke request.
126  GaiaAuthFetcher fetcher_;
127  // An ID of an extension the access token is related to.
128  const std::string extension_id_;
129  // The access token to revoke.
130  const std::string access_token_;
131  // An object that needs to be notified once the access token is revoked.
132  IdentityInternalsUIMessageHandler* consumer_;  // weak.
133
134  DISALLOW_COPY_AND_ASSIGN(IdentityInternalsTokenRevoker);
135};
136
137IdentityInternalsUIMessageHandler::IdentityInternalsUIMessageHandler() {}
138
139IdentityInternalsUIMessageHandler::~IdentityInternalsUIMessageHandler() {}
140
141void IdentityInternalsUIMessageHandler::OnTokenRevokerDone(
142    IdentityInternalsTokenRevoker* token_revoker) {
143  // Remove token from the cache.
144  extensions::IdentityAPI::GetFactoryInstance()
145      ->Get(Profile::FromWebUI(web_ui()))
146      ->EraseCachedToken(token_revoker->extension_id(),
147                         token_revoker->access_token());
148
149  // Update view about the token being removed.
150  base::ListValue result;
151  result.AppendString(token_revoker->access_token());
152  web_ui()->CallJavascriptFunction("identity_internals.tokenRevokeDone",
153                                   result);
154
155  // Erase the revoker.
156  ScopedVector<IdentityInternalsTokenRevoker>::iterator iter =
157      std::find(token_revokers_.begin(), token_revokers_.end(), token_revoker);
158  DCHECK(iter != token_revokers_.end());
159  token_revokers_.erase(iter);
160}
161
162const std::string IdentityInternalsUIMessageHandler::GetExtensionName(
163    const extensions::ExtensionTokenKey& token_cache_key) {
164  ExtensionService* extension_service = extensions::ExtensionSystem::Get(
165      Profile::FromWebUI(web_ui()))->extension_service();
166  const extensions::Extension* extension =
167      extension_service->extensions()->GetByID(token_cache_key.extension_id);
168  if (!extension)
169    return std::string();
170  return extension->name();
171}
172
173base::ListValue* IdentityInternalsUIMessageHandler::GetScopes(
174    const extensions::ExtensionTokenKey& token_cache_key) {
175  base::ListValue* scopes_value = new base::ListValue();
176  for (std::set<std::string>::const_iterator
177           iter = token_cache_key.scopes.begin();
178       iter != token_cache_key.scopes.end(); ++iter) {
179    scopes_value->AppendString(*iter);
180  }
181  return scopes_value;
182}
183
184const base::string16 IdentityInternalsUIMessageHandler::GetStatus(
185    const extensions::IdentityTokenCacheValue& token_cache_value) {
186  switch (token_cache_value.status()) {
187    case extensions::IdentityTokenCacheValue::CACHE_STATUS_ADVICE:
188      // Fallthrough to NOT FOUND case, as ADVICE is short lived.
189    case extensions::IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND:
190      return l10n_util::GetStringUTF16(
191          IDS_IDENTITY_INTERNALS_TOKEN_NOT_FOUND);
192    case extensions::IdentityTokenCacheValue::CACHE_STATUS_TOKEN:
193      return l10n_util::GetStringUTF16(
194          IDS_IDENTITY_INTERNALS_TOKEN_PRESENT);
195  }
196  NOTREACHED();
197  return base::string16();
198}
199
200const std::string IdentityInternalsUIMessageHandler::GetExpirationTime(
201    const extensions::IdentityTokenCacheValue& token_cache_value) {
202  return base::UTF16ToUTF8(base::TimeFormatFriendlyDateAndTime(
203      token_cache_value.expiration_time()));
204}
205
206base::DictionaryValue* IdentityInternalsUIMessageHandler::GetInfoForToken(
207    const extensions::ExtensionTokenKey& token_cache_key,
208    const extensions::IdentityTokenCacheValue& token_cache_value) {
209  base::DictionaryValue* token_data = new base::DictionaryValue();
210  token_data->SetString(kExtensionId, token_cache_key.extension_id);
211  token_data->SetString(kExtensionName, GetExtensionName(token_cache_key));
212  token_data->Set(kScopes, GetScopes(token_cache_key));
213  token_data->SetString(kStatus, GetStatus(token_cache_value));
214  token_data->SetString(kAccessToken, token_cache_value.token());
215  token_data->SetString(kTokenExpirationTime,
216                        GetExpirationTime(token_cache_value));
217  return token_data;
218}
219
220void IdentityInternalsUIMessageHandler::GetInfoForAllTokens(
221    const base::ListValue* args) {
222  base::ListValue results;
223  extensions::IdentityAPI::CachedTokens tokens =
224      extensions::IdentityAPI::GetFactoryInstance()
225          ->Get(Profile::FromWebUI(web_ui()))
226          ->GetAllCachedTokens();
227  for (extensions::IdentityAPI::CachedTokens::const_iterator
228           iter = tokens.begin(); iter != tokens.end(); ++iter) {
229    results.Append(GetInfoForToken(iter->first, iter->second));
230  }
231
232  web_ui()->CallJavascriptFunction("identity_internals.returnTokens", results);
233}
234
235void IdentityInternalsUIMessageHandler::RegisterMessages() {
236  web_ui()->RegisterMessageCallback("identityInternalsGetTokens",
237      base::Bind(&IdentityInternalsUIMessageHandler::GetInfoForAllTokens,
238                 base::Unretained(this)));
239  web_ui()->RegisterMessageCallback("identityInternalsRevokeToken",
240      base::Bind(&IdentityInternalsUIMessageHandler::RevokeToken,
241                 base::Unretained(this)));
242}
243
244void IdentityInternalsUIMessageHandler::RevokeToken(
245    const base::ListValue* args) {
246  std::string extension_id;
247  std::string access_token;
248  args->GetString(kRevokeTokenExtensionOffset, &extension_id);
249  args->GetString(kRevokeTokenTokenOffset, &access_token);
250  token_revokers_.push_back(new IdentityInternalsTokenRevoker(
251      extension_id, access_token, Profile::FromWebUI(web_ui()), this));
252}
253
254IdentityInternalsTokenRevoker::IdentityInternalsTokenRevoker(
255    const std::string& extension_id,
256    const std::string& access_token,
257    Profile* profile,
258    IdentityInternalsUIMessageHandler* consumer)
259    : fetcher_(this, GaiaConstants::kChromeSource,
260               profile->GetRequestContext()),
261      extension_id_(extension_id),
262      access_token_(access_token),
263      consumer_(consumer) {
264  DCHECK(consumer_);
265  fetcher_.StartRevokeOAuth2Token(access_token);
266}
267
268IdentityInternalsTokenRevoker::~IdentityInternalsTokenRevoker() {}
269
270void IdentityInternalsTokenRevoker::OnOAuth2RevokeTokenCompleted() {
271  consumer_->OnTokenRevokerDone(this);
272}
273
274}  // namespace
275
276IdentityInternalsUI::IdentityInternalsUI(content::WebUI* web_ui)
277  : content::WebUIController(web_ui) {
278  // chrome://identity-internals source.
279  content::WebUIDataSource* html_source =
280    content::WebUIDataSource::Create(chrome::kChromeUIIdentityInternalsHost);
281  html_source->SetUseJsonJSFormatV2();
282
283  // Localized strings
284  html_source->AddLocalizedString("tokenCacheHeader",
285      IDS_IDENTITY_INTERNALS_TOKEN_CACHE_TEXT);
286  html_source->AddLocalizedString("accessToken",
287      IDS_IDENTITY_INTERNALS_ACCESS_TOKEN);
288  html_source->AddLocalizedString("extensionName",
289      IDS_IDENTITY_INTERNALS_EXTENSION_NAME);
290  html_source->AddLocalizedString("extensionId",
291      IDS_IDENTITY_INTERNALS_EXTENSION_ID);
292  html_source->AddLocalizedString("tokenStatus",
293      IDS_IDENTITY_INTERNALS_TOKEN_STATUS);
294  html_source->AddLocalizedString("expirationTime",
295      IDS_IDENTITY_INTERNALS_EXPIRATION_TIME);
296  html_source->AddLocalizedString("scopes",
297      IDS_IDENTITY_INTERNALS_SCOPES);
298  html_source->AddLocalizedString("revoke",
299      IDS_IDENTITY_INTERNALS_REVOKE);
300  html_source->SetJsonPath("strings.js");
301
302  // Required resources
303  html_source->AddResourcePath("identity_internals.css",
304      IDR_IDENTITY_INTERNALS_CSS);
305  html_source->AddResourcePath("identity_internals.js",
306      IDR_IDENTITY_INTERNALS_JS);
307  html_source->SetDefaultResource(IDR_IDENTITY_INTERNALS_HTML);
308
309  content::WebUIDataSource::Add(Profile::FromWebUI(web_ui), html_source);
310
311  web_ui->AddMessageHandler(new IdentityInternalsUIMessageHandler());
312}
313
314IdentityInternalsUI::~IdentityInternalsUI() {}
315